@usabledev/usable-chat 1.152.0 → 1.152.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/cli.js +292 -125
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -35469,10 +35469,13 @@ function contentText(content) {
|
|
|
35469
35469
|
return content.map((p28) => {
|
|
35470
35470
|
if (typeof p28.text === "string") return p28.text;
|
|
35471
35471
|
if (p28.type === "tool-call") return `[tool-call: ${String(p28.toolName ?? "")}]`;
|
|
35472
|
-
if (p28.type === "tool-result") {
|
|
35473
|
-
const out = p28.result ?? p28.output;
|
|
35472
|
+
if (p28.type === "tool-result" || p28.type === "tool_result") {
|
|
35473
|
+
const out = p28.result ?? p28.output ?? p28.content;
|
|
35474
35474
|
const s17 = typeof out === "string" ? out : JSON.stringify(out ?? "");
|
|
35475
|
-
|
|
35475
|
+
const pointers = s17.match(/\[⚠️ FULL RESULT TOO LARGE[^\]]*\]/g) ?? [];
|
|
35476
|
+
const tail2 = pointers.length ? `
|
|
35477
|
+
${pointers.join("\n")}` : "";
|
|
35478
|
+
return `[tool-result: ${s17.slice(0, 2e3)}${tail2}]`;
|
|
35476
35479
|
}
|
|
35477
35480
|
return "";
|
|
35478
35481
|
}).filter(Boolean).join("\n");
|
|
@@ -35485,6 +35488,64 @@ function buildCompactionTranscript(messages4) {
|
|
|
35485
35488
|
return text3.trim() ? `${m33.role.toUpperCase()}: ${text3}` : "";
|
|
35486
35489
|
}).filter(Boolean).join("\n\n");
|
|
35487
35490
|
}
|
|
35491
|
+
function fingerprintMessages(messages4) {
|
|
35492
|
+
let h25 = 5381;
|
|
35493
|
+
for (const m33 of messages4) {
|
|
35494
|
+
const s17 = m33.role + ":" + (typeof m33.content === "string" ? m33.content : JSON.stringify(m33.content)).slice(0, 200);
|
|
35495
|
+
for (let i18 = 0; i18 < s17.length; i18++) h25 = (h25 << 5) + h25 + s17.charCodeAt(i18) | 0;
|
|
35496
|
+
}
|
|
35497
|
+
return `${h25 >>> 0}:${messages4.length}`;
|
|
35498
|
+
}
|
|
35499
|
+
function getCachedCompaction(conversationId) {
|
|
35500
|
+
return compactionCache.get(conversationId);
|
|
35501
|
+
}
|
|
35502
|
+
function setCachedCompaction(conversationId, entry) {
|
|
35503
|
+
if (compactionCache.size >= COMPACTION_CACHE_MAX && !compactionCache.has(conversationId)) {
|
|
35504
|
+
const oldest = compactionCache.keys().next().value;
|
|
35505
|
+
if (oldest !== void 0) compactionCache.delete(oldest);
|
|
35506
|
+
}
|
|
35507
|
+
compactionCache.set(conversationId, entry);
|
|
35508
|
+
}
|
|
35509
|
+
async function generateCompactionSummaryCached(conversationId, dropped, opts) {
|
|
35510
|
+
const cached2 = conversationId ? getCachedCompaction(conversationId) : void 0;
|
|
35511
|
+
if (cached2 && dropped.length >= cached2.count) {
|
|
35512
|
+
const prefixFp = fingerprintMessages(dropped.slice(0, cached2.count));
|
|
35513
|
+
if (prefixFp === cached2.fingerprint) {
|
|
35514
|
+
if (dropped.length === cached2.count) {
|
|
35515
|
+
return { summary: cached2.summary, cacheHit: "exact" };
|
|
35516
|
+
}
|
|
35517
|
+
const delta = dropped.slice(cached2.count);
|
|
35518
|
+
const summary2 = await generateCompactionSummary(
|
|
35519
|
+
[
|
|
35520
|
+
{
|
|
35521
|
+
role: "user",
|
|
35522
|
+
content: `[prior checkpoint summary of the earlier conversation]
|
|
35523
|
+
${cached2.summary}`
|
|
35524
|
+
},
|
|
35525
|
+
...delta
|
|
35526
|
+
],
|
|
35527
|
+
opts
|
|
35528
|
+
);
|
|
35529
|
+
if (summary2 && conversationId) {
|
|
35530
|
+
setCachedCompaction(conversationId, {
|
|
35531
|
+
fingerprint: fingerprintMessages(dropped),
|
|
35532
|
+
count: dropped.length,
|
|
35533
|
+
summary: summary2
|
|
35534
|
+
});
|
|
35535
|
+
}
|
|
35536
|
+
return { summary: summary2, cacheHit: "rolling" };
|
|
35537
|
+
}
|
|
35538
|
+
}
|
|
35539
|
+
const summary = await generateCompactionSummary(dropped, opts);
|
|
35540
|
+
if (summary && conversationId) {
|
|
35541
|
+
setCachedCompaction(conversationId, {
|
|
35542
|
+
fingerprint: fingerprintMessages(dropped),
|
|
35543
|
+
count: dropped.length,
|
|
35544
|
+
summary
|
|
35545
|
+
});
|
|
35546
|
+
}
|
|
35547
|
+
return { summary, cacheHit: "miss" };
|
|
35548
|
+
}
|
|
35488
35549
|
async function viaOpenRouter(system, user, opts) {
|
|
35489
35550
|
const apiKey = opts.provider === "usable" ? opts.authToken ?? "" : opts.openRouterApiKey ?? "";
|
|
35490
35551
|
const provider = createOpenRouter({
|
|
@@ -35561,7 +35622,7 @@ async function generateCompactionSummary(messages4, opts) {
|
|
|
35561
35622
|
return null;
|
|
35562
35623
|
}
|
|
35563
35624
|
}
|
|
35564
|
-
var COMPACTION_SYSTEM_PROMPT, DEFAULT_CALLERS;
|
|
35625
|
+
var COMPACTION_SYSTEM_PROMPT, compactionCache, COMPACTION_CACHE_MAX, DEFAULT_CALLERS;
|
|
35565
35626
|
var init_compaction_summary = __esm({
|
|
35566
35627
|
"src/core/orchestrator/compaction-summary.ts"() {
|
|
35567
35628
|
"use strict";
|
|
@@ -35580,11 +35641,15 @@ var init_compaction_summary = __esm({
|
|
|
35580
35641
|
"- PROGRESS: what is DONE, what is IN-PROGRESS, what is BLOCKED.",
|
|
35581
35642
|
"- DECISIONS: key choices, constraints, and user preferences to honor.",
|
|
35582
35643
|
"- FILES: files read and files modified so far (paths).",
|
|
35644
|
+
'- DATA: where large tool results are preserved \u2014 copy any "[\u26A0\uFE0F FULL RESULT TOO LARGE \u2014 \u2026]"',
|
|
35645
|
+
" spill paths VERBATIM so the work can re-read them instead of re-calling the tools.",
|
|
35583
35646
|
"- NEXT: the concrete next steps to continue from here.",
|
|
35584
35647
|
"",
|
|
35585
35648
|
"Be factual and specific (keep ids, paths, names verbatim \u2014 never shorten UUIDs). Do not invent",
|
|
35586
35649
|
"progress that did not happen. Output the summary only \u2014 no preamble, no sign-off."
|
|
35587
35650
|
].join("\n");
|
|
35651
|
+
compactionCache = /* @__PURE__ */ new Map();
|
|
35652
|
+
COMPACTION_CACHE_MAX = 200;
|
|
35588
35653
|
DEFAULT_CALLERS = {
|
|
35589
35654
|
openrouter: viaOpenRouter,
|
|
35590
35655
|
anthropic: viaAnthropic,
|
|
@@ -35641,6 +35706,17 @@ function extractFileOps(messages4) {
|
|
|
35641
35706
|
}
|
|
35642
35707
|
return { read: [...read], modified: [...modified] };
|
|
35643
35708
|
}
|
|
35709
|
+
function extractSpillPointers(messages4) {
|
|
35710
|
+
const paths = /* @__PURE__ */ new Set();
|
|
35711
|
+
const re10 = /(?:\/data\/tool-results\/|[^\s"'\\]*usable-tool-results\/)[^\s"'\\\]]+/g;
|
|
35712
|
+
for (const msg of messages4) {
|
|
35713
|
+
const text3 = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
35714
|
+
for (const m33 of text3.match(re10) ?? []) {
|
|
35715
|
+
paths.add(m33.replace(/[.,;:!?]+$/, ""));
|
|
35716
|
+
}
|
|
35717
|
+
}
|
|
35718
|
+
return [...paths];
|
|
35719
|
+
}
|
|
35644
35720
|
function isStepStart(messages4, i18) {
|
|
35645
35721
|
if (i18 <= 0) return false;
|
|
35646
35722
|
return messages4[i18].role === "assistant" && messages4[i18 - 1].role !== "assistant";
|
|
@@ -164395,6 +164471,7 @@ var init_agents2 = __esm({
|
|
|
164395
164471
|
// src/core/utils/tool-result-summarizer.ts
|
|
164396
164472
|
var tool_result_summarizer_exports = {};
|
|
164397
164473
|
__export(tool_result_summarizer_exports, {
|
|
164474
|
+
dedupeFragmentContentFields: () => dedupeFragmentContentFields,
|
|
164398
164475
|
sanitizeImageToolResult: () => sanitizeImageToolResult,
|
|
164399
164476
|
summarizeToolResultForHistory: () => summarizeToolResultForHistory,
|
|
164400
164477
|
truncateToolResultWithNotice: () => truncateToolResultWithNotice
|
|
@@ -164443,12 +164520,52 @@ function extractFragmentReferences(result) {
|
|
|
164443
164520
|
}
|
|
164444
164521
|
function truncateToolResultWithNotice(serialized, maxChars) {
|
|
164445
164522
|
if (serialized.length <= maxChars) return serialized;
|
|
164446
|
-
|
|
164447
|
-
const
|
|
164448
|
-
|
|
164523
|
+
let head = serialized.slice(0, maxChars);
|
|
164524
|
+
const boundary = Math.max(
|
|
164525
|
+
head.lastIndexOf("},"),
|
|
164526
|
+
head.lastIndexOf("],"),
|
|
164527
|
+
head.lastIndexOf('",'),
|
|
164528
|
+
head.lastIndexOf("\n")
|
|
164529
|
+
);
|
|
164530
|
+
if (boundary > maxChars * 0.8) head = head.slice(0, boundary + 1);
|
|
164531
|
+
const keptKb = Math.round(head.length / 1024);
|
|
164532
|
+
const omittedKb = Math.max(1, Math.round((serialized.length - head.length) / 1024));
|
|
164449
164533
|
return `${head}
|
|
164450
164534
|
|
|
164451
|
-
[tool-result truncated \u2014 kept the first ${keptKb}KB,
|
|
164535
|
+
[tool-result truncated \u2014 kept the first ${keptKb}KB, ~${omittedKb}KB omitted. The omitted tail is NOT in this conversation unless a spill-file path is noted below. NEVER guess or reconstruct omitted values (ids, uuids, names) \u2014 re-run the tool with narrower filters/pagination to get the part you need, or read the spill file if a path is given.]`;
|
|
164536
|
+
}
|
|
164537
|
+
function dedupeFragmentContentFields(result) {
|
|
164538
|
+
if (!result || typeof result !== "object") return result;
|
|
164539
|
+
if (Array.isArray(result)) {
|
|
164540
|
+
let changed = false;
|
|
164541
|
+
const mapped = result.map((item) => {
|
|
164542
|
+
if (item && typeof item === "object" && item.type === "text" && typeof item.text === "string") {
|
|
164543
|
+
const text3 = item.text;
|
|
164544
|
+
if (text3.includes('"contentWithoutFrontmatter"')) {
|
|
164545
|
+
try {
|
|
164546
|
+
const parsed = JSON.parse(text3);
|
|
164547
|
+
const deduped = dedupeFragmentContentFields(parsed);
|
|
164548
|
+
if (deduped !== parsed) {
|
|
164549
|
+
changed = true;
|
|
164550
|
+
return { ...item, text: JSON.stringify(deduped) };
|
|
164551
|
+
}
|
|
164552
|
+
} catch {
|
|
164553
|
+
}
|
|
164554
|
+
}
|
|
164555
|
+
}
|
|
164556
|
+
return item;
|
|
164557
|
+
});
|
|
164558
|
+
return changed ? mapped : result;
|
|
164559
|
+
}
|
|
164560
|
+
const r17 = result;
|
|
164561
|
+
if (typeof r17.content === "string" && typeof r17.contentWithoutFrontmatter === "string" && r17.contentWithoutFrontmatter.length > 1e3) {
|
|
164562
|
+
return { ...r17, contentWithoutFrontmatter: FRAGMENT_DUP_MARKER };
|
|
164563
|
+
}
|
|
164564
|
+
if (Array.isArray(r17.content)) {
|
|
164565
|
+
const deduped = dedupeFragmentContentFields(r17.content);
|
|
164566
|
+
if (deduped !== r17.content) return { ...r17, content: deduped };
|
|
164567
|
+
}
|
|
164568
|
+
return result;
|
|
164452
164569
|
}
|
|
164453
164570
|
function sanitizeImageToolResult(result) {
|
|
164454
164571
|
if (!result || typeof result !== "object") {
|
|
@@ -164491,7 +164608,7 @@ function sanitizeImageToolResult(result) {
|
|
|
164491
164608
|
return sanitized;
|
|
164492
164609
|
}
|
|
164493
164610
|
async function summarizeToolResultForHistory(toolName, result, _conversationContext) {
|
|
164494
|
-
const sanitizedResult = sanitizeImageToolResult(result);
|
|
164611
|
+
const sanitizedResult = dedupeFragmentContentFields(sanitizeImageToolResult(result));
|
|
164495
164612
|
const serialized = JSON.stringify(sanitizedResult);
|
|
164496
164613
|
const originalSize = countTokens(JSON.stringify(result));
|
|
164497
164614
|
const references = extractFragmentReferences(result);
|
|
@@ -164506,12 +164623,13 @@ async function summarizeToolResultForHistory(toolName, result, _conversationCont
|
|
|
164506
164623
|
usedQwen: false
|
|
164507
164624
|
};
|
|
164508
164625
|
}
|
|
164509
|
-
var MAX_TOOL_RESULT_CHARS;
|
|
164626
|
+
var MAX_TOOL_RESULT_CHARS, FRAGMENT_DUP_MARKER;
|
|
164510
164627
|
var init_tool_result_summarizer = __esm({
|
|
164511
164628
|
"src/core/utils/tool-result-summarizer.ts"() {
|
|
164512
164629
|
"use strict";
|
|
164513
164630
|
init_token_counter();
|
|
164514
164631
|
MAX_TOOL_RESULT_CHARS = 5e4;
|
|
164632
|
+
FRAGMENT_DUP_MARKER = "[omitted \u2014 duplicate of `content` minus the YAML frontmatter; read `content`]";
|
|
164515
164633
|
}
|
|
164516
164634
|
});
|
|
164517
164635
|
|
|
@@ -248154,6 +248272,65 @@ function pruneOversizedToolResultBodies(messages4, maxCharsPerResult) {
|
|
|
248154
248272
|
});
|
|
248155
248273
|
return { messages: pruned, prunedCount, bytesReclaimed };
|
|
248156
248274
|
}
|
|
248275
|
+
async function buildCollectionContextParts(opts) {
|
|
248276
|
+
const { usableApiUrl, accessToken, contextId, workspaceId } = opts;
|
|
248277
|
+
const parts = [];
|
|
248278
|
+
const fallback = () => {
|
|
248279
|
+
parts.length = 0;
|
|
248280
|
+
parts.push(`<collection id="${contextId}" name="${opts.metadataName || "Unnamed"}">`);
|
|
248281
|
+
if (opts.metadataSummary) parts.push(`<description>${opts.metadataSummary}</description>`);
|
|
248282
|
+
parts.push("</collection>");
|
|
248283
|
+
return parts;
|
|
248284
|
+
};
|
|
248285
|
+
if (!workspaceId) return fallback();
|
|
248286
|
+
const headers = {
|
|
248287
|
+
Authorization: `Bearer ${accessToken}`,
|
|
248288
|
+
"Content-Type": "application/json"
|
|
248289
|
+
};
|
|
248290
|
+
const collectionResponse = await fetch(
|
|
248291
|
+
`${usableApiUrl}/workspaces/${workspaceId}/collections/${contextId}`,
|
|
248292
|
+
{ headers }
|
|
248293
|
+
);
|
|
248294
|
+
if (!collectionResponse.ok) return fallback();
|
|
248295
|
+
const collectionData = await collectionResponse.json();
|
|
248296
|
+
const collection = collectionData.collection ?? collectionData;
|
|
248297
|
+
let fragments = [];
|
|
248298
|
+
let totalCount = 0;
|
|
248299
|
+
try {
|
|
248300
|
+
const fragmentsResponse = await fetch(
|
|
248301
|
+
`${usableApiUrl}/workspaces/${workspaceId}/collections/${contextId}/fragments?limit=50`,
|
|
248302
|
+
{ headers }
|
|
248303
|
+
);
|
|
248304
|
+
if (fragmentsResponse.ok) {
|
|
248305
|
+
const fragmentsData = await fragmentsResponse.json();
|
|
248306
|
+
fragments = fragmentsData.fragments || fragmentsData.data || [];
|
|
248307
|
+
totalCount = fragmentsData.total ?? fragments.length;
|
|
248308
|
+
}
|
|
248309
|
+
} catch {
|
|
248310
|
+
}
|
|
248311
|
+
parts.push(
|
|
248312
|
+
`<collection id="${contextId}" name="${collection.name || opts.metadataName || "Unnamed"}" workspace-id="${collection.workspaceId || workspaceId}">`
|
|
248313
|
+
);
|
|
248314
|
+
if (collection.description) parts.push(`<description>${collection.description}</description>`);
|
|
248315
|
+
parts.push(`<fragments count="${totalCount}">`);
|
|
248316
|
+
for (const frag of fragments.slice(0, 50)) {
|
|
248317
|
+
const fragId = frag.id || frag.fragmentId;
|
|
248318
|
+
const fragTitle = frag.title || "Untitled";
|
|
248319
|
+
const fragSummary = frag.summary || "";
|
|
248320
|
+
parts.push(`<fragment-ref id="${fragId}" title="${fragTitle}" summary="${fragSummary}" />`);
|
|
248321
|
+
}
|
|
248322
|
+
parts.push("</fragments>");
|
|
248323
|
+
if (totalCount > 50) {
|
|
248324
|
+
parts.push(
|
|
248325
|
+
`<note>Collection has ${totalCount} fragments total. Only showing first 50. Use list-memory-fragments with collectionId to see more.</note>`
|
|
248326
|
+
);
|
|
248327
|
+
}
|
|
248328
|
+
parts.push(
|
|
248329
|
+
"<instructions>Use get-memory-fragment-content to read full content of specific fragments. Don't add all fragments to context.</instructions>"
|
|
248330
|
+
);
|
|
248331
|
+
parts.push("</collection>");
|
|
248332
|
+
return parts;
|
|
248333
|
+
}
|
|
248157
248334
|
function enforceContextLimits(messages4, systemMessage, contextTokens, toCountableMessage, modelId, toolDefinitionTokens = 0) {
|
|
248158
248335
|
const model = getModelById(modelId);
|
|
248159
248336
|
const contextLength = model?.capabilities.contextLength || 128e3;
|
|
@@ -249613,54 +249790,16 @@ Folder behavior:
|
|
|
249613
249790
|
}
|
|
249614
249791
|
contextParts.push("</tool-result>");
|
|
249615
249792
|
} else if (item.contextType === "collection") {
|
|
249616
|
-
|
|
249617
|
-
|
|
249618
|
-
|
|
249619
|
-
|
|
249620
|
-
|
|
249621
|
-
|
|
249622
|
-
|
|
249623
|
-
|
|
249624
|
-
}
|
|
249793
|
+
contextParts.push(
|
|
249794
|
+
...await buildCollectionContextParts({
|
|
249795
|
+
usableApiUrl: config4.mcp.url.replace("/mcp", ""),
|
|
249796
|
+
accessToken: context.session.user?.accessToken,
|
|
249797
|
+
contextId: item.contextId,
|
|
249798
|
+
workspaceId: item.metadata?.workspaceId,
|
|
249799
|
+
metadataName: item.metadata?.name,
|
|
249800
|
+
metadataSummary: item.metadata?.summary
|
|
249801
|
+
})
|
|
249625
249802
|
);
|
|
249626
|
-
if (collectionResponse.ok) {
|
|
249627
|
-
const collection = await collectionResponse.json();
|
|
249628
|
-
const fragments = collection.fragments || collection.items || [];
|
|
249629
|
-
const totalCount = collection.fragmentCount || collection.totalFragments || fragments.length;
|
|
249630
|
-
contextParts.push(
|
|
249631
|
-
`<collection id="${item.contextId}" name="${collection.name || item.metadata?.name || "Unnamed"}" workspace-id="${collection.workspaceId || item.metadata?.workspaceId || ""}">`
|
|
249632
|
-
);
|
|
249633
|
-
if (collection.description) {
|
|
249634
|
-
contextParts.push(`<description>${collection.description}</description>`);
|
|
249635
|
-
}
|
|
249636
|
-
contextParts.push(`<fragments count="${totalCount}">`);
|
|
249637
|
-
for (const frag of fragments.slice(0, 50)) {
|
|
249638
|
-
const fragId = frag.id || frag.fragmentId;
|
|
249639
|
-
const fragTitle = frag.title || "Untitled";
|
|
249640
|
-
const fragSummary = frag.summary || "";
|
|
249641
|
-
contextParts.push(
|
|
249642
|
-
`<fragment-ref id="${fragId}" title="${fragTitle}" summary="${fragSummary}" />`
|
|
249643
|
-
);
|
|
249644
|
-
}
|
|
249645
|
-
contextParts.push("</fragments>");
|
|
249646
|
-
if (totalCount > 50) {
|
|
249647
|
-
contextParts.push(
|
|
249648
|
-
`<note>Collection has ${totalCount} fragments total. Only showing first 50. Use list-memory-fragments with collectionId to see more.</note>`
|
|
249649
|
-
);
|
|
249650
|
-
}
|
|
249651
|
-
contextParts.push(
|
|
249652
|
-
"<instructions>Use get-memory-fragment-content to read full content of specific fragments. Don't add all fragments to context.</instructions>"
|
|
249653
|
-
);
|
|
249654
|
-
contextParts.push("</collection>");
|
|
249655
|
-
} else {
|
|
249656
|
-
contextParts.push(
|
|
249657
|
-
`<collection id="${item.contextId}" name="${item.metadata?.name || "Unnamed"}">`
|
|
249658
|
-
);
|
|
249659
|
-
if (item.metadata?.summary) {
|
|
249660
|
-
contextParts.push(`<description>${item.metadata.summary}</description>`);
|
|
249661
|
-
}
|
|
249662
|
-
contextParts.push("</collection>");
|
|
249663
|
-
}
|
|
249664
249803
|
}
|
|
249665
249804
|
} catch (error41) {
|
|
249666
249805
|
orchestrationLogger.warn("Failed to fetch context item content", {
|
|
@@ -249942,54 +250081,16 @@ Folder behavior:
|
|
|
249942
250081
|
}
|
|
249943
250082
|
contextParts.push("</tool-result>");
|
|
249944
250083
|
} else if (item.contextType === "collection") {
|
|
249945
|
-
|
|
249946
|
-
|
|
249947
|
-
|
|
249948
|
-
|
|
249949
|
-
|
|
249950
|
-
|
|
249951
|
-
|
|
249952
|
-
|
|
249953
|
-
}
|
|
250084
|
+
contextParts.push(
|
|
250085
|
+
...await buildCollectionContextParts({
|
|
250086
|
+
usableApiUrl: config4.mcp.url.replace("/mcp", ""),
|
|
250087
|
+
accessToken: context.session.user?.accessToken,
|
|
250088
|
+
contextId: item.contextId,
|
|
250089
|
+
workspaceId: item.metadata?.workspaceId,
|
|
250090
|
+
metadataName: item.metadata?.name,
|
|
250091
|
+
metadataSummary: item.metadata?.summary
|
|
250092
|
+
})
|
|
249954
250093
|
);
|
|
249955
|
-
if (collectionResponse.ok) {
|
|
249956
|
-
const collection = await collectionResponse.json();
|
|
249957
|
-
const fragments = collection.fragments || collection.items || [];
|
|
249958
|
-
const totalCount = collection.fragmentCount || collection.totalFragments || fragments.length;
|
|
249959
|
-
contextParts.push(
|
|
249960
|
-
`<collection id="${item.contextId}" name="${collection.name || item.metadata?.name || "Unnamed"}" workspace-id="${collection.workspaceId || item.metadata?.workspaceId || ""}">`
|
|
249961
|
-
);
|
|
249962
|
-
if (collection.description) {
|
|
249963
|
-
contextParts.push(`<description>${collection.description}</description>`);
|
|
249964
|
-
}
|
|
249965
|
-
contextParts.push(`<fragments count="${totalCount}">`);
|
|
249966
|
-
for (const frag of fragments.slice(0, 50)) {
|
|
249967
|
-
const fragId = frag.id || frag.fragmentId;
|
|
249968
|
-
const fragTitle = frag.title || "Untitled";
|
|
249969
|
-
const fragSummary = frag.summary || "";
|
|
249970
|
-
contextParts.push(
|
|
249971
|
-
`<fragment-ref id="${fragId}" title="${fragTitle}" summary="${fragSummary}" />`
|
|
249972
|
-
);
|
|
249973
|
-
}
|
|
249974
|
-
contextParts.push("</fragments>");
|
|
249975
|
-
if (totalCount > 50) {
|
|
249976
|
-
contextParts.push(
|
|
249977
|
-
`<note>Collection has ${totalCount} fragments total. Only showing first 50. Use list-memory-fragments with collectionId to see more.</note>`
|
|
249978
|
-
);
|
|
249979
|
-
}
|
|
249980
|
-
contextParts.push(
|
|
249981
|
-
"<instructions>Use get-memory-fragment-content to read full content of specific fragments. Don't add all fragments to context.</instructions>"
|
|
249982
|
-
);
|
|
249983
|
-
contextParts.push("</collection>");
|
|
249984
|
-
} else {
|
|
249985
|
-
contextParts.push(
|
|
249986
|
-
`<collection id="${item.contextId}" name="${item.metadata?.name || "Unnamed"}">`
|
|
249987
|
-
);
|
|
249988
|
-
if (item.metadata?.summary) {
|
|
249989
|
-
contextParts.push(`<description>${item.metadata.summary}</description>`);
|
|
249990
|
-
}
|
|
249991
|
-
contextParts.push("</collection>");
|
|
249992
|
-
}
|
|
249993
250094
|
}
|
|
249994
250095
|
} catch (error41) {
|
|
249995
250096
|
orchestrationLogger.warn("Failed to fetch context item content", {
|
|
@@ -251287,44 +251388,101 @@ ${combinedSystemMessage}` : combinedSystemMessage;
|
|
|
251287
251388
|
const ctxLen = getModelById(selectedModelId)?.capabilities.contextLength || 128e3;
|
|
251288
251389
|
const envRatio = Number(process.env.USABLE_HARNESS_COMPACT_RATIO);
|
|
251289
251390
|
const compactRatio = envRatio > 0 && envRatio <= 1 ? envRatio : HARNESS_COMPACT_RATIO;
|
|
251290
|
-
const tokensOf = (m33) =>
|
|
251291
|
-
const
|
|
251391
|
+
const tokensOf = (m33) => countMessageTokens(toCountableMessage(m33));
|
|
251392
|
+
const compactToolDefTokens = estimateToolDefinitionTokens(rawTools);
|
|
251393
|
+
const systemTokens = systemMessage ? countTokens(systemMessage) : 0;
|
|
251394
|
+
const baseTokens = systemTokens + contextTokens + compactToolDefTokens;
|
|
251395
|
+
const messageTokens = conversationMessages.reduce((s17, m33) => s17 + tokensOf(m33), 0);
|
|
251396
|
+
const estTokens = baseTokens + messageTokens;
|
|
251397
|
+
const threshold = Math.floor(ctxLen * compactRatio);
|
|
251292
251398
|
const alreadyCompacted = isCompactionCheckpoint(conversationMessages[0]);
|
|
251293
|
-
|
|
251399
|
+
orchestrationLogger.debug("\u{1F9F9} compaction check", {
|
|
251400
|
+
estTokens,
|
|
251401
|
+
baseTokens,
|
|
251402
|
+
systemTokens,
|
|
251403
|
+
contextTokens,
|
|
251404
|
+
toolDefTokens: compactToolDefTokens,
|
|
251405
|
+
messageTokens,
|
|
251406
|
+
threshold,
|
|
251407
|
+
ctxLen,
|
|
251408
|
+
compactRatio,
|
|
251409
|
+
messages: conversationMessages.length,
|
|
251410
|
+
alreadyCompacted,
|
|
251411
|
+
path: config3.localFilesystem ? "cli" : "web"
|
|
251412
|
+
});
|
|
251413
|
+
if (!alreadyCompacted && estTokens > threshold) {
|
|
251294
251414
|
const beforeLen = conversationMessages.length;
|
|
251295
251415
|
const envKeep = Number(process.env.USABLE_HARNESS_KEEP_TOKENS);
|
|
251296
|
-
const
|
|
251416
|
+
const desiredKeep = envKeep > 0 ? envKeep : Math.floor(ctxLen * HARNESS_KEEP_RECENT_RATIO);
|
|
251417
|
+
const roomForMessages = Math.floor(ctxLen * 0.85) - baseTokens;
|
|
251418
|
+
const keepRecentTokens = Math.max(2e3, Math.min(desiredKeep, roomForMessages));
|
|
251297
251419
|
const cut = findCutIndex(conversationMessages, keepRecentTokens, tokensOf);
|
|
251298
251420
|
if (cut >= 3) {
|
|
251421
|
+
const compactionStartEvent = emitter.emit("compaction-needed", {
|
|
251422
|
+
contextUsagePercent: Math.round(estTokens / ctxLen * 100),
|
|
251423
|
+
inputTokens: estTokens,
|
|
251424
|
+
contextLength: ctxLen,
|
|
251425
|
+
model: selectedModelId,
|
|
251426
|
+
serverCompacted: true
|
|
251427
|
+
});
|
|
251428
|
+
multiplexer.send(compactionStartEvent);
|
|
251429
|
+
const compactingPlan = emitter.emit("plan", {
|
|
251430
|
+
plan: "\u{1F9F9} Summarizing older conversation to free context\u2026",
|
|
251431
|
+
steps: config3.maxSteps
|
|
251432
|
+
});
|
|
251433
|
+
multiplexer.send(compactingPlan);
|
|
251299
251434
|
const dropped = conversationMessages.slice(0, cut);
|
|
251300
251435
|
const tail2 = conversationMessages.slice(cut);
|
|
251301
251436
|
const fileOps = extractFileOps(dropped);
|
|
251302
|
-
const
|
|
251303
|
-
|
|
251304
|
-
|
|
251305
|
-
|
|
251306
|
-
|
|
251307
|
-
|
|
251308
|
-
|
|
251309
|
-
|
|
251310
|
-
|
|
251311
|
-
|
|
251312
|
-
|
|
251437
|
+
const compactionConversationId = context.metadata?.conversationId;
|
|
251438
|
+
const { summary, cacheHit } = await generateCompactionSummaryCached(
|
|
251439
|
+
compactionConversationId,
|
|
251440
|
+
dropped,
|
|
251441
|
+
{
|
|
251442
|
+
provider: providerRouting?.provider,
|
|
251443
|
+
model: selectedModelId,
|
|
251444
|
+
openRouterApiKey: apiKey,
|
|
251445
|
+
anthropicApiKey,
|
|
251446
|
+
baseUrl: request.providerBaseUrl,
|
|
251447
|
+
authToken: request.providerAuthToken,
|
|
251448
|
+
codexAuth: request.codexAuth,
|
|
251449
|
+
abortSignal: context.abortSignal
|
|
251450
|
+
}
|
|
251451
|
+
);
|
|
251452
|
+
let checkpoint = summary ? buildSummaryCheckpoint(summary, dropped.length, fileOps) : buildCompactionMarker(goalText(conversationMessages), dropped.length, fileOps);
|
|
251453
|
+
const spillPaths = extractSpillPointers(dropped);
|
|
251454
|
+
if (spillPaths.length > 0) {
|
|
251455
|
+
checkpoint = {
|
|
251456
|
+
role: "user",
|
|
251457
|
+
content: `${checkpoint.content}
|
|
251458
|
+
|
|
251459
|
+
Large tool results from the compacted work are PRESERVED at:
|
|
251460
|
+
` + spillPaths.map((p28) => `- ${p28}`).join("\n") + `
|
|
251461
|
+
Re-read them (working_memory on web, bash on the CLI \u2014 grep/sed projection, not a full cat) instead of re-calling the tools that produced them.`
|
|
251462
|
+
};
|
|
251463
|
+
}
|
|
251313
251464
|
conversationMessages = ensureToolCallIntegrity([checkpoint, ...tail2]);
|
|
251314
251465
|
orchestrationLogger.warn("\u{1F9F9} Proactive conversation compaction", {
|
|
251315
251466
|
beforeLen,
|
|
251316
251467
|
afterLen: conversationMessages.length,
|
|
251317
251468
|
droppedCount: dropped.length,
|
|
251318
251469
|
estTokens,
|
|
251470
|
+
baseTokens,
|
|
251471
|
+
messageTokens,
|
|
251472
|
+
keepRecentTokens,
|
|
251473
|
+
threshold,
|
|
251319
251474
|
contextLength: ctxLen,
|
|
251320
251475
|
usedLlmSummary: !!summary,
|
|
251476
|
+
cacheHit,
|
|
251321
251477
|
path: config3.localFilesystem ? "cli" : "web"
|
|
251322
251478
|
});
|
|
251323
|
-
const
|
|
251324
|
-
|
|
251325
|
-
|
|
251479
|
+
const compactionDoneEvent = emitter.emit("compaction", {
|
|
251480
|
+
summary: summary ?? (typeof checkpoint.content === "string" ? checkpoint.content : ""),
|
|
251481
|
+
droppedCount: dropped.length,
|
|
251482
|
+
usedLlmSummary: !!summary,
|
|
251483
|
+
cacheHit
|
|
251326
251484
|
});
|
|
251327
|
-
multiplexer.send(
|
|
251485
|
+
multiplexer.send(compactionDoneEvent);
|
|
251328
251486
|
if (request.onContextCompacted) {
|
|
251329
251487
|
try {
|
|
251330
251488
|
await request.onContextCompacted(conversationMessages);
|
|
@@ -251911,7 +252069,7 @@ ${combinedSystemMessage}` : combinedSystemMessage;
|
|
|
251911
252069
|
await bash.exec(`echo "${b64}" | base64 -d > ${filename}`);
|
|
251912
252070
|
summary.content = `${summary.content}
|
|
251913
252071
|
|
|
251914
|
-
[\u26A0\uFE0F FULL RESULT TOO LARGE \u2014 stored at
|
|
252072
|
+
[\u26A0\uFE0F FULL RESULT TOO LARGE \u2014 full data stored at ${filename}. Read it with the working_memory tool, but do NOT \`cat\` the whole file (it re-truncates). Project just the part you need, e.g. \`grep -n PATTERN ${filename}\` or \`sed -n '100,200p' ${filename}\` \u2014 do NOT re-call the original tool to recover the omitted part.]`;
|
|
251915
252073
|
} catch {
|
|
251916
252074
|
}
|
|
251917
252075
|
}
|
|
@@ -269922,6 +270080,15 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
|
|
|
269922
270080
|
push({ kind: "system", text: plan, tone: "info" });
|
|
269923
270081
|
ui2.requestRender();
|
|
269924
270082
|
}
|
|
270083
|
+
} else if (e14.type === "compaction") {
|
|
270084
|
+
const data2 = e14.data;
|
|
270085
|
+
finalize();
|
|
270086
|
+
const head = `\u{1F9F9} Compacted ${data2?.droppedCount ?? 0} older messages into a checkpoint`;
|
|
270087
|
+
const body = data2?.summary ? `${head}:
|
|
270088
|
+
|
|
270089
|
+
${data2.summary}` : `${head}.`;
|
|
270090
|
+
push({ kind: "system", text: body, tone: "info" });
|
|
270091
|
+
ui2.requestRender();
|
|
269925
270092
|
}
|
|
269926
270093
|
};
|
|
269927
270094
|
try {
|
|
@@ -270781,7 +270948,7 @@ init_tui_select();
|
|
|
270781
270948
|
init_model_registry();
|
|
270782
270949
|
|
|
270783
270950
|
// package.json
|
|
270784
|
-
var version2 = "1.152.
|
|
270951
|
+
var version2 = "1.152.1";
|
|
270785
270952
|
|
|
270786
270953
|
// src/adapters/cli/model-catalog.ts
|
|
270787
270954
|
init_codex_auth();
|