chainlesschain 0.166.136 → 0.166.138
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/package.json +1 -1
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agent.js +12 -0
- package/src/data/changelog.json +26 -0
- package/src/harness/prompt-compressor.js +57 -0
- package/src/harness/provider-backed-compaction.js +6 -0
- package/src/lib/coding-task-guidance.js +2 -0
- package/src/lib/context-memory-kernel/jsonl-session-context-port.js +19 -10
- package/src/lib/context-memory-kernel/live-compaction.js +114 -6
- package/src/lib/context-memory-kernel/message-adapter.js +17 -13
- package/src/lib/context-memory-kernel/task-binding.js +41 -0
- package/src/lib/context-memory-kernel/task-worklog-port.js +26 -3
- package/src/lib/evolution/evidence-json-text.js +324 -14
- package/src/lib/evolution/evolution-evidence-projector.js +67 -1
- package/src/lib/read-file-loop-guard.js +134 -4
- package/src/lib/tool-result-preview.js +11 -2
- package/src/repl/agent-repl.js +2 -0
- package/src/runtime/agent-core.js +40 -6
- package/src/runtime/headless-runner.js +1 -0
- package/src/runtime/headless-stream.js +82 -10
- package/src/runtime/message-roles.js +28 -0
- package/src/runtime/policies/agent-policy.js +1 -0
|
@@ -4,6 +4,7 @@ import { isToolRecoveryPaused } from "./tool-recovery-result.js";
|
|
|
4
4
|
import { hostToolOutputIdentity } from "./tool-result-preview.js";
|
|
5
5
|
import { serializeRetainedContext } from "./retained-context-preview.js";
|
|
6
6
|
const MAX_FILES = 12;
|
|
7
|
+
const MAX_UNDELIVERED_READS = 6;
|
|
7
8
|
|
|
8
9
|
function keyFor(filePath, args) {
|
|
9
10
|
return JSON.stringify([
|
|
@@ -94,6 +95,44 @@ export class ReadFileLoopGuard {
|
|
|
94
95
|
retainContext(context);
|
|
95
96
|
}
|
|
96
97
|
}
|
|
98
|
+
for (const entry of this.progress.values()) {
|
|
99
|
+
for (const pending of entry.pendingPages) {
|
|
100
|
+
if (this.isPageVisible(pending.page)) {
|
|
101
|
+
entry.confirmedSpans = mergeSpan(
|
|
102
|
+
entry.confirmedSpans,
|
|
103
|
+
pending.page.readSpan,
|
|
104
|
+
);
|
|
105
|
+
if (pending.page.readSpan.end >= entry.confirmedCursorEnd) {
|
|
106
|
+
entry.confirmedCursorEnd = pending.page.readSpan.end;
|
|
107
|
+
entry.confirmedCursor = {
|
|
108
|
+
lastReadRange: pending.page.range,
|
|
109
|
+
nextRead: pending.page.nextRead || null,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
entry.undeliveredReads++;
|
|
114
|
+
if (pending.reread) entry.rereads.delete(pending.reread);
|
|
115
|
+
if (pending.restoring) entry.contextRestoreOffered = false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
entry.pendingPages = [];
|
|
119
|
+
// Execution reserves ranges synchronously for parallel reads. Only
|
|
120
|
+
// source actually selected for the provider becomes historical coverage.
|
|
121
|
+
entry.spans = entry.confirmedSpans.map((span) => [...span]);
|
|
122
|
+
if (entry.summary) {
|
|
123
|
+
const through =
|
|
124
|
+
entry.confirmedSpans[0]?.[0] === 0 ? entry.confirmedSpans[0][1] : 0;
|
|
125
|
+
entry.summary.readThrough = through;
|
|
126
|
+
entry.summary.reachedEnd = through >= entry.total;
|
|
127
|
+
entry.summary.pendingDelivery = false;
|
|
128
|
+
if (entry.confirmedCursor)
|
|
129
|
+
Object.assign(entry.summary, entry.confirmedCursor);
|
|
130
|
+
else {
|
|
131
|
+
delete entry.summary.lastReadRange;
|
|
132
|
+
entry.summary.nextRead = null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
97
136
|
}
|
|
98
137
|
|
|
99
138
|
isPageVisible(page) {
|
|
@@ -103,8 +142,29 @@ export class ReadFileLoopGuard {
|
|
|
103
142
|
keyFor(visible.path, {}) === keyFor(page.path, {}) &&
|
|
104
143
|
visible.fileVersion === page.fileVersion &&
|
|
105
144
|
typeof page.content === "string" &&
|
|
106
|
-
page.content.length > 0 &&
|
|
107
|
-
visible.
|
|
145
|
+
(page.content.length > 0 || page.readSpan?.total === 0) &&
|
|
146
|
+
((visible.readSpan &&
|
|
147
|
+
page.readSpan &&
|
|
148
|
+
visible.readSpan.total === page.readSpan.total &&
|
|
149
|
+
visible.readSpan.start <= page.readSpan.start &&
|
|
150
|
+
visible.readSpan.end >= page.readSpan.end &&
|
|
151
|
+
visible.readSpan.end - visible.readSpan.start >=
|
|
152
|
+
visible.content.length &&
|
|
153
|
+
visible.readSpan.end - visible.readSpan.start <=
|
|
154
|
+
visible.content.length + 1 &&
|
|
155
|
+
page.readSpan.end - page.readSpan.start >= page.content.length &&
|
|
156
|
+
page.readSpan.end - page.readSpan.start <=
|
|
157
|
+
page.content.length + 1 &&
|
|
158
|
+
visible.content.slice(
|
|
159
|
+
page.readSpan.start - visible.readSpan.start,
|
|
160
|
+
page.readSpan.start -
|
|
161
|
+
visible.readSpan.start +
|
|
162
|
+
page.content.length,
|
|
163
|
+
) === page.content) ||
|
|
164
|
+
(!visible.readSpan &&
|
|
165
|
+
!visible.contentTruncated &&
|
|
166
|
+
JSON.stringify(visible.range) === JSON.stringify(page.range) &&
|
|
167
|
+
visible.content === page.content)),
|
|
108
168
|
) === true
|
|
109
169
|
);
|
|
110
170
|
}
|
|
@@ -166,6 +226,11 @@ export class ReadFileLoopGuard {
|
|
|
166
226
|
entry = {
|
|
167
227
|
fileVersion,
|
|
168
228
|
spans: [],
|
|
229
|
+
confirmedSpans: [],
|
|
230
|
+
pendingPages: [],
|
|
231
|
+
undeliveredReads: 0,
|
|
232
|
+
confirmedCursor: null,
|
|
233
|
+
confirmedCursorEnd: 0,
|
|
169
234
|
rereads: new Set(),
|
|
170
235
|
summary: null,
|
|
171
236
|
excerpts: [],
|
|
@@ -184,7 +249,21 @@ export class ReadFileLoopGuard {
|
|
|
184
249
|
readProgress: { newContent: false },
|
|
185
250
|
};
|
|
186
251
|
}
|
|
252
|
+
if (entry.undeliveredReads >= MAX_UNDELIVERED_READS) {
|
|
253
|
+
return {
|
|
254
|
+
path: filePath,
|
|
255
|
+
fileVersion,
|
|
256
|
+
code: "CC_READ_SOURCE_NOT_DELIVERED",
|
|
257
|
+
error:
|
|
258
|
+
"Repeated file reads did not deliver source content to the model. Report this limitation or use a focused search; do not claim the file was read.",
|
|
259
|
+
contentOmitted: true,
|
|
260
|
+
reachedEnd: false,
|
|
261
|
+
readRecovery: { action: "source-unavailable" },
|
|
262
|
+
};
|
|
263
|
+
}
|
|
187
264
|
let span = page.readSpan;
|
|
265
|
+
let reread;
|
|
266
|
+
let restoringPage = false;
|
|
188
267
|
const covered = entry.spans.some(
|
|
189
268
|
([start, end]) => start <= span.start && end >= span.end,
|
|
190
269
|
);
|
|
@@ -221,6 +300,7 @@ export class ReadFileLoopGuard {
|
|
|
221
300
|
span = page.readSpan;
|
|
222
301
|
targeted = true;
|
|
223
302
|
entry.contextRestoreOffered = true;
|
|
303
|
+
restoringPage = true;
|
|
224
304
|
}
|
|
225
305
|
}
|
|
226
306
|
if (covered || (coveredPrefix && !targeted)) {
|
|
@@ -240,6 +320,7 @@ export class ReadFileLoopGuard {
|
|
|
240
320
|
entry.rereads.size < 3
|
|
241
321
|
) {
|
|
242
322
|
entry.rereads.add(fingerprint);
|
|
323
|
+
reread = fingerprint;
|
|
243
324
|
page.readRecovery = {
|
|
244
325
|
action: restoring ? "context-restore" : "targeted-review",
|
|
245
326
|
};
|
|
@@ -257,6 +338,19 @@ export class ReadFileLoopGuard {
|
|
|
257
338
|
};
|
|
258
339
|
newContent = true;
|
|
259
340
|
} else {
|
|
341
|
+
const confirmedEnd =
|
|
342
|
+
entry.confirmedSpans[0]?.[0] === 0 ? entry.confirmedSpans[0][1] : 0;
|
|
343
|
+
if (this.visiblePages !== null && confirmedEnd < span.total) {
|
|
344
|
+
return {
|
|
345
|
+
path: filePath,
|
|
346
|
+
fileVersion,
|
|
347
|
+
contentOmitted: true,
|
|
348
|
+
reachedEnd: false,
|
|
349
|
+
readProgress: { newContent: false },
|
|
350
|
+
readRecovery: { action: "pending-delivery" },
|
|
351
|
+
hint: "This range has a read pending in the current tool batch. Historical source coverage is recorded only after content reaches the model.",
|
|
352
|
+
};
|
|
353
|
+
}
|
|
260
354
|
return {
|
|
261
355
|
path: filePath,
|
|
262
356
|
fileVersion,
|
|
@@ -283,6 +377,25 @@ export class ReadFileLoopGuard {
|
|
|
283
377
|
}
|
|
284
378
|
}
|
|
285
379
|
entry.spans = mergeSpan(entry.spans, page.readSpan);
|
|
380
|
+
entry.total = page.readSpan.total;
|
|
381
|
+
if (this.visiblePages === null) {
|
|
382
|
+
// Direct tool consumers without provider selection keep synchronous
|
|
383
|
+
// semantics. Agent runs observe the initial request before any reads.
|
|
384
|
+
entry.confirmedSpans = entry.spans.map((span) => [...span]);
|
|
385
|
+
} else {
|
|
386
|
+
entry.pendingPages.push({
|
|
387
|
+
page: {
|
|
388
|
+
path: page.path,
|
|
389
|
+
fileVersion: page.fileVersion,
|
|
390
|
+
content: page.content,
|
|
391
|
+
readSpan: { ...page.readSpan },
|
|
392
|
+
...(page.range ? { range: { ...page.range } } : {}),
|
|
393
|
+
...(page.nextRead ? { nextRead: { ...page.nextRead } } : {}),
|
|
394
|
+
},
|
|
395
|
+
reread,
|
|
396
|
+
restoring: restoringPage,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
286
399
|
entry.lastReadRequest = this.visibleRequest;
|
|
287
400
|
// Coverage alone cannot restore source code after compaction. Keep bounded
|
|
288
401
|
// observed pages for every file type, not only large Markdown outlines.
|
|
@@ -302,10 +415,12 @@ export class ReadFileLoopGuard {
|
|
|
302
415
|
].slice(-2);
|
|
303
416
|
}
|
|
304
417
|
const allRead =
|
|
305
|
-
entry.
|
|
418
|
+
entry.confirmedSpans[0]?.[0] === 0 &&
|
|
419
|
+
entry.confirmedSpans[0][1] >= page.readSpan.total;
|
|
306
420
|
// A read of the tail alone must never imply coverage of earlier sections.
|
|
307
421
|
const previousEnd = entry.summary?.readThrough || 0;
|
|
308
|
-
const updateCursor =
|
|
422
|
+
const updateCursor =
|
|
423
|
+
this.visiblePages === null && page.readSpan.end >= previousEnd;
|
|
309
424
|
entry.summary = {
|
|
310
425
|
...entry.summary,
|
|
311
426
|
path: filePath,
|
|
@@ -317,8 +432,16 @@ export class ReadFileLoopGuard {
|
|
|
317
432
|
}
|
|
318
433
|
: {}),
|
|
319
434
|
reachedEnd: allRead,
|
|
435
|
+
...(this.visiblePages !== null ? { pendingDelivery: true } : {}),
|
|
320
436
|
...(page.outline ? { outline: page.outline } : {}),
|
|
321
437
|
};
|
|
438
|
+
if (updateCursor) {
|
|
439
|
+
entry.confirmedCursorEnd = page.readSpan.end;
|
|
440
|
+
entry.confirmedCursor = {
|
|
441
|
+
lastReadRange: page.range,
|
|
442
|
+
nextRead: page.nextRead || null,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
322
445
|
this.progress.delete(key);
|
|
323
446
|
this.progress.set(key, entry);
|
|
324
447
|
// Discard pathological fragmented coverage instead of inventing coverage.
|
|
@@ -336,6 +459,13 @@ export class ReadFileLoopGuard {
|
|
|
336
459
|
// as another duplicate would immediately schedule another "one-turn"
|
|
337
460
|
// pause and prevent read_file from ever becoming available again.
|
|
338
461
|
if (isToolRecoveryPaused(result)) return;
|
|
462
|
+
if (
|
|
463
|
+
tool === "read_file" &&
|
|
464
|
+
result?.code === "CC_READ_SOURCE_NOT_DELIVERED"
|
|
465
|
+
) {
|
|
466
|
+
this.repeatedBatches = Math.max(this.repeatedBatches, 6);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
339
469
|
if (
|
|
340
470
|
!this.batch ||
|
|
341
471
|
result?.error ||
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createEvidenceJsonTextBoundary,
|
|
3
3
|
AGENT_CODE_TEXT_RESULT_TOOLS,
|
|
4
|
+
EVIDENCE_JSON_POLICY,
|
|
4
5
|
} from "./evolution/evidence-json-text.js";
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
6
7
|
|
|
@@ -33,7 +34,11 @@ const PREVIEW_HINT =
|
|
|
33
34
|
|
|
34
35
|
/** Check only host-created excerpts; original uncut tool text still reaches ingress. */
|
|
35
36
|
function completeExcerpt(text, tool) {
|
|
36
|
-
const scan = createEvidenceJsonTextBoundary((message) => new Error(message)
|
|
37
|
+
const scan = createEvidenceJsonTextBoundary((message) => new Error(message), {
|
|
38
|
+
...EVIDENCE_JSON_POLICY,
|
|
39
|
+
sourceLiteralPolicy: "explicit-source-containers-v1",
|
|
40
|
+
maxSourceTokens: 32768,
|
|
41
|
+
});
|
|
37
42
|
const visit = (value, layer) => {
|
|
38
43
|
if (typeof value === "string") return project(value, layer);
|
|
39
44
|
if (Array.isArray(value)) return value.map((child) => visit(child, layer));
|
|
@@ -48,6 +53,7 @@ function completeExcerpt(text, tool) {
|
|
|
48
53
|
scan(value, {
|
|
49
54
|
layer,
|
|
50
55
|
codeText: AGENT_CODE_TEXT_RESULT_TOOLS.includes(tool),
|
|
56
|
+
sourceText: layer === 0,
|
|
51
57
|
visit,
|
|
52
58
|
plain: (plain) => plain,
|
|
53
59
|
equal: (left, right) => JSON.stringify(left) === JSON.stringify(right),
|
|
@@ -235,7 +241,10 @@ export function safeHostToolResultForModel(tool, result) {
|
|
|
235
241
|
const text = result[field];
|
|
236
242
|
if (typeof text !== "string" || completeExcerpt(text, tool)) continue;
|
|
237
243
|
if (safe === result) safe = { ...result };
|
|
238
|
-
safe[field] =
|
|
244
|
+
safe[field] =
|
|
245
|
+
tool === "read_file"
|
|
246
|
+
? `Text omitted (${text.length} chars); this excerpt could not be safely projected. Read a complete surrounding statement or container; expand the range or read the full file within existing size limits when necessary. Omitted content has not been inspected.`
|
|
247
|
+
: omitted(text.length);
|
|
239
248
|
safe[`${field}Omitted`] = true;
|
|
240
249
|
safe[`${field}OmittedChars`] = text.length;
|
|
241
250
|
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -3544,6 +3544,7 @@ async function startAgentReplInWorkspaceOwned(
|
|
|
3544
3544
|
});
|
|
3545
3545
|
if (feature("PROMPT_COMPRESSOR") || contextMemoryCutover.canonical) {
|
|
3546
3546
|
_compressor = new PromptCompressor({
|
|
3547
|
+
contextMemoryModelWindowTokens: options.contextMemoryModelWindowTokens,
|
|
3547
3548
|
model,
|
|
3548
3549
|
provider,
|
|
3549
3550
|
llmQuery: async (prompt) => {
|
|
@@ -9852,6 +9853,7 @@ async function startAgentReplInWorkspaceOwned(
|
|
|
9852
9853
|
usageEvents,
|
|
9853
9854
|
thinking: reasoning,
|
|
9854
9855
|
} = await agentLoop(messages, {
|
|
9856
|
+
contextMemoryModelWindowTokens: options.contextMemoryModelWindowTokens,
|
|
9855
9857
|
...captureSkillRuntimeDependencies(options),
|
|
9856
9858
|
...liveOpts,
|
|
9857
9859
|
waitForOutput: _replOutputFlow.wait,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
projectMessagesWithTaskBinding,
|
|
3
|
+
getTaskCompactionBindings,
|
|
4
|
+
} from "./message-roles.js";
|
|
2
5
|
import {
|
|
3
6
|
providerFetch,
|
|
4
7
|
requireUnscopedToolExecution,
|
|
@@ -12375,7 +12378,10 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12375
12378
|
),
|
|
12376
12379
|
{
|
|
12377
12380
|
signal,
|
|
12378
|
-
retries:
|
|
12381
|
+
retries:
|
|
12382
|
+
options.workflowEffectId || options.disableTransportRetry === true
|
|
12383
|
+
? 0
|
|
12384
|
+
: undefined,
|
|
12379
12385
|
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
12380
12386
|
...(typeof options.onStreamRetry === "function"
|
|
12381
12387
|
? {
|
|
@@ -12527,7 +12533,10 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12527
12533
|
),
|
|
12528
12534
|
{
|
|
12529
12535
|
signal,
|
|
12530
|
-
retries:
|
|
12536
|
+
retries:
|
|
12537
|
+
options.workflowEffectId || options.disableTransportRetry === true
|
|
12538
|
+
? 0
|
|
12539
|
+
: undefined,
|
|
12531
12540
|
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
12532
12541
|
...(typeof options.onStreamRetry === "function"
|
|
12533
12542
|
? {
|
|
@@ -12655,7 +12664,10 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12655
12664
|
),
|
|
12656
12665
|
{
|
|
12657
12666
|
signal,
|
|
12658
|
-
retries:
|
|
12667
|
+
retries:
|
|
12668
|
+
options.workflowEffectId || options.disableTransportRetry === true
|
|
12669
|
+
? 0
|
|
12670
|
+
: undefined,
|
|
12659
12671
|
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
12660
12672
|
...(typeof options.onStreamRetry === "function"
|
|
12661
12673
|
? {
|
|
@@ -12720,7 +12732,10 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12720
12732
|
),
|
|
12721
12733
|
{
|
|
12722
12734
|
signal,
|
|
12723
|
-
retries:
|
|
12735
|
+
retries:
|
|
12736
|
+
options.workflowEffectId || options.disableTransportRetry === true
|
|
12737
|
+
? 0
|
|
12738
|
+
: undefined,
|
|
12724
12739
|
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
12725
12740
|
...(typeof options.onStreamRetry === "function"
|
|
12726
12741
|
? {
|
|
@@ -12768,7 +12783,10 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12768
12783
|
),
|
|
12769
12784
|
{
|
|
12770
12785
|
signal,
|
|
12771
|
-
retries:
|
|
12786
|
+
retries:
|
|
12787
|
+
options.workflowEffectId || options.disableTransportRetry === true
|
|
12788
|
+
? 0
|
|
12789
|
+
: undefined,
|
|
12772
12790
|
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
12773
12791
|
...(typeof options.onStreamRetry === "function"
|
|
12774
12792
|
? {
|
|
@@ -14607,6 +14625,9 @@ async function _getAutoCompactor(options) {
|
|
|
14607
14625
|
onToken: undefined,
|
|
14608
14626
|
onStall: undefined,
|
|
14609
14627
|
onStreamRetry: undefined,
|
|
14628
|
+
// Summary spend is settled once at the compactor boundary.
|
|
14629
|
+
// A second transport attempt would bypass that accounting.
|
|
14630
|
+
disableTransportRetry: true,
|
|
14610
14631
|
providerRequestId:
|
|
14611
14632
|
requestBinding?.providerRequestId || undefined,
|
|
14612
14633
|
maxOutputTokens,
|
|
@@ -14642,6 +14663,7 @@ async function _getAutoCompactor(options) {
|
|
|
14642
14663
|
return compactLiveMessagesCanonical(messages, {
|
|
14643
14664
|
compressor: compatibilitySummarizer,
|
|
14644
14665
|
compressOptions,
|
|
14666
|
+
requiredUserBindings: compressOptions.requiredUserBindings,
|
|
14645
14667
|
sessionId: options.sessionId,
|
|
14646
14668
|
operationId: `auto-${randomUUID()}`,
|
|
14647
14669
|
provider: options.provider || "ollama",
|
|
@@ -14979,6 +15001,11 @@ export async function* agentLoop(messages, options) {
|
|
|
14979
15001
|
).length === 1,
|
|
14980
15002
|
}
|
|
14981
15003
|
: null;
|
|
15004
|
+
const taskCompactionBindings = getTaskCompactionBindings(
|
|
15005
|
+
options.projectProviderMessages,
|
|
15006
|
+
messages,
|
|
15007
|
+
currentUserTaskBinding,
|
|
15008
|
+
);
|
|
14982
15009
|
if (options.serviceTier != null) {
|
|
14983
15010
|
resolveModelServiceTier(
|
|
14984
15011
|
resolveModelCapabilityProfile(options),
|
|
@@ -15901,6 +15928,7 @@ export async function* agentLoop(messages, options) {
|
|
|
15901
15928
|
}
|
|
15902
15929
|
: await compactor.compress(workingMessages, {
|
|
15903
15930
|
preserveToolPairs: true,
|
|
15931
|
+
requiredUserBindings: taskCompactionBindings,
|
|
15904
15932
|
...pinOpts,
|
|
15905
15933
|
});
|
|
15906
15934
|
if (
|
|
@@ -15936,6 +15964,9 @@ export async function* agentLoop(messages, options) {
|
|
|
15936
15964
|
reason:
|
|
15937
15965
|
stats.summaryUsageUnknownReason ||
|
|
15938
15966
|
"provider_transport_outcome_unknown",
|
|
15967
|
+
...(stats.summaryProviderError
|
|
15968
|
+
? { providerError: stats.summaryProviderError }
|
|
15969
|
+
: {}),
|
|
15939
15970
|
};
|
|
15940
15971
|
}
|
|
15941
15972
|
}
|
|
@@ -16042,6 +16073,9 @@ export async function* agentLoop(messages, options) {
|
|
|
16042
16073
|
? compactionUsageCall.model
|
|
16043
16074
|
: stats.summaryModel || options.model || null,
|
|
16044
16075
|
reason: unknownReason,
|
|
16076
|
+
...(stats.summaryProviderError
|
|
16077
|
+
? { providerError: stats.summaryProviderError }
|
|
16078
|
+
: {}),
|
|
16045
16079
|
code:
|
|
16046
16080
|
unknownReason === "provider_usage_not_reported"
|
|
16047
16081
|
? "provider_usage_missing"
|
|
@@ -2942,6 +2942,7 @@ async function runAgentHeadlessInWorkspace(
|
|
|
2942
2942
|
});
|
|
2943
2943
|
};
|
|
2944
2944
|
const loopOptions = {
|
|
2945
|
+
contextMemoryModelWindowTokens: options.contextMemoryModelWindowTokens,
|
|
2945
2946
|
...(evolutionIngress === null ? {} : { evolutionIngress }),
|
|
2946
2947
|
model,
|
|
2947
2948
|
provider,
|
|
@@ -964,6 +964,7 @@ async function runTurn(
|
|
|
964
964
|
let endReason = "complete";
|
|
965
965
|
let stopForCost = false;
|
|
966
966
|
let stopForCompactionUsageUnknown = false;
|
|
967
|
+
let compactionUsageUnknown = null;
|
|
967
968
|
let responseCompleted = false;
|
|
968
969
|
// P0-2: the side-effect op currently in flight (at most one — the loop runs
|
|
969
970
|
// tools serially). Non-null only between a dangerous tool's tool-executing
|
|
@@ -1223,6 +1224,7 @@ async function runTurn(
|
|
|
1223
1224
|
}
|
|
1224
1225
|
break;
|
|
1225
1226
|
case "compaction-usage-unknown":
|
|
1227
|
+
compactionUsageUnknown = compactionUsageUnknownDiagnostic(event);
|
|
1226
1228
|
if (event.ledgerPersisted !== true) {
|
|
1227
1229
|
persistUsageEvent?.({
|
|
1228
1230
|
...event,
|
|
@@ -1282,7 +1284,52 @@ async function runTurn(
|
|
|
1282
1284
|
) {
|
|
1283
1285
|
sideEffects.persist();
|
|
1284
1286
|
}
|
|
1285
|
-
return { finalText, endReason, usage, toolCalls };
|
|
1287
|
+
return { finalText, endReason, usage, toolCalls, compactionUsageUnknown };
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function compactionUsageUnknownDiagnostic(event) {
|
|
1291
|
+
const reason =
|
|
1292
|
+
event?.reason === "provider_usage_not_reported"
|
|
1293
|
+
? "provider_usage_not_reported"
|
|
1294
|
+
: "provider_transport_outcome_unknown";
|
|
1295
|
+
const providerError = event?.providerError;
|
|
1296
|
+
const providerHints = {
|
|
1297
|
+
authentication:
|
|
1298
|
+
"Provider authentication failed; check the API credentials.",
|
|
1299
|
+
access_denied: "Provider access was denied; check model permissions.",
|
|
1300
|
+
rate_limit: "The provider rate limit was reached; wait before resuming.",
|
|
1301
|
+
server_error:
|
|
1302
|
+
"The provider returned a server error; check service availability.",
|
|
1303
|
+
http_error:
|
|
1304
|
+
"The provider rejected the compaction request; check the model endpoint.",
|
|
1305
|
+
network: "The model connection failed; check network and proxy settings.",
|
|
1306
|
+
invalid_response:
|
|
1307
|
+
"The provider returned an invalid response; check API compatibility.",
|
|
1308
|
+
unknown: "The provider request failed.",
|
|
1309
|
+
};
|
|
1310
|
+
const category = providerError?.category;
|
|
1311
|
+
const projected = Object.hasOwn(providerHints, category || "")
|
|
1312
|
+
? {
|
|
1313
|
+
category,
|
|
1314
|
+
...(Number.isInteger(providerError.httpStatus) &&
|
|
1315
|
+
providerError.httpStatus >= 400 &&
|
|
1316
|
+
providerError.httpStatus <= 599
|
|
1317
|
+
? { httpStatus: providerError.httpStatus }
|
|
1318
|
+
: {}),
|
|
1319
|
+
}
|
|
1320
|
+
: null;
|
|
1321
|
+
return {
|
|
1322
|
+
code: "CC_COMPACTION_USAGE_UNKNOWN",
|
|
1323
|
+
usage_unknown_reason: reason,
|
|
1324
|
+
...(projected ? { provider_error: projected } : {}),
|
|
1325
|
+
error:
|
|
1326
|
+
(reason === "provider_usage_not_reported"
|
|
1327
|
+
? "Compaction provider did not report valid token usage"
|
|
1328
|
+
: "Compaction request failed before provider usage could be confirmed") +
|
|
1329
|
+
"; the session stopped without retrying the request. " +
|
|
1330
|
+
(projected ? providerHints[category] + " " : "") +
|
|
1331
|
+
"Review the saved session and provider usage, check the model connection, then resume explicitly.",
|
|
1332
|
+
};
|
|
1286
1333
|
}
|
|
1287
1334
|
|
|
1288
1335
|
/**
|
|
@@ -3821,6 +3868,31 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
3821
3868
|
{ code: "CC_SESSION_TOOL_ROUND_SETTLEMENT_HEAD_UNKNOWN" },
|
|
3822
3869
|
);
|
|
3823
3870
|
}
|
|
3871
|
+
// Only a new, acknowledged tool_round has item positions to associate
|
|
3872
|
+
// with live IDE output. A no-op settlement returns an existing head only.
|
|
3873
|
+
if (appended.event?.type === "tool_round") {
|
|
3874
|
+
const items = appended.event.data.messages.map((message, itemIndex) => ({
|
|
3875
|
+
itemIndex,
|
|
3876
|
+
role: message.role,
|
|
3877
|
+
toolCallId:
|
|
3878
|
+
message.role === "assistant"
|
|
3879
|
+
? message.tool_calls[0].id
|
|
3880
|
+
: message.tool_call_id,
|
|
3881
|
+
}));
|
|
3882
|
+
for (let offset = 0; offset < items.length; offset += 100) {
|
|
3883
|
+
emit({
|
|
3884
|
+
type: "system",
|
|
3885
|
+
subtype: "tool_round_persisted",
|
|
3886
|
+
session_id: sessionId,
|
|
3887
|
+
transcript_refs: {
|
|
3888
|
+
schema: "chainlesschain.session-tool-round-references/v1",
|
|
3889
|
+
sessionId,
|
|
3890
|
+
eventId: appended.hash,
|
|
3891
|
+
items: items.slice(offset, offset + 100),
|
|
3892
|
+
},
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3824
3896
|
return appended;
|
|
3825
3897
|
};
|
|
3826
3898
|
const settleToolRoundsForHost = (phase, clientMessageId = null) => {
|
|
@@ -3873,6 +3945,7 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
3873
3945
|
return settleToolRoundsForHost(phase);
|
|
3874
3946
|
};
|
|
3875
3947
|
const loopOptionsBase = {
|
|
3948
|
+
contextMemoryModelWindowTokens: options.contextMemoryModelWindowTokens,
|
|
3876
3949
|
...(evolutionIngress === null ? {} : { evolutionIngress }),
|
|
3877
3950
|
reviewChanges: options.reviewChanges === true,
|
|
3878
3951
|
model,
|
|
@@ -4851,9 +4924,11 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
4851
4924
|
type: "result",
|
|
4852
4925
|
subtype: "error_compaction_usage_unknown",
|
|
4853
4926
|
is_error: true,
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4927
|
+
...compactionUsageUnknownDiagnostic(
|
|
4928
|
+
compatibilityResult?.usageUnknownEvent || {
|
|
4929
|
+
reason: "provider_transport_outcome_unknown",
|
|
4930
|
+
},
|
|
4931
|
+
),
|
|
4857
4932
|
session_id: sessionId,
|
|
4858
4933
|
});
|
|
4859
4934
|
sawError = true;
|
|
@@ -4905,9 +4980,7 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
4905
4980
|
type: "result",
|
|
4906
4981
|
subtype: "error_compaction_usage_unknown",
|
|
4907
4982
|
is_error: true,
|
|
4908
|
-
|
|
4909
|
-
error:
|
|
4910
|
-
"Compaction provider usage is unknown; the paid compaction is not retried",
|
|
4983
|
+
...compactionUsageUnknownDiagnostic(usageUnknownEvent),
|
|
4911
4984
|
session_id: sessionId,
|
|
4912
4985
|
...(costBudget ? { budget_state: "unverifiable" } : {}),
|
|
4913
4986
|
});
|
|
@@ -5908,9 +5981,8 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
5908
5981
|
type: "result",
|
|
5909
5982
|
subtype: "error_compaction_usage_unknown",
|
|
5910
5983
|
is_error: true,
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
"Compaction provider usage is unknown; the paid compaction is not retried",
|
|
5984
|
+
...(outcome.compactionUsageUnknown ||
|
|
5985
|
+
compactionUsageUnknownDiagnostic(null)),
|
|
5914
5986
|
session_id: sessionId,
|
|
5915
5987
|
turn: turns,
|
|
5916
5988
|
usage: outcome.usage,
|
|
@@ -166,6 +166,34 @@ export function createResumeRoleProjection(messages) {
|
|
|
166
166
|
return projection;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/** Capture the original user sources needed by an owned resume merge. */
|
|
170
|
+
export function getTaskCompactionBindings(projection, input, taskBinding) {
|
|
171
|
+
if (!taskBinding) return [];
|
|
172
|
+
const owned = RESUME_ROLE_PROJECTIONS.get(projection);
|
|
173
|
+
const capture = (binding) => ({
|
|
174
|
+
...binding,
|
|
175
|
+
matchCount: input.filter((message) =>
|
|
176
|
+
isDeepStrictEqual(message, binding.message),
|
|
177
|
+
).length,
|
|
178
|
+
});
|
|
179
|
+
if (!owned) return [capture(taskBinding)];
|
|
180
|
+
const sourceIndex = input.indexOf(taskBinding.original);
|
|
181
|
+
const group = owned(input).sources.find((indices) =>
|
|
182
|
+
indices.includes(sourceIndex),
|
|
183
|
+
);
|
|
184
|
+
return (group || [sourceIndex]).map((index) => {
|
|
185
|
+
if (index === sourceIndex) return capture(taskBinding);
|
|
186
|
+
const original = input[index];
|
|
187
|
+
return capture({
|
|
188
|
+
original,
|
|
189
|
+
message: JSON.parse(canonicalJson(original)),
|
|
190
|
+
allowContentRelocation:
|
|
191
|
+
input.filter((message) => isDeepStrictEqual(message, original))
|
|
192
|
+
.length === 1,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
169
197
|
/**
|
|
170
198
|
* Preserve a bound task through this module's exact, captured resume merges.
|
|
171
199
|
* Arbitrary callbacks, wrappers and forged properties never receive lineage.
|
|
@@ -23,6 +23,7 @@ export function resolveAgentPolicy({
|
|
|
23
23
|
|
|
24
24
|
return {
|
|
25
25
|
model: connection.model || defaults.model || "qwen2:7b",
|
|
26
|
+
contextMemoryModelWindowTokens: overrides.contextMemoryModelWindowTokens,
|
|
26
27
|
visionModel: overrides.visionModel,
|
|
27
28
|
provider: connection.provider,
|
|
28
29
|
baseUrl: connection.baseUrl,
|