chainlesschain 0.166.138 → 0.166.139
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 +25 -9
- 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/data/changelog.json +13 -0
- package/src/gateways/ws/ws-server.js +16 -0
- package/src/lib/coding-change-review-context.js +51 -0
- package/src/lib/coding-task-guidance.js +1 -1
- package/src/lib/read-file-loop-guard.js +13 -10
- package/src/lib/read-file-page.js +4 -1
- package/src/lib/source-read-recovery.js +89 -0
- package/src/lib/tool-result-preview.js +7 -3
- package/src/runtime/agent-core.js +63 -34
package/README.md
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
# chainlesschain CLI
|
|
2
2
|
|
|
3
|
-
Publication authorization: the user explicitly permits this release irrespective of the five task results. Complete exact-commit CLI CI and Strict Sandbox remain required before Actions OIDC publication, followed by CLI registry verification and paired IDE releases. Latest model acceptance is 0/5; historical 41/45 is unchanged.
|
|
4
|
-
|
|
5
3
|
Command-line interface for installing, configuring, and managing [ChainlessChain](https://www.chainlesschain.com), including the coding agent, governed tools, sessions, memory, and IDE integrations.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
<!-- release139-candidate:start -->
|
|
6
|
+
|
|
7
|
+
## CLI 0.166.139 / IDE repair release
|
|
8
|
+
|
|
9
|
+
This package is CLI **0.166.139**, paired with Open VSX **0.37.175** and JetBrains **0.4.192**, with unchanged upstream SDK **0.2.17** and Kernel **0.1.11**. Production publication uses GitHub Actions OIDC after the exact-source CLI CI and Strict Sandbox matrices pass on every configured platform. Install `npm i -g chainlesschain@0.166.139 --registry https://registry.npmjs.org`, restart the CLI and IDE chat hosts, and check `cc --version` and `cc doctor`. IDE plugins publish after the public CLI package is verified; mobile and PC clients follow verified IDE releases on their separate product version track.
|
|
10
|
+
|
|
11
|
+
The repair reserves author, fresh independent review and final reporting work within the original total request budget. Partial JavaScript reads recover complete surrounding statements from the same file snapshot within the existing output limit; focused reads do not silently switch to unrelated ranges. This release includes the standalone WebSocket host-model-policy repair.
|
|
12
|
+
|
|
13
|
+
CLI .138 acceptance remains **2/4**: verify-13 and first-run-darwin-vscode passed; verify-08 and verify-36 failed on author-stage iteration exhaustion despite passing candidate checks. CLI .139 engineering checks do not establish new model-task acceptance or rescore those results. CLI .136 verify-19 remains a separate historical pass; historical **41/45** and .137 **0/5** are unchanged. Review stays enabled, with 20 model turns, 24 physical requests per diagnostic case and the 32768-token planning context. JetBrains uploads may await Marketplace approval; an official pending-review ZIP is distinct from a public listing. Microsoft Marketplace remains deferred.
|
|
14
|
+
|
|
15
|
+
<!-- release139-candidate:end -->
|
|
16
|
+
|
|
17
|
+
<!-- release138-current:start -->
|
|
18
|
+
|
|
19
|
+
## CLI 0.166.138 — previous verified public release
|
|
20
|
+
|
|
21
|
+
CLI **0.166.138** and upstream Agent SDK **0.2.17** are published through GitHub Actions OIDC. Install `npm i -g chainlesschain@0.166.138 --registry https://registry.npmjs.org`, restart `cc ui` and IDE chat hosts, then run `cc --version` and `cc doctor`. Open VSX **0.37.174** publication succeeded. JetBrains **0.4.191** is approved and publicly listed in Marketplace (update `1209507`); public ZIP payload comparison with the official build remains pending. Both new IDE versions recommend CLI **0.166.138**.
|
|
22
|
+
|
|
23
|
+
This release preserves recovery when source content was omitted from a model request and retains the original user task across canonical compaction, tool tails and persistent sessions. Only exact source spans selected for a provider count as historical read coverage. Repeated delivery failures stop with an explicit limitation.
|
|
8
24
|
|
|
9
|
-
|
|
25
|
+
Exact release source `e42ea07b6da7ab2c3eb709002448b32d5fedf535` passed all 75 required CLI CI jobs (one optional PR dry-run skipped) and all 6 Strict Sandbox jobs across the configured Linux, Windows and macOS matrix. The official [OIDC workflow](https://github.com/chainlesschain/chainlesschain/actions/runs/41313099948) independently downloaded the public SDK/CLI, verified immutable package bytes and signed provenance, and installed/started the CLI before IDE release tags were created. SDK 0.2.17 aligns its full source tree with provenance; SDK runtime output is unchanged.
|
|
10
26
|
|
|
11
|
-
|
|
27
|
+
As reviewed on **2026-11-02**, the public .138 four-sample retest accepted **2/4**: first-run VS Code and verify-13 passed. verify-08 and verify-36 passed independent candidate tests but exhausted **20/20** author turns before independent review, so both remain failures. All **80/80** physical requests have complete usage records. Previous .137 **0/5**, historical cumulative **41/45**, and the separate image follow-up **0/1** retain their own scopes; this is not a full 45-task success rate for .138. Accepted samples are skipped in later retests. Engineering checks and publication add no model task acceptance. Kernel stays **0.1.11**; Microsoft Marketplace and PC/mobile are outside this release.
|
|
12
28
|
|
|
13
|
-
|
|
29
|
+
[Publication evidence](../../docs/research/cli/evidence/release138-public/README.md) · [Open VSX release](https://github.com/chainlesschain/chainlesschain/actions/runs/41316653600) · [Current model results](../../docs/research/cli/verify01-openai-138/README.md) · [Upgrade guide](https://docs.chainlesschain.com/chainlesschain/cli-ide-update-2026-11-02-138.html).
|
|
14
30
|
|
|
15
|
-
|
|
31
|
+
Main also contains the later standalone WebSocket host-model-policy repair (`2e97ce3d79`), awaiting its own fully validated CLI release. The published CLI 0.166.138 remains bound to its verified release source.
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
<!-- release138-current:end -->
|
|
18
34
|
|
|
19
|
-
## CLI 0.166.136 —
|
|
35
|
+
## CLI 0.166.136 — previous public repair
|
|
20
36
|
|
|
21
37
|
Install with `npm i -g chainlesschain@0.166.136 --registry https://registry.npmjs.org`, restart the chat host, then run `cc --version` and `cc doctor`. This release repairs full-history session forks and preserves independent plugin/MCP restrictions through background restore. Malformed policy values fail before normalization or customization loading.
|
|
22
38
|
|
package/package.json
CHANGED
package/src/data/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "CHANGELOG.md",
|
|
3
3
|
"releases": [
|
|
4
|
+
{
|
|
5
|
+
"productVersion": null,
|
|
6
|
+
"cliVersion": "0.166.139",
|
|
7
|
+
"cliVersions": ["0.166.139"],
|
|
8
|
+
"date": "2026-11-02",
|
|
9
|
+
"title": "",
|
|
10
|
+
"sections": [
|
|
11
|
+
{
|
|
12
|
+
"heading": "Fixed - cc CLI 0.166.139: complete author work and independent review within one budget (candidate, 2026-11-02)",
|
|
13
|
+
"body": "- Reserve fresh independent inspection, repair, read-back and final reporting inside the original model-turn limit, including when the author continues calling tools. Unknown usage and insufficient remaining review capacity fail explicitly; last-turn tool calls cannot become a successful terminal result.\n- Recover complete surrounding JavaScript statements from the same file snapshot when a partial page cannot be projected. Preserve the original requested range and actual returned range, the existing JSON/source validation, provider delivery accounting and output metadata headroom.\n- Preserve focused explicit read ranges instead of silently substituting an unrelated unread section. Broad truncated scans retain bounded cursor progress.\n- Pair CLI 0.166.139 with Open VSX 0.37.175 and JetBrains 0.4.192 after complete exact-source release gates and verified upstream publication. SDK 0.2.17 and Kernel 0.1.11 remain unchanged and must pass upstream reuse checks.\n- Retest only verify-08 and verify-36 with the new package set, keeping independent review and the original budget. Prior CLI .138 result remains 2/4; previously accepted samples are not repeated. These engineering repairs do not claim new real-model success."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"productVersion": null,
|
|
6
19
|
"cliVersion": "0.166.138",
|
|
@@ -388,6 +388,22 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
388
388
|
);
|
|
389
389
|
this.resolveEvolutionCompositionFactory =
|
|
390
390
|
options.resolveEvolutionCompositionFactory || null;
|
|
391
|
+
// Standalone embedders use the same host-owned optional Evolution policy
|
|
392
|
+
// as cc ui. Explicit policy hooks/factories (including null) still win.
|
|
393
|
+
if (
|
|
394
|
+
options.prepareUiModelTurn === undefined &&
|
|
395
|
+
options.evolutionCompositionFactory === undefined &&
|
|
396
|
+
options.resolveEvolutionCompositionFactory === undefined
|
|
397
|
+
) {
|
|
398
|
+
this.prepareUiModelTurn = async (context) => {
|
|
399
|
+
const { prepareConfiguredUiModelTurn } = await import(
|
|
400
|
+
"../../lib/evolution/optional-ui-model-turn.js"
|
|
401
|
+
);
|
|
402
|
+
return prepareConfiguredUiModelTurn(context, {
|
|
403
|
+
cwd: this.projectRoot,
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
}
|
|
391
407
|
this.runtimeAdmissionDependencies =
|
|
392
408
|
captureSkillRuntimeDependencies(options);
|
|
393
409
|
this.skillOutcomeIndex =
|
|
@@ -140,6 +140,57 @@ function pendingCalls(messages) {
|
|
|
140
140
|
* unmarked system messages must already be trusted host instructions.
|
|
141
141
|
* This journal neither replaces durable history nor grants any authority.
|
|
142
142
|
*/
|
|
143
|
+
export function changeReviewRequestReserve(limit) {
|
|
144
|
+
// Inspection, repair, read-back and a terminal account share the original
|
|
145
|
+
// budget. Tiny runs retain at least one author request, never extra credit.
|
|
146
|
+
return Number.isFinite(limit) ? Math.min(4, Math.max(0, limit - 1)) : 4;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function changeReviewBudgetNotice(budget, reviewing) {
|
|
150
|
+
const available = budget.remaining() + 1;
|
|
151
|
+
if (reviewing)
|
|
152
|
+
return available === 1
|
|
153
|
+
? " Independent change review: this is the final model request. Give a concise terminal account based on observations already obtained. Report unresolved requirements as incomplete. A tool call now cannot receive another model response and will not count as completion."
|
|
154
|
+
: " Independent change review is active. Inspect current files, repair concrete requirement gaps, then read back the repair. Reserve the final request for a concise terminal account of results and limitations; further exploration cannot extend this budget.";
|
|
155
|
+
const reserve = changeReviewRequestReserve(budget.limit);
|
|
156
|
+
const authorRequests = Math.max(0, available - reserve);
|
|
157
|
+
return (
|
|
158
|
+
` Author allocation: ${authorRequests} requests remain before the ${reserve}-request independent change-review reserve (including this request when positive). Write the deliverable early; author completion is provisional. Review needs fresh inspection and a terminal response and does not receive extra requests. ` +
|
|
159
|
+
(authorRequests === 0
|
|
160
|
+
? "The preferred author allocation is exhausted; further inspection without a delivered change may leave insufficient budget for review."
|
|
161
|
+
: "The runtime will hand changed files to independent review when the reserve is reached.")
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function changeReviewBudgetInsufficientError(remaining) {
|
|
166
|
+
const error = new Error(
|
|
167
|
+
`Independent change review requires at least two remaining model requests for fresh inspection and a terminal response; only ${remaining} remain. The task has not completed independent review.`,
|
|
168
|
+
);
|
|
169
|
+
error.code = "CC_CHANGE_REVIEW_BUDGET_INSUFFICIENT";
|
|
170
|
+
return error;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function canReserveChangeReview({
|
|
174
|
+
enabled,
|
|
175
|
+
changed,
|
|
176
|
+
requested,
|
|
177
|
+
usageKnown,
|
|
178
|
+
pendingMutations,
|
|
179
|
+
pendingChildren,
|
|
180
|
+
remaining,
|
|
181
|
+
limit,
|
|
182
|
+
}) {
|
|
183
|
+
return (
|
|
184
|
+
enabled === true &&
|
|
185
|
+
changed === true &&
|
|
186
|
+
!requested &&
|
|
187
|
+
usageKnown !== false &&
|
|
188
|
+
pendingMutations === 0 &&
|
|
189
|
+
pendingChildren === 0 &&
|
|
190
|
+
remaining <= changeReviewRequestReserve(limit)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
143
194
|
export class CodingChangeReviewContext {
|
|
144
195
|
#initial;
|
|
145
196
|
#journal = [];
|
|
@@ -11,7 +11,7 @@ export const CODING_TASK_QUALITY_GUIDANCE = `Coding task quality:
|
|
|
11
11
|
- Trace rejection before asserting its aftermath: it may precede registration or persistence. Inspect whether state stays unchanged, a failed record exists or an event is emitted. Compare independently recorded identities and provenance; equal text does not prove ownership.
|
|
12
12
|
- Inspect persistence projections before comparing histories. Host notices, lineage and imports may accompany user/assistant messages; verify their provenance separately. Report defects that cannot be repaired within the permitted scope instead of inventing passing behavior.
|
|
13
13
|
- Run focused checks with permitted execution tools. With file tools only, inspect imports, contracts and fixtures and say tests were not run. Read-back or static inspection is not a passing test.
|
|
14
|
-
- Finish requested edits and read-back before the final reply; reserve the last available model request for a concise terminal account. For large files, write a small complete section, await its result, then extend it with a unique edit anchor rather than rewriting the growing file. Report delivery, actual checks and limitations once.`;
|
|
14
|
+
- Finish requested edits and read-back before the final reply; reserve the last available model request for a concise terminal account. If an independent review allocation is announced, finish the author deliverable within the author allocation: the review must inspect current files, repair gaps and report within the same total budget. Do not spend the review allocation on additional author discovery. For large files, write a small complete section, await its result, then extend it with a unique edit anchor rather than rewriting the growing file. Report delivery, actual checks and limitations once.`;
|
|
15
15
|
|
|
16
16
|
export const HERMETIC_CODING_SYSTEM_PROMPT = `You are a hermetic coding assistant. The exposed file tools are the complete tool surface. Inspect and mutate only the exact paths authorized by the user request. Do not invoke hidden tools, commands, language servers or other execution paths.
|
|
17
17
|
|
|
@@ -267,11 +267,9 @@ export class ReadFileLoopGuard {
|
|
|
267
267
|
const covered = entry.spans.some(
|
|
268
268
|
([start, end]) => start <= span.start && end >= span.end,
|
|
269
269
|
);
|
|
270
|
-
|
|
271
|
-
Number(args.offset) > 0 &&
|
|
272
|
-
|
|
273
|
-
Number(args.limit) <= 200 &&
|
|
274
|
-
!page.truncated;
|
|
270
|
+
const explicitRange =
|
|
271
|
+
Number(args.offset) > 0 && Number(args.limit) > 0 && !page.truncated;
|
|
272
|
+
let targeted = explicitRange;
|
|
275
273
|
const coveredPrefix = entry.spans.some(
|
|
276
274
|
([start, end]) => start <= span.start && end > span.start,
|
|
277
275
|
);
|
|
@@ -327,7 +325,7 @@ export class ReadFileLoopGuard {
|
|
|
327
325
|
if (restoring) page.readRecovery.fileVersion = fileVersion;
|
|
328
326
|
} else {
|
|
329
327
|
const unread = entry.spans[0]?.[0] === 0 ? entry.spans[0][1] : 0;
|
|
330
|
-
if (unread < span.total) {
|
|
328
|
+
if (unread < span.total && !explicitRange) {
|
|
331
329
|
const requestedRange = page.range;
|
|
332
330
|
page = continuePage(unread);
|
|
333
331
|
if (page.error || !page.readSpan) return page;
|
|
@@ -340,7 +338,12 @@ export class ReadFileLoopGuard {
|
|
|
340
338
|
} else {
|
|
341
339
|
const confirmedEnd =
|
|
342
340
|
entry.confirmedSpans[0]?.[0] === 0 ? entry.confirmedSpans[0][1] : 0;
|
|
343
|
-
if (
|
|
341
|
+
if (
|
|
342
|
+
this.visiblePages !== null &&
|
|
343
|
+
!entry.confirmedSpans.some(
|
|
344
|
+
([start, end]) => start <= span.start && end >= span.end,
|
|
345
|
+
)
|
|
346
|
+
) {
|
|
344
347
|
return {
|
|
345
348
|
path: filePath,
|
|
346
349
|
fileVersion,
|
|
@@ -358,7 +361,7 @@ export class ReadFileLoopGuard {
|
|
|
358
361
|
hashed: args.hashed === true,
|
|
359
362
|
alreadyRead: true,
|
|
360
363
|
contentOmitted: true,
|
|
361
|
-
reachedEnd:
|
|
364
|
+
reachedEnd: confirmedEnd >= span.total,
|
|
362
365
|
readProgress: { newContent: false },
|
|
363
366
|
readRecovery: { action: "use-findings" },
|
|
364
367
|
...(entry.summary?.outline
|
|
@@ -366,12 +369,12 @@ export class ReadFileLoopGuard {
|
|
|
366
369
|
: {}),
|
|
367
370
|
recoveryRead: {
|
|
368
371
|
path: filePath,
|
|
369
|
-
offset: 1,
|
|
372
|
+
offset: explicitRange ? page.range.startLine : 1,
|
|
370
373
|
limit: 80,
|
|
371
374
|
...(args.hashed === true ? { hashed: true } : {}),
|
|
372
375
|
...(args.raw === true ? { raw: true } : {}),
|
|
373
376
|
},
|
|
374
|
-
hint: "
|
|
377
|
+
hint: "This requested range was read earlier, but historical coverage does not mean its source is visible now. The runtime has not substituted an unrelated file range. Use source actually present in this request; recover missing details with a focused search or the bounded recoveryRead. Reading the file is not task completion.",
|
|
375
378
|
};
|
|
376
379
|
}
|
|
377
380
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { recoverCompleteSourceRead } from "./source-read-recovery.js";
|
|
2
|
+
|
|
1
3
|
/** Build a bounded read result with a cursor describing what was actually read. */
|
|
2
4
|
export function buildReadFilePage(
|
|
3
5
|
rendered,
|
|
@@ -61,7 +63,7 @@ export function buildReadFilePage(
|
|
|
61
63
|
const charEnd = truncated
|
|
62
64
|
? charStart + count
|
|
63
65
|
: lines.slice(0, end).reduce((n, line) => n + line.length + 1, 0);
|
|
64
|
-
|
|
66
|
+
const page = {
|
|
65
67
|
path: filePath,
|
|
66
68
|
...(fileVersion ? { fileVersion } : {}),
|
|
67
69
|
...(offset || limit || column > 1 || truncated
|
|
@@ -96,6 +98,7 @@ export function buildReadFilePage(
|
|
|
96
98
|
hashed: args.hashed === true,
|
|
97
99
|
content,
|
|
98
100
|
};
|
|
101
|
+
return recoverCompleteSourceRead(page, rendered, args, maxChars);
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
/** A bounded navigation index, not a substitute for reading the cited lines. */
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { parse } from "acorn";
|
|
2
|
+
import { canProjectHostToolExcerpt } from "./tool-result-preview.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A cut source container is not safe evidence. Recover its complete top-level
|
|
6
|
+
* statements from the very same read snapshot, with explicit range metadata.
|
|
7
|
+
* This does not exempt source from either projection or provider sanitization.
|
|
8
|
+
*/
|
|
9
|
+
export function recoverCompleteSourceRead(page, source, args, maxChars) {
|
|
10
|
+
if (
|
|
11
|
+
typeof page?.content !== "string" ||
|
|
12
|
+
typeof page.fileVersion !== "string" ||
|
|
13
|
+
!/\.(?:[cm]?js)$/iu.test(page.path || "") ||
|
|
14
|
+
args.hashed === true ||
|
|
15
|
+
!page.range ||
|
|
16
|
+
!page.readSpan ||
|
|
17
|
+
source.length > 512 * 1024 ||
|
|
18
|
+
canProjectHostToolExcerpt(page.content, "read_file")
|
|
19
|
+
)
|
|
20
|
+
return page;
|
|
21
|
+
|
|
22
|
+
let program;
|
|
23
|
+
let tokens = 0;
|
|
24
|
+
try {
|
|
25
|
+
program = parse(source, {
|
|
26
|
+
ecmaVersion: "latest",
|
|
27
|
+
sourceType: "module",
|
|
28
|
+
allowReturnOutsideFunction: true,
|
|
29
|
+
onToken() {
|
|
30
|
+
if (++tokens > 32768)
|
|
31
|
+
throw new RangeError("Source recovery token bound");
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
} catch {
|
|
35
|
+
// Malformed or oversized source retains the ordinary omission boundary.
|
|
36
|
+
return page;
|
|
37
|
+
}
|
|
38
|
+
const statements = program.body.filter(
|
|
39
|
+
(node) => node.start < page.readSpan.end && node.end > page.readSpan.start,
|
|
40
|
+
);
|
|
41
|
+
if (!statements.length) return page;
|
|
42
|
+
const start = Math.min(
|
|
43
|
+
page.readSpan.start,
|
|
44
|
+
source.lastIndexOf("\n", statements[0].start - 1) + 1,
|
|
45
|
+
);
|
|
46
|
+
const followingNewline = source.indexOf("\n", statements.at(-1).end);
|
|
47
|
+
const end = Math.max(
|
|
48
|
+
page.readSpan.end,
|
|
49
|
+
followingNewline < 0 ? source.length : followingNewline + 1,
|
|
50
|
+
);
|
|
51
|
+
const content = source.slice(start, end);
|
|
52
|
+
if (
|
|
53
|
+
content === page.content ||
|
|
54
|
+
!canProjectHostToolExcerpt(content, "read_file")
|
|
55
|
+
)
|
|
56
|
+
return page;
|
|
57
|
+
const startLine = 1 + (source.slice(0, start).match(/\n/gu) || []).length;
|
|
58
|
+
const endLine =
|
|
59
|
+
startLine +
|
|
60
|
+
(content.match(/\n/gu) || []).length -
|
|
61
|
+
(content.endsWith("\n") ? 1 : 0);
|
|
62
|
+
const recovered = {
|
|
63
|
+
...page,
|
|
64
|
+
content,
|
|
65
|
+
range: { startLine, endLine, totalLines: page.range.totalLines },
|
|
66
|
+
readSpan: { start, end, total: source.length },
|
|
67
|
+
sourceRecovery: {
|
|
68
|
+
action: "complete-source-container",
|
|
69
|
+
fileVersion: page.fileVersion,
|
|
70
|
+
requestedRange: { ...page.range },
|
|
71
|
+
requestedSpan: { ...page.readSpan },
|
|
72
|
+
note: "The requested excerpt cut a source container. These complete surrounding statements come from the same file version; the range and readSpan describe the content actually returned.",
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
delete recovered.truncated;
|
|
76
|
+
delete recovered.size;
|
|
77
|
+
delete recovered.nextRead;
|
|
78
|
+
delete recovered.hint;
|
|
79
|
+
if (end < source.length) {
|
|
80
|
+
recovered.nextRead = { ...args, offset: endLine + 1, column: 1 };
|
|
81
|
+
recovered.hint = "Continue with nextRead only if more source is needed.";
|
|
82
|
+
}
|
|
83
|
+
// The read guard, host telemetry and delivery envelope still add metadata.
|
|
84
|
+
// Reuse the ordinary page's headroom: an almost-full recovery would be
|
|
85
|
+
// projected back to an omission by the final cap before reaching a provider.
|
|
86
|
+
return JSON.stringify(recovered).length <= Math.max(0, maxChars - 2048)
|
|
87
|
+
? recovered
|
|
88
|
+
: page;
|
|
89
|
+
}
|
|
@@ -33,7 +33,7 @@ const PREVIEW_HINT =
|
|
|
33
33
|
"Tool output truncated. Narrow the request: filter/count locally, select fields, search_files, or read_file with offset/limit. Omitted text is not full-output evidence.";
|
|
34
34
|
|
|
35
35
|
/** Check only host-created excerpts; original uncut tool text still reaches ingress. */
|
|
36
|
-
function
|
|
36
|
+
export function canProjectHostToolExcerpt(text, tool) {
|
|
37
37
|
const scan = createEvidenceJsonTextBoundary((message) => new Error(message), {
|
|
38
38
|
...EVIDENCE_JSON_POLICY,
|
|
39
39
|
sourceLiteralPolicy: "explicit-source-containers-v1",
|
|
@@ -239,7 +239,8 @@ export function safeHostToolResultForModel(tool, result) {
|
|
|
239
239
|
let safe = result;
|
|
240
240
|
for (const field of fields) {
|
|
241
241
|
const text = result[field];
|
|
242
|
-
if (typeof text !== "string" ||
|
|
242
|
+
if (typeof text !== "string" || canProjectHostToolExcerpt(text, tool))
|
|
243
|
+
continue;
|
|
243
244
|
if (safe === result) safe = { ...result };
|
|
244
245
|
safe[field] =
|
|
245
246
|
tool === "read_file"
|
|
@@ -268,7 +269,10 @@ export function boundedCommandOutput(output, max, tool = "run_code") {
|
|
|
268
269
|
output,
|
|
269
270
|
Math.max(1, Math.floor(max / 2)),
|
|
270
271
|
);
|
|
271
|
-
if (
|
|
272
|
+
if (
|
|
273
|
+
JSON.stringify(preview).length <= max &&
|
|
274
|
+
canProjectHostToolExcerpt(preview, tool)
|
|
275
|
+
)
|
|
272
276
|
return preview;
|
|
273
277
|
} else if (!/[{}\[\]"']/u.test(output)) {
|
|
274
278
|
let low = 0;
|
|
@@ -77,7 +77,12 @@ import {
|
|
|
77
77
|
CODING_TASK_QUALITY_GUIDANCE,
|
|
78
78
|
CODING_CHANGE_REVIEW_PROMPT,
|
|
79
79
|
} from "../lib/coding-task-guidance.js";
|
|
80
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
CodingChangeReviewContext,
|
|
82
|
+
changeReviewBudgetNotice,
|
|
83
|
+
changeReviewBudgetInsufficientError,
|
|
84
|
+
canReserveChangeReview,
|
|
85
|
+
} from "../lib/coding-change-review-context.js";
|
|
81
86
|
import { RemoteReadLoopGuard } from "../lib/remote-read-loop-guard.js";
|
|
82
87
|
import {
|
|
83
88
|
isPrActionRequest,
|
|
@@ -15608,6 +15613,37 @@ export async function* agentLoop(messages, options) {
|
|
|
15608
15613
|
toolContext.readFileLoopGuard = readFileLoopGuard;
|
|
15609
15614
|
let lastTaskProgressIntervention = null;
|
|
15610
15615
|
|
|
15616
|
+
// Both an author completion and the reserved review window enter the same
|
|
15617
|
+
// phase. Neither path manufactures an author response or adds request budget.
|
|
15618
|
+
const beginChangeReview = (draft = null) => {
|
|
15619
|
+
throwIfAborted(signal);
|
|
15620
|
+
if (budget.remaining() < 2)
|
|
15621
|
+
throw changeReviewBudgetInsufficientError(budget.remaining());
|
|
15622
|
+
if (independentReviewSetupError) throw independentReviewSetupError;
|
|
15623
|
+
changeReviewState.requested = true;
|
|
15624
|
+
const reviewPrompt = markRuntimeOnlySessionMessage({
|
|
15625
|
+
role: "user",
|
|
15626
|
+
content: CODING_CHANGE_REVIEW_PROMPT,
|
|
15627
|
+
});
|
|
15628
|
+
if (draft)
|
|
15629
|
+
appendMessages(
|
|
15630
|
+
markRuntimeOnlySessionMessage({
|
|
15631
|
+
role: "assistant",
|
|
15632
|
+
content: draft.content || "",
|
|
15633
|
+
}),
|
|
15634
|
+
);
|
|
15635
|
+
appendMessages(reviewPrompt);
|
|
15636
|
+
if (independentReviewContext) {
|
|
15637
|
+
independentReviewContext.activate(reviewPrompt);
|
|
15638
|
+
changeReviewState.independentReviewPending = true;
|
|
15639
|
+
readFileLoopGuard = new ReadFileLoopGuard();
|
|
15640
|
+
remoteReadLoopGuard = new RemoteReadLoopGuard();
|
|
15641
|
+
taskProgressTracker = new TaskProgressTracker();
|
|
15642
|
+
toolContext.readFileLoopGuard = readFileLoopGuard;
|
|
15643
|
+
lastTaskProgressIntervention = null;
|
|
15644
|
+
}
|
|
15645
|
+
};
|
|
15646
|
+
|
|
15611
15647
|
while (budget.hasRemaining()) {
|
|
15612
15648
|
if (loopMcpCatalog) {
|
|
15613
15649
|
currentMcpSnapshot = readMcpToolCatalog(loopMcpCatalog);
|
|
@@ -15654,6 +15690,24 @@ export async function* agentLoop(messages, options) {
|
|
|
15654
15690
|
return;
|
|
15655
15691
|
}
|
|
15656
15692
|
readFileLoopGuard.finishBatch();
|
|
15693
|
+
if (
|
|
15694
|
+
canReserveChangeReview({
|
|
15695
|
+
enabled: options.reviewChanges,
|
|
15696
|
+
changed: hasFileContentChanges,
|
|
15697
|
+
requested: changeReviewState.requested,
|
|
15698
|
+
usageKnown: changeReviewState.usageKnown,
|
|
15699
|
+
pendingMutations: pendingSubtreeMutations.size,
|
|
15700
|
+
pendingChildren: backgroundSubAgents.size,
|
|
15701
|
+
remaining: budget.remaining(),
|
|
15702
|
+
limit: budget.limit,
|
|
15703
|
+
})
|
|
15704
|
+
) {
|
|
15705
|
+
beginChangeReview();
|
|
15706
|
+
yield {
|
|
15707
|
+
type: "iteration-warning",
|
|
15708
|
+
message: "Reviewing changed files against the task before finishing.",
|
|
15709
|
+
};
|
|
15710
|
+
}
|
|
15657
15711
|
if (remoteReadLoopGuard.stalled && !remoteReadLoopGuard.synthesisRequired) {
|
|
15658
15712
|
await _awaitBackgroundUsageSettlement(
|
|
15659
15713
|
backgroundSubAgents,
|
|
@@ -16585,7 +16639,10 @@ export async function* agentLoop(messages, options) {
|
|
|
16585
16639
|
`Execution budget: this request plus ${furtherRequests} further model requests. ` +
|
|
16586
16640
|
"For requested changes, reserve requests for implementation, focused validation, and repair or an accurate unfinished-work report. " +
|
|
16587
16641
|
"Reading or an existing passing baseline does not deliver requested changes. " +
|
|
16588
|
-
"For research or monitoring, use the remaining requests for evidence and the requested answer; do not create files just to show activity."
|
|
16642
|
+
"For research or monitoring, use the remaining requests for evidence and the requested answer; do not create files just to show activity." +
|
|
16643
|
+
(options.reviewChanges === true
|
|
16644
|
+
? changeReviewBudgetNotice(budget, changeReviewState.requested)
|
|
16645
|
+
: ""),
|
|
16589
16646
|
},
|
|
16590
16647
|
];
|
|
16591
16648
|
contextMemoryTrustedSystemIndexes.push(callMessages.length - 1);
|
|
@@ -16988,7 +17045,7 @@ export async function* agentLoop(messages, options) {
|
|
|
16988
17045
|
}
|
|
16989
17046
|
throwIfAborted(signal);
|
|
16990
17047
|
if (
|
|
16991
|
-
|
|
17048
|
+
options.reviewChanges === true &&
|
|
16992
17049
|
changeReviewState.usageKnown === false
|
|
16993
17050
|
) {
|
|
16994
17051
|
const error = new Error(
|
|
@@ -17247,42 +17304,14 @@ export async function* agentLoop(messages, options) {
|
|
|
17247
17304
|
};
|
|
17248
17305
|
return;
|
|
17249
17306
|
}
|
|
17250
|
-
//
|
|
17251
|
-
// Never emit a provisional completion or turn this self-check into an
|
|
17252
|
-
// acceptance verdict. Failed/deferred writes do not arm the review.
|
|
17307
|
+
// Author completion is provisional until the independent review phase.
|
|
17253
17308
|
if (
|
|
17254
17309
|
options.reviewChanges === true &&
|
|
17255
17310
|
hasFileContentChanges &&
|
|
17256
17311
|
!changeReviewState.requested &&
|
|
17257
|
-
changeReviewState.usageKnown !== false
|
|
17258
|
-
budget.hasRemaining()
|
|
17312
|
+
changeReviewState.usageKnown !== false
|
|
17259
17313
|
) {
|
|
17260
|
-
|
|
17261
|
-
if (independentReviewSetupError) throw independentReviewSetupError;
|
|
17262
|
-
changeReviewState.requested = true;
|
|
17263
|
-
const reviewPrompt = markRuntimeOnlySessionMessage({
|
|
17264
|
-
role: "user",
|
|
17265
|
-
content: CODING_CHANGE_REVIEW_PROMPT,
|
|
17266
|
-
});
|
|
17267
|
-
appendMessages(
|
|
17268
|
-
markRuntimeOnlySessionMessage({
|
|
17269
|
-
role: "assistant",
|
|
17270
|
-
content: msg.content || "",
|
|
17271
|
-
}),
|
|
17272
|
-
reviewPrompt,
|
|
17273
|
-
);
|
|
17274
|
-
if (independentReviewContext) {
|
|
17275
|
-
independentReviewContext.activate(reviewPrompt);
|
|
17276
|
-
changeReviewState.independentReviewPending = true;
|
|
17277
|
-
// These are phase-local discovery hints, not permission or spend
|
|
17278
|
-
// authorities. Fresh inspection must not inherit author assertions,
|
|
17279
|
-
// old visible file pages, or a pause caused by author exploration.
|
|
17280
|
-
readFileLoopGuard = new ReadFileLoopGuard();
|
|
17281
|
-
remoteReadLoopGuard = new RemoteReadLoopGuard();
|
|
17282
|
-
taskProgressTracker = new TaskProgressTracker();
|
|
17283
|
-
toolContext.readFileLoopGuard = readFileLoopGuard;
|
|
17284
|
-
lastTaskProgressIntervention = null;
|
|
17285
|
-
}
|
|
17314
|
+
beginChangeReview(msg);
|
|
17286
17315
|
yield {
|
|
17287
17316
|
type: "iteration-warning",
|
|
17288
17317
|
message: "Reviewing changed files against the task before finishing.",
|