@usabledev/usable-chat 1.152.0 → 1.153.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +330 -128
- 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
|
|
|
@@ -175858,6 +175976,33 @@ data: ${JSON.stringify(envelope)}
|
|
|
175858
175976
|
});
|
|
175859
175977
|
|
|
175860
175978
|
// src/core/tools/spawn-subagent.ts
|
|
175979
|
+
async function maybeStartBackgroundRelay(redis, context, conversationId, taskId, success2) {
|
|
175980
|
+
if (!redis || !context.startBackgroundSubagentRelay) return false;
|
|
175981
|
+
try {
|
|
175982
|
+
const record2 = await spawnedTaskStore.get(redis, taskId);
|
|
175983
|
+
if (record2?.notifiedTurnAt) {
|
|
175984
|
+
return true;
|
|
175985
|
+
}
|
|
175986
|
+
const lockKey = `subagents:background-relay-lock:${conversationId}`;
|
|
175987
|
+
const acquired = await redis.set(lockKey, taskId, "NX", "EX", 180).catch(() => null);
|
|
175988
|
+
if (!acquired) {
|
|
175989
|
+
return true;
|
|
175990
|
+
}
|
|
175991
|
+
const started = await context.startBackgroundSubagentRelay({
|
|
175992
|
+
conversationId,
|
|
175993
|
+
taskId,
|
|
175994
|
+
reason: success2 ? "completed" : "failed"
|
|
175995
|
+
});
|
|
175996
|
+
return started;
|
|
175997
|
+
} catch (err) {
|
|
175998
|
+
logger.debug("SpawnSubagent", "Failed to start background relay job", {
|
|
175999
|
+
conversationId,
|
|
176000
|
+
taskId,
|
|
176001
|
+
error: err instanceof Error ? err.message : String(err)
|
|
176002
|
+
});
|
|
176003
|
+
return false;
|
|
176004
|
+
}
|
|
176005
|
+
}
|
|
175861
176006
|
async function maybeEmitPingPrompt(redis, conversationId, taskId) {
|
|
175862
176007
|
if (!redis) return;
|
|
175863
176008
|
try {
|
|
@@ -176158,7 +176303,10 @@ HOW TO USE THIS:
|
|
|
176158
176303
|
tokenUsage: response.tokenUsage
|
|
176159
176304
|
}
|
|
176160
176305
|
});
|
|
176161
|
-
await
|
|
176306
|
+
const relayed = await maybeStartBackgroundRelay(redis, context, conversationId, taskId, success2);
|
|
176307
|
+
if (!relayed) {
|
|
176308
|
+
await maybeEmitPingPrompt(redis, conversationId, taskId);
|
|
176309
|
+
}
|
|
176162
176310
|
} catch (err) {
|
|
176163
176311
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
176164
176312
|
const status = abort.signal.aborted ? "cancelled" : "failed";
|
|
@@ -176173,7 +176321,10 @@ HOW TO USE THIS:
|
|
|
176173
176321
|
taskId,
|
|
176174
176322
|
data: { error: errorMessage }
|
|
176175
176323
|
});
|
|
176176
|
-
await
|
|
176324
|
+
const relayed = await maybeStartBackgroundRelay(redis, context, conversationId, taskId, false);
|
|
176325
|
+
if (!relayed) {
|
|
176326
|
+
await maybeEmitPingPrompt(redis, conversationId, taskId);
|
|
176327
|
+
}
|
|
176177
176328
|
} finally {
|
|
176178
176329
|
spawnedTaskStore.unregisterLocalAbort(taskId);
|
|
176179
176330
|
}
|
|
@@ -248154,6 +248305,65 @@ function pruneOversizedToolResultBodies(messages4, maxCharsPerResult) {
|
|
|
248154
248305
|
});
|
|
248155
248306
|
return { messages: pruned, prunedCount, bytesReclaimed };
|
|
248156
248307
|
}
|
|
248308
|
+
async function buildCollectionContextParts(opts) {
|
|
248309
|
+
const { usableApiUrl, accessToken, contextId, workspaceId } = opts;
|
|
248310
|
+
const parts = [];
|
|
248311
|
+
const fallback = () => {
|
|
248312
|
+
parts.length = 0;
|
|
248313
|
+
parts.push(`<collection id="${contextId}" name="${opts.metadataName || "Unnamed"}">`);
|
|
248314
|
+
if (opts.metadataSummary) parts.push(`<description>${opts.metadataSummary}</description>`);
|
|
248315
|
+
parts.push("</collection>");
|
|
248316
|
+
return parts;
|
|
248317
|
+
};
|
|
248318
|
+
if (!workspaceId) return fallback();
|
|
248319
|
+
const headers = {
|
|
248320
|
+
Authorization: `Bearer ${accessToken}`,
|
|
248321
|
+
"Content-Type": "application/json"
|
|
248322
|
+
};
|
|
248323
|
+
const collectionResponse = await fetch(
|
|
248324
|
+
`${usableApiUrl}/workspaces/${workspaceId}/collections/${contextId}`,
|
|
248325
|
+
{ headers }
|
|
248326
|
+
);
|
|
248327
|
+
if (!collectionResponse.ok) return fallback();
|
|
248328
|
+
const collectionData = await collectionResponse.json();
|
|
248329
|
+
const collection = collectionData.collection ?? collectionData;
|
|
248330
|
+
let fragments = [];
|
|
248331
|
+
let totalCount = 0;
|
|
248332
|
+
try {
|
|
248333
|
+
const fragmentsResponse = await fetch(
|
|
248334
|
+
`${usableApiUrl}/workspaces/${workspaceId}/collections/${contextId}/fragments?limit=50`,
|
|
248335
|
+
{ headers }
|
|
248336
|
+
);
|
|
248337
|
+
if (fragmentsResponse.ok) {
|
|
248338
|
+
const fragmentsData = await fragmentsResponse.json();
|
|
248339
|
+
fragments = fragmentsData.fragments || fragmentsData.data || [];
|
|
248340
|
+
totalCount = fragmentsData.total ?? fragments.length;
|
|
248341
|
+
}
|
|
248342
|
+
} catch {
|
|
248343
|
+
}
|
|
248344
|
+
parts.push(
|
|
248345
|
+
`<collection id="${contextId}" name="${collection.name || opts.metadataName || "Unnamed"}" workspace-id="${collection.workspaceId || workspaceId}">`
|
|
248346
|
+
);
|
|
248347
|
+
if (collection.description) parts.push(`<description>${collection.description}</description>`);
|
|
248348
|
+
parts.push(`<fragments count="${totalCount}">`);
|
|
248349
|
+
for (const frag of fragments.slice(0, 50)) {
|
|
248350
|
+
const fragId = frag.id || frag.fragmentId;
|
|
248351
|
+
const fragTitle = frag.title || "Untitled";
|
|
248352
|
+
const fragSummary = frag.summary || "";
|
|
248353
|
+
parts.push(`<fragment-ref id="${fragId}" title="${fragTitle}" summary="${fragSummary}" />`);
|
|
248354
|
+
}
|
|
248355
|
+
parts.push("</fragments>");
|
|
248356
|
+
if (totalCount > 50) {
|
|
248357
|
+
parts.push(
|
|
248358
|
+
`<note>Collection has ${totalCount} fragments total. Only showing first 50. Use list-memory-fragments with collectionId to see more.</note>`
|
|
248359
|
+
);
|
|
248360
|
+
}
|
|
248361
|
+
parts.push(
|
|
248362
|
+
"<instructions>Use get-memory-fragment-content to read full content of specific fragments. Don't add all fragments to context.</instructions>"
|
|
248363
|
+
);
|
|
248364
|
+
parts.push("</collection>");
|
|
248365
|
+
return parts;
|
|
248366
|
+
}
|
|
248157
248367
|
function enforceContextLimits(messages4, systemMessage, contextTokens, toCountableMessage, modelId, toolDefinitionTokens = 0) {
|
|
248158
248368
|
const model = getModelById(modelId);
|
|
248159
248369
|
const contextLength = model?.capabilities.contextLength || 128e3;
|
|
@@ -249145,7 +249355,8 @@ async function orchestrate(request) {
|
|
|
249145
249355
|
chatMode,
|
|
249146
249356
|
imageGenModel: context.metadata?.imageGenModel,
|
|
249147
249357
|
imageGenThinking: context.metadata?.imageGenThinking,
|
|
249148
|
-
registeredParentToolSchemas: context.registeredParentToolSchemas
|
|
249358
|
+
registeredParentToolSchemas: context.registeredParentToolSchemas,
|
|
249359
|
+
startBackgroundSubagentRelay: context.startBackgroundSubagentRelay
|
|
249149
249360
|
});
|
|
249150
249361
|
if (!config3.localFilesystem) {
|
|
249151
249362
|
aiTools["spawn_subagent"] = {
|
|
@@ -249613,54 +249824,16 @@ Folder behavior:
|
|
|
249613
249824
|
}
|
|
249614
249825
|
contextParts.push("</tool-result>");
|
|
249615
249826
|
} else if (item.contextType === "collection") {
|
|
249616
|
-
|
|
249617
|
-
|
|
249618
|
-
|
|
249619
|
-
|
|
249620
|
-
|
|
249621
|
-
|
|
249622
|
-
|
|
249623
|
-
|
|
249624
|
-
}
|
|
249827
|
+
contextParts.push(
|
|
249828
|
+
...await buildCollectionContextParts({
|
|
249829
|
+
usableApiUrl: config4.mcp.url.replace("/mcp", ""),
|
|
249830
|
+
accessToken: context.session.user?.accessToken,
|
|
249831
|
+
contextId: item.contextId,
|
|
249832
|
+
workspaceId: item.metadata?.workspaceId,
|
|
249833
|
+
metadataName: item.metadata?.name,
|
|
249834
|
+
metadataSummary: item.metadata?.summary
|
|
249835
|
+
})
|
|
249625
249836
|
);
|
|
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
249837
|
}
|
|
249665
249838
|
} catch (error41) {
|
|
249666
249839
|
orchestrationLogger.warn("Failed to fetch context item content", {
|
|
@@ -249942,54 +250115,16 @@ Folder behavior:
|
|
|
249942
250115
|
}
|
|
249943
250116
|
contextParts.push("</tool-result>");
|
|
249944
250117
|
} else if (item.contextType === "collection") {
|
|
249945
|
-
|
|
249946
|
-
|
|
249947
|
-
|
|
249948
|
-
|
|
249949
|
-
|
|
249950
|
-
|
|
249951
|
-
|
|
249952
|
-
|
|
249953
|
-
}
|
|
250118
|
+
contextParts.push(
|
|
250119
|
+
...await buildCollectionContextParts({
|
|
250120
|
+
usableApiUrl: config4.mcp.url.replace("/mcp", ""),
|
|
250121
|
+
accessToken: context.session.user?.accessToken,
|
|
250122
|
+
contextId: item.contextId,
|
|
250123
|
+
workspaceId: item.metadata?.workspaceId,
|
|
250124
|
+
metadataName: item.metadata?.name,
|
|
250125
|
+
metadataSummary: item.metadata?.summary
|
|
250126
|
+
})
|
|
249954
250127
|
);
|
|
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
250128
|
}
|
|
249994
250129
|
} catch (error41) {
|
|
249995
250130
|
orchestrationLogger.warn("Failed to fetch context item content", {
|
|
@@ -251287,44 +251422,101 @@ ${combinedSystemMessage}` : combinedSystemMessage;
|
|
|
251287
251422
|
const ctxLen = getModelById(selectedModelId)?.capabilities.contextLength || 128e3;
|
|
251288
251423
|
const envRatio = Number(process.env.USABLE_HARNESS_COMPACT_RATIO);
|
|
251289
251424
|
const compactRatio = envRatio > 0 && envRatio <= 1 ? envRatio : HARNESS_COMPACT_RATIO;
|
|
251290
|
-
const tokensOf = (m33) =>
|
|
251291
|
-
const
|
|
251425
|
+
const tokensOf = (m33) => countMessageTokens(toCountableMessage(m33));
|
|
251426
|
+
const compactToolDefTokens = estimateToolDefinitionTokens(rawTools);
|
|
251427
|
+
const systemTokens = systemMessage ? countTokens(systemMessage) : 0;
|
|
251428
|
+
const baseTokens = systemTokens + contextTokens + compactToolDefTokens;
|
|
251429
|
+
const messageTokens = conversationMessages.reduce((s17, m33) => s17 + tokensOf(m33), 0);
|
|
251430
|
+
const estTokens = baseTokens + messageTokens;
|
|
251431
|
+
const threshold = Math.floor(ctxLen * compactRatio);
|
|
251292
251432
|
const alreadyCompacted = isCompactionCheckpoint(conversationMessages[0]);
|
|
251293
|
-
|
|
251433
|
+
orchestrationLogger.debug("\u{1F9F9} compaction check", {
|
|
251434
|
+
estTokens,
|
|
251435
|
+
baseTokens,
|
|
251436
|
+
systemTokens,
|
|
251437
|
+
contextTokens,
|
|
251438
|
+
toolDefTokens: compactToolDefTokens,
|
|
251439
|
+
messageTokens,
|
|
251440
|
+
threshold,
|
|
251441
|
+
ctxLen,
|
|
251442
|
+
compactRatio,
|
|
251443
|
+
messages: conversationMessages.length,
|
|
251444
|
+
alreadyCompacted,
|
|
251445
|
+
path: config3.localFilesystem ? "cli" : "web"
|
|
251446
|
+
});
|
|
251447
|
+
if (!alreadyCompacted && estTokens > threshold) {
|
|
251294
251448
|
const beforeLen = conversationMessages.length;
|
|
251295
251449
|
const envKeep = Number(process.env.USABLE_HARNESS_KEEP_TOKENS);
|
|
251296
|
-
const
|
|
251450
|
+
const desiredKeep = envKeep > 0 ? envKeep : Math.floor(ctxLen * HARNESS_KEEP_RECENT_RATIO);
|
|
251451
|
+
const roomForMessages = Math.floor(ctxLen * 0.85) - baseTokens;
|
|
251452
|
+
const keepRecentTokens = Math.max(2e3, Math.min(desiredKeep, roomForMessages));
|
|
251297
251453
|
const cut = findCutIndex(conversationMessages, keepRecentTokens, tokensOf);
|
|
251298
251454
|
if (cut >= 3) {
|
|
251455
|
+
const compactionStartEvent = emitter.emit("compaction-needed", {
|
|
251456
|
+
contextUsagePercent: Math.round(estTokens / ctxLen * 100),
|
|
251457
|
+
inputTokens: estTokens,
|
|
251458
|
+
contextLength: ctxLen,
|
|
251459
|
+
model: selectedModelId,
|
|
251460
|
+
serverCompacted: true
|
|
251461
|
+
});
|
|
251462
|
+
multiplexer.send(compactionStartEvent);
|
|
251463
|
+
const compactingPlan = emitter.emit("plan", {
|
|
251464
|
+
plan: "\u{1F9F9} Summarizing older conversation to free context\u2026",
|
|
251465
|
+
steps: config3.maxSteps
|
|
251466
|
+
});
|
|
251467
|
+
multiplexer.send(compactingPlan);
|
|
251299
251468
|
const dropped = conversationMessages.slice(0, cut);
|
|
251300
251469
|
const tail2 = conversationMessages.slice(cut);
|
|
251301
251470
|
const fileOps = extractFileOps(dropped);
|
|
251302
|
-
const
|
|
251303
|
-
|
|
251304
|
-
|
|
251305
|
-
|
|
251306
|
-
|
|
251307
|
-
|
|
251308
|
-
|
|
251309
|
-
|
|
251310
|
-
|
|
251311
|
-
|
|
251312
|
-
|
|
251471
|
+
const compactionConversationId = context.metadata?.conversationId;
|
|
251472
|
+
const { summary, cacheHit } = await generateCompactionSummaryCached(
|
|
251473
|
+
compactionConversationId,
|
|
251474
|
+
dropped,
|
|
251475
|
+
{
|
|
251476
|
+
provider: providerRouting?.provider,
|
|
251477
|
+
model: selectedModelId,
|
|
251478
|
+
openRouterApiKey: apiKey,
|
|
251479
|
+
anthropicApiKey,
|
|
251480
|
+
baseUrl: request.providerBaseUrl,
|
|
251481
|
+
authToken: request.providerAuthToken,
|
|
251482
|
+
codexAuth: request.codexAuth,
|
|
251483
|
+
abortSignal: context.abortSignal
|
|
251484
|
+
}
|
|
251485
|
+
);
|
|
251486
|
+
let checkpoint = summary ? buildSummaryCheckpoint(summary, dropped.length, fileOps) : buildCompactionMarker(goalText(conversationMessages), dropped.length, fileOps);
|
|
251487
|
+
const spillPaths = extractSpillPointers(dropped);
|
|
251488
|
+
if (spillPaths.length > 0) {
|
|
251489
|
+
checkpoint = {
|
|
251490
|
+
role: "user",
|
|
251491
|
+
content: `${checkpoint.content}
|
|
251492
|
+
|
|
251493
|
+
Large tool results from the compacted work are PRESERVED at:
|
|
251494
|
+
` + spillPaths.map((p28) => `- ${p28}`).join("\n") + `
|
|
251495
|
+
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.`
|
|
251496
|
+
};
|
|
251497
|
+
}
|
|
251313
251498
|
conversationMessages = ensureToolCallIntegrity([checkpoint, ...tail2]);
|
|
251314
251499
|
orchestrationLogger.warn("\u{1F9F9} Proactive conversation compaction", {
|
|
251315
251500
|
beforeLen,
|
|
251316
251501
|
afterLen: conversationMessages.length,
|
|
251317
251502
|
droppedCount: dropped.length,
|
|
251318
251503
|
estTokens,
|
|
251504
|
+
baseTokens,
|
|
251505
|
+
messageTokens,
|
|
251506
|
+
keepRecentTokens,
|
|
251507
|
+
threshold,
|
|
251319
251508
|
contextLength: ctxLen,
|
|
251320
251509
|
usedLlmSummary: !!summary,
|
|
251510
|
+
cacheHit,
|
|
251321
251511
|
path: config3.localFilesystem ? "cli" : "web"
|
|
251322
251512
|
});
|
|
251323
|
-
const
|
|
251324
|
-
|
|
251325
|
-
|
|
251513
|
+
const compactionDoneEvent = emitter.emit("compaction", {
|
|
251514
|
+
summary: summary ?? (typeof checkpoint.content === "string" ? checkpoint.content : ""),
|
|
251515
|
+
droppedCount: dropped.length,
|
|
251516
|
+
usedLlmSummary: !!summary,
|
|
251517
|
+
cacheHit
|
|
251326
251518
|
});
|
|
251327
|
-
multiplexer.send(
|
|
251519
|
+
multiplexer.send(compactionDoneEvent);
|
|
251328
251520
|
if (request.onContextCompacted) {
|
|
251329
251521
|
try {
|
|
251330
251522
|
await request.onContextCompacted(conversationMessages);
|
|
@@ -251911,7 +252103,7 @@ ${combinedSystemMessage}` : combinedSystemMessage;
|
|
|
251911
252103
|
await bash.exec(`echo "${b64}" | base64 -d > ${filename}`);
|
|
251912
252104
|
summary.content = `${summary.content}
|
|
251913
252105
|
|
|
251914
|
-
[\u26A0\uFE0F FULL RESULT TOO LARGE \u2014 stored at
|
|
252106
|
+
[\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
252107
|
} catch {
|
|
251916
252108
|
}
|
|
251917
252109
|
}
|
|
@@ -252242,6 +252434,7 @@ function createOrchestratorRequest(messages4, context, config3, persona, apiKey,
|
|
|
252242
252434
|
metadata: context.metadata,
|
|
252243
252435
|
allowedWorkspaceIds: context.allowedWorkspaceIds,
|
|
252244
252436
|
registeredParentToolSchemas: context.registeredParentToolSchemas,
|
|
252437
|
+
startBackgroundSubagentRelay: context.startBackgroundSubagentRelay,
|
|
252245
252438
|
extensions: context.extensions
|
|
252246
252439
|
// Plugin hook seam (CLI only; undefined on web)
|
|
252247
252440
|
},
|
|
@@ -269922,6 +270115,15 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
|
|
|
269922
270115
|
push({ kind: "system", text: plan, tone: "info" });
|
|
269923
270116
|
ui2.requestRender();
|
|
269924
270117
|
}
|
|
270118
|
+
} else if (e14.type === "compaction") {
|
|
270119
|
+
const data2 = e14.data;
|
|
270120
|
+
finalize();
|
|
270121
|
+
const head = `\u{1F9F9} Compacted ${data2?.droppedCount ?? 0} older messages into a checkpoint`;
|
|
270122
|
+
const body = data2?.summary ? `${head}:
|
|
270123
|
+
|
|
270124
|
+
${data2.summary}` : `${head}.`;
|
|
270125
|
+
push({ kind: "system", text: body, tone: "info" });
|
|
270126
|
+
ui2.requestRender();
|
|
269925
270127
|
}
|
|
269926
270128
|
};
|
|
269927
270129
|
try {
|
|
@@ -270781,7 +270983,7 @@ init_tui_select();
|
|
|
270781
270983
|
init_model_registry();
|
|
270782
270984
|
|
|
270783
270985
|
// package.json
|
|
270784
|
-
var version2 = "1.
|
|
270986
|
+
var version2 = "1.153.0";
|
|
270785
270987
|
|
|
270786
270988
|
// src/adapters/cli/model-catalog.ts
|
|
270787
270989
|
init_codex_auth();
|