chainlesschain 0.166.140 → 0.166.142
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 +161 -13
- package/package.json +2 -2
- 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/goal.js +161 -0
- package/src/data/changelog.json +26 -0
- package/src/lib/evolution/evolution-artifact-ports.js +64 -1
- package/src/lib/plugin-runtime/cli-goal-capabilities.js +28 -0
- package/src/lib/plugin-runtime/cli-goal-draft-request.js +2 -6
- package/src/lib/plugin-runtime/cli-goal-drafts.js +2 -6
- package/src/lib/plugin-runtime/cli-goal-event-history.js +284 -0
- package/src/lib/plugin-runtime/cli-goal-events.js +175 -0
- package/src/lib/plugin-runtime/cli-goal-extension-host.js +21 -13
- package/src/lib/plugin-runtime/cli-project-goal-events.js +212 -0
- package/src/lib/plugin-runtime/examples/goal-project-observer/plugin.json +20 -0
- package/src/lib/plugin-runtime/examples/goal-status-observer/plugin.json +17 -0
- package/src/lib/plugin-runtime/examples/project-delivery-assistant/README.md +92 -0
- package/src/lib/plugin-runtime/examples/project-delivery-assistant/plugin.json +6 -7
- package/src/lib/plugin-runtime/goal-extensions.js +16 -0
- package/src/lib/project-goal-connection.js +111 -27
- package/src/lib/read-file-loop-guard.js +46 -11
- package/src/lib/tool-result-preview.js +1 -0
- package/src/runtime/agent-core.js +58 -0
|
@@ -26,6 +26,13 @@ function mergeSpan(spans, span) {
|
|
|
26
26
|
return merged;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function rereadFingerprint(span, restoring) {
|
|
30
|
+
return (
|
|
31
|
+
(restoring ? "restore:" : "review:") +
|
|
32
|
+
createHash("sha256").update(JSON.stringify(span)).digest("hex")
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
/** Per-run read coverage and recovery, independent of compacted transcripts. */
|
|
30
37
|
export class ReadFileLoopGuard {
|
|
31
38
|
constructor() {
|
|
@@ -309,9 +316,7 @@ export class ReadFileLoopGuard {
|
|
|
309
316
|
this.visiblePages !== null &&
|
|
310
317
|
entry.lastReadRequest < this.visibleRequest &&
|
|
311
318
|
!this.isPageVisible(page);
|
|
312
|
-
const fingerprint =
|
|
313
|
-
(restoring ? "restore:" : "review:") +
|
|
314
|
-
createHash("sha256").update(JSON.stringify(span)).digest("hex");
|
|
319
|
+
const fingerprint = rereadFingerprint(span, restoring);
|
|
315
320
|
if (
|
|
316
321
|
targeted &&
|
|
317
322
|
!entry.rereads.has(fingerprint) &&
|
|
@@ -354,6 +359,40 @@ export class ReadFileLoopGuard {
|
|
|
354
359
|
hint: "This range has a read pending in the current tool batch. Historical source coverage is recorded only after content reaches the model.",
|
|
355
360
|
};
|
|
356
361
|
}
|
|
362
|
+
const recoveryRead = {
|
|
363
|
+
path: filePath,
|
|
364
|
+
offset: explicitRange ? page.range.startLine : 1,
|
|
365
|
+
limit: 80,
|
|
366
|
+
...(args.hashed === true ? { hashed: true } : {}),
|
|
367
|
+
...(args.raw === true ? { raw: true } : {}),
|
|
368
|
+
};
|
|
369
|
+
const recoveryPage = readPage(recoveryRead);
|
|
370
|
+
const validRecoveryPage =
|
|
371
|
+
!recoveryPage.error &&
|
|
372
|
+
!!recoveryPage.readSpan &&
|
|
373
|
+
!recoveryPage.truncated;
|
|
374
|
+
const recoveryCovered =
|
|
375
|
+
validRecoveryPage &&
|
|
376
|
+
entry.spans.some(
|
|
377
|
+
([start, end]) =>
|
|
378
|
+
start <= recoveryPage.readSpan.start &&
|
|
379
|
+
end >= recoveryPage.readSpan.end,
|
|
380
|
+
);
|
|
381
|
+
const recoveryRestoring =
|
|
382
|
+
validRecoveryPage &&
|
|
383
|
+
this.visiblePages !== null &&
|
|
384
|
+
entry.lastReadRequest < this.visibleRequest &&
|
|
385
|
+
!this.isPageVisible(recoveryPage);
|
|
386
|
+
// A recovery hint is a contract. Do not prescribe a read that this
|
|
387
|
+
// same guard would immediately omit again. This checks a pure page
|
|
388
|
+
// from the already-authorized file without minting a new allowance.
|
|
389
|
+
const canRecover =
|
|
390
|
+
validRecoveryPage &&
|
|
391
|
+
(!recoveryCovered ||
|
|
392
|
+
(entry.rereads.size < 3 &&
|
|
393
|
+
!entry.rereads.has(
|
|
394
|
+
rereadFingerprint(recoveryPage.readSpan, recoveryRestoring),
|
|
395
|
+
)));
|
|
357
396
|
return {
|
|
358
397
|
path: filePath,
|
|
359
398
|
fileVersion,
|
|
@@ -367,14 +406,10 @@ export class ReadFileLoopGuard {
|
|
|
367
406
|
...(entry.summary?.outline
|
|
368
407
|
? { outline: entry.summary.outline }
|
|
369
408
|
: {}),
|
|
370
|
-
recoveryRead: {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
...(args.hashed === true ? { hashed: true } : {}),
|
|
375
|
-
...(args.raw === true ? { raw: true } : {}),
|
|
376
|
-
},
|
|
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.",
|
|
409
|
+
...(canRecover ? { recoveryRead } : {}),
|
|
410
|
+
hint: canRecover
|
|
411
|
+
? "This requested range was read earlier, but historical coverage does not mean its source is visible now. Recover missing details with a focused search or the bounded recoveryRead. Reading the file is not task completion."
|
|
412
|
+
: "This unchanged range cannot be reread within the remaining read allowance. Do not repeat it. Use source still visible in this request, or search_files for a precise symbol (set regex:true when using a regular expression). If required source is unavailable, report the limitation instead of guessing APIs.",
|
|
378
413
|
};
|
|
379
414
|
}
|
|
380
415
|
}
|
|
@@ -36,6 +36,7 @@ const PREVIEW_HINT =
|
|
|
36
36
|
export function canProjectHostToolExcerpt(text, tool) {
|
|
37
37
|
const scan = createEvidenceJsonTextBoundary((message) => new Error(message), {
|
|
38
38
|
...EVIDENCE_JSON_POLICY,
|
|
39
|
+
sourceExpressionPolicy: "bounded-source-tokens-v1",
|
|
39
40
|
sourceLiteralPolicy: "explicit-source-containers-v1",
|
|
40
41
|
maxSourceTokens: 32768,
|
|
41
42
|
});
|
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
requireUnscopedToolExecution,
|
|
8
8
|
} from "../lib/formal-budget-provider-scope.js";
|
|
9
9
|
import { isFormalBudgetError } from "../lib/formal-budget-errors.js";
|
|
10
|
+
import {
|
|
11
|
+
FORMAL_QUALITY_ALLOWED_FILES_ENV,
|
|
12
|
+
FORMAL_QUALITY_FILE_TOOLS,
|
|
13
|
+
formalQualityAllowedFiles,
|
|
14
|
+
isFormalQualityHermeticRuntime,
|
|
15
|
+
} from "../lib/formal-quality-eval-runtime.js";
|
|
10
16
|
import { normalizeOllamaBaseUrl } from "../lib/ollama-base-url.js";
|
|
11
17
|
import { ollamaProviderFetch } from "../lib/ollama-provider-fetch.js";
|
|
12
18
|
import {
|
|
@@ -14987,7 +14993,59 @@ function permissionDecision(callId, tool, result) {
|
|
|
14987
14993
|
});
|
|
14988
14994
|
}
|
|
14989
14995
|
|
|
14996
|
+
function bindFormalQualityLoopOptions(options) {
|
|
14997
|
+
// The launcher owns this binding. Apply it at the shared loop so direct
|
|
14998
|
+
// gateway/IDE callers receive the same schema and execution guard as CLI.
|
|
14999
|
+
// Legacy hermetic callers without a file binding retain their own boundary.
|
|
15000
|
+
if (
|
|
15001
|
+
!isFormalQualityHermeticRuntime(process.env) ||
|
|
15002
|
+
!Object.hasOwn(process.env, FORMAL_QUALITY_ALLOWED_FILES_ENV)
|
|
15003
|
+
) {
|
|
15004
|
+
return options;
|
|
15005
|
+
}
|
|
15006
|
+
const allowedPaths = formalQualityAllowedFiles(process.env);
|
|
15007
|
+
const cwd = options.cwd || process.cwd();
|
|
15008
|
+
const launcherScope = normalizeExactFileMutationScope(
|
|
15009
|
+
{ exact: true, worktreeRoot: canonicalRealpath(cwd), allowedPaths },
|
|
15010
|
+
{ cwd },
|
|
15011
|
+
);
|
|
15012
|
+
const callerScope = normalizeExactFileMutationScope(
|
|
15013
|
+
options.fileMutationScope,
|
|
15014
|
+
{
|
|
15015
|
+
cwd,
|
|
15016
|
+
},
|
|
15017
|
+
);
|
|
15018
|
+
if (
|
|
15019
|
+
callerScope &&
|
|
15020
|
+
callerScope.allowedPaths.some((file) => !allowedPaths.includes(file))
|
|
15021
|
+
) {
|
|
15022
|
+
throw new Error("mutation scope widens the formal quality file binding");
|
|
15023
|
+
}
|
|
15024
|
+
if (
|
|
15025
|
+
options.enabledToolNames != null &&
|
|
15026
|
+
!Array.isArray(options.enabledToolNames)
|
|
15027
|
+
) {
|
|
15028
|
+
throw new Error("formal quality enabled tool names must be an array");
|
|
15029
|
+
}
|
|
15030
|
+
return {
|
|
15031
|
+
...options,
|
|
15032
|
+
fileMutationScope: callerScope || launcherScope,
|
|
15033
|
+
hermeticExecution: true,
|
|
15034
|
+
exactToolNames: true,
|
|
15035
|
+
// Hermetic execution ignores host executors. Match that on the provider
|
|
15036
|
+
// side too: a same-named host executor must not suppress the path enum
|
|
15037
|
+
// for the built-in mutation tool that will actually execute.
|
|
15038
|
+
externalToolExecutors: null,
|
|
15039
|
+
enabledToolNames: Object.freeze(
|
|
15040
|
+
(options.enabledToolNames ?? FORMAL_QUALITY_FILE_TOOLS).filter((name) =>
|
|
15041
|
+
FORMAL_QUALITY_FILE_TOOLS.includes(name),
|
|
15042
|
+
),
|
|
15043
|
+
),
|
|
15044
|
+
};
|
|
15045
|
+
}
|
|
15046
|
+
|
|
14990
15047
|
export async function* agentLoop(messages, options) {
|
|
15048
|
+
options = bindFormalQualityLoopOptions(options);
|
|
14991
15049
|
// Bind the actual task before compaction, runtime nudges or provider projection.
|
|
14992
15050
|
// A new host-admitted user turn starts a new loop and receives its own binding.
|
|
14993
15051
|
const currentUserTask = messages.findLast(
|