@sema-agent/core 2.12.0 → 3.0.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/dist/agents/agent-transcript-tool.d.ts +1 -0
- package/dist/agents/agent-transcript-tool.js +1 -1
- package/dist/agents/cascade.d.ts +4 -0
- package/dist/agents/cascade.js +54 -9
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +101 -2
- package/dist/agents/retain-ledger.d.ts +18 -2
- package/dist/agents/retain-ledger.js +37 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +22 -1
- package/dist/agents/teacher.js +4 -2
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +36 -1
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/circuit-breaker.d.ts +1 -0
- package/dist/brain/circuit-breaker.js +26 -9
- package/dist/brain/degrading.js +13 -1
- package/dist/brain/failover.js +2 -0
- package/dist/brain/repetition.js +10 -1
- package/dist/brain/routing.js +5 -1
- package/dist/brain/status-sink.js +5 -1
- package/dist/brain/stream-engine.js +4 -1
- package/dist/brain/tool-call-repair.js +32 -14
- package/dist/core/auto-compaction.js +17 -7
- package/dist/core/auto-promote.js +2 -1
- package/dist/core/checkpoint-store.d.ts +1 -0
- package/dist/core/checkpoint-store.js +28 -10
- package/dist/core/consolidate-scope.js +4 -3
- package/dist/core/context-edit.js +3 -0
- package/dist/core/context-guard.js +3 -0
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +17 -1
- package/dist/core/hooks.d.ts +1 -0
- package/dist/core/hooks.js +40 -2
- package/dist/core/lsp-diagnostics.js +13 -7
- package/dist/core/lsp-protocol.d.ts +1 -1
- package/dist/core/lsp-protocol.js +20 -8
- package/dist/core/lsp-session.d.ts +12 -2
- package/dist/core/lsp-session.js +114 -49
- package/dist/core/lsp.d.ts +13 -1
- package/dist/core/lsp.js +64 -9
- package/dist/core/mcp.d.ts +1 -0
- package/dist/core/mcp.js +4 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -1
- package/dist/core/memory-engine/engine.js +82 -7
- package/dist/core/memory-engine/file-backend.d.ts +1 -0
- package/dist/core/memory-engine/file-backend.js +13 -3
- package/dist/core/memory-engine/layout.js +12 -1
- package/dist/core/memory.js +3 -0
- package/dist/core/permission-rules.d.ts +2 -1
- package/dist/core/permission-rules.js +24 -3
- package/dist/core/runner/active-skill-scope.js +34 -6
- package/dist/core/runner/assemble-result.d.ts +1 -1
- package/dist/core/runner/assemble-result.js +0 -1
- package/dist/core/runner/prepare-task.js +23 -3
- package/dist/core/runner/runtask.js +48 -30
- package/dist/core/runner/tool-disclosure.d.ts +1 -0
- package/dist/core/runner/tool-disclosure.js +26 -7
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +9 -3
- package/dist/core/session-store.js +15 -4
- package/dist/core/skill-tool-specifier.d.ts +8 -0
- package/dist/core/skill-tool-specifier.js +58 -0
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/skills-directory.js +16 -4
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/task-registry-monitor.js +7 -1
- package/dist/core/tool-policy.js +65 -17
- package/dist/core/trace.d.ts +1 -0
- package/dist/core/types.d.ts +3 -5
- package/dist/core/with-retry.js +0 -1
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/lsp/frame-decoder.js +6 -3
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +103 -26
- package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
- package/dist/index.d.ts +1 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/workflow-governance.js +43 -9
- package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
- package/dist/orchestration/workflow-script-store.js +34 -4
- package/dist/orchestration/workflow.js +10 -6
- package/dist/prompt-assembly/artifact-store.d.ts +2 -0
- package/dist/prompt-assembly/artifact-store.js +39 -24
- package/dist/prompt-assembly/assemble.js +38 -7
- package/dist/prompt-assembly/epoch.js +19 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/explain.js +1 -1
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/fs-atomic.d.ts +3 -0
- package/dist/stores/file/fs-atomic.js +53 -8
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +7 -0
- package/dist/stores/file/session-store.d.ts +2 -0
- package/dist/stores/file/session-store.js +28 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
- package/dist/tools/fs/bash-readonly-classifier.js +57 -8
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +93 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.js +55 -9
- package/dist/tools/scheduler-tools.js +9 -3
- package/dist/tools/web.d.ts +8 -2
- package/dist/tools/web.js +46 -17
- package/dist/tools/worktree.d.ts +2 -0
- package/dist/tools/worktree.js +233 -143
- package/package.json +5 -2
package/dist/agents/verify.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type, type Static } from "typebox";
|
|
2
2
|
import type { Runner, ResumeTaskConfig } from "../core/runner/runtask.js";
|
|
3
|
-
import type { CheckpointToken, ResumeOutcome } from "../core/checkpoint-store.js";
|
|
3
|
+
import type { CheckpointGate, CheckpointToken, ResumeOutcome } from "../core/checkpoint-store.js";
|
|
4
4
|
import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js";
|
|
5
5
|
export declare const VERIFICATION_PROMPT = "You are a verification specialist. Your job is NOT to confirm the implementation works \u2014 it is to try to BREAK it.\n\nYou have two documented failure patterns. First, verification avoidance: faced with a check, you find reasons not to run it \u2014 you read code, narrate what you would test, declare \"PASS,\" and move on. Second, being seduced by the first 80%: a polished result or a passing test suite makes you inclined to pass it, not noticing the edge that crashes, the state that vanishes, the bad input that is unhandled. The first 80% is the easy part. Your entire value is in finding the last 20%.\n\n## Hard boundary \u2014 do not modify the project\nYou are STRICTLY a verifier. Do NOT create, modify, or delete project files; do NOT install packages; do NOT run version-control write operations. Use only the read/probe/execute tools available to you. (If you need a scratch file, use a temp directory, and clean up.)\n\n## Evidence is mandatory\nReading code is NOT verification. Every check must actually run something \u2014 execute the code, hit the endpoint, run the build/tests \u2014 and record the command and its real output. A \"PASS\" with no command output is a skip, not a pass.\n\n## Strategy (adapt to what changed)\n- Build/lib changes: build it, run the full test suite, exercise the public API as a consumer would.\n- Backend/API: start it, call endpoints, check response *shapes* (not just status codes), test error paths.\n- CLI/script: run with representative AND edge inputs (empty, malformed, boundary); check stdout/stderr/exit codes.\n- Bug fix: reproduce the original bug first, verify the fix, then check for regressions and side effects.\n- Refactor (no behavior change): the existing suite must pass unchanged; diff the public surface; same inputs \u2192 same outputs.\nRun the project's own build/tests/linters as a baseline, then apply the type-specific checks. Test results are context, not proof \u2014 the implementer is an LLM too; its tests may be happy-path or circular.\n\n## Adversarial probes (pick the ones that fit)\nBoundary values (0, -1, empty, very long, unicode, max), idempotency (same mutating call twice), orphan operations (ids that don't exist), concurrency (parallel create-if-not-exists). Your verdict must include at least one adversarial probe you actually ran and its result \u2014 even if it was handled correctly.\n\n## Before you FAIL\nCheck you haven't missed why it's actually fine: defensive code elsewhere, intentional behavior documented in comments/specs, or an unfixable external-contract limitation (note that as an observation, not a FAIL). Don't wave away real issues, but don't FAIL on intentional behavior.\n\n## Verdict\nSubmit exactly one verdict via the provided output tool:\n- PASS \u2014 you ran real checks (including \u22651 adversarial probe) and it holds up. Put the commands + observed output in `evidence`.\n- FAIL \u2014 something is broken. Put each concrete problem (with how to reproduce) in `findings`.\n- PARTIAL \u2014 environmental limitation only (no test framework, a tool/server unavailable). Not for \"I'm unsure\": if you can run the check, decide PASS or FAIL. Note what you couldn't verify and why in `findings`.";
|
|
6
6
|
export declare const STATIC_VERIFICATION_PROMPT = "You are a verification judge. Your job is NOT to confirm the change works \u2014 it is to find where it BREAKS.\n\nYou are READ-ONLY by design: the build and tests have ALREADY been run by a separate mechanical gate. Their results and the code change (a diff) are usually supplied to you as evidence; but if little or no diff/results are supplied this round (e.g. a re-verification AFTER a fix), judge the CURRENT working tree directly \u2014 do NOT return PARTIAL merely because a diff is absent. Do NOT try to execute code, run tests, or invoke a runtime \u2014 the environment will refuse it, and that is expected, not a limitation. Judge from any supplied diff/results plus read-only inspection of the working tree (read files, search, list).\n\nYou have two documented failure patterns. First, being seduced by the first 80%: a clean diff or a green test run makes you inclined to pass it, not noticing the edge that crashes, the state that vanishes, the bad input that is unhandled, the cross-module assumption that breaks. Second, hiding behind PARTIAL because you couldn't run something \u2014 that is NOT what PARTIAL is for here; execution was the mechanical gate's job. Your entire value is finding the last 20% by READING.\n\n## What to scrutinize (adapt to the diff)\n- Boundary/edge cases the tests likely miss: 0, -1, empty, very long, unicode, max, malformed input, idempotency, orphan ids, off-by-one, negative numbers, EOF/empty fields.\n- Semantic correctness vs the task spec: does the change actually do what was asked, including cases the tests don't cover (the title()/CSV/base62 class of defect)?\n- Cross-module/integration hazards in the diff: a changed signature/export/contract/default a caller elsewhere still assumes; a deleted helper something depends on.\n- If the provided test results show failures, that is a concrete FAIL with the failing output as evidence.\n\n## Verdict\nSubmit exactly one verdict via the provided output tool:\n- PASS \u2014 you read the diff + results, looked for the edges above, and it holds. Cite the specific things you checked in `evidence`.\n- FAIL \u2014 you found a concrete defect. Put each problem (with the diff location / input that breaks it) in `findings`.\n- PARTIAL \u2014 ONLY when the evidence itself is genuinely insufficient to judge (e.g. the diff is empty or unrelated to the task, no results supplied). NOT for \"I couldn't execute it.\" Say what's missing in `findings`.";
|
|
@@ -24,8 +24,15 @@ export interface VerifyConfig {
|
|
|
24
24
|
verdict: VerificationOutcome["verdict"];
|
|
25
25
|
findings: string[];
|
|
26
26
|
}) => void;
|
|
27
|
+
onVerifierPause?: (info: {
|
|
28
|
+
round: number;
|
|
29
|
+
status: TaskResult["status"];
|
|
30
|
+
sessionId: string;
|
|
31
|
+
checkpointToken?: CheckpointToken;
|
|
32
|
+
checkpointGate?: CheckpointGate;
|
|
33
|
+
}) => void;
|
|
27
34
|
}
|
|
28
|
-
export type UnverifiedReason = "suspended" | "needs_review" | "no_verdict" | "opted_out" | "impl_incomplete" | "could_not_verify";
|
|
35
|
+
export type UnverifiedReason = "suspended" | "needs_review" | "no_verdict" | "opted_out" | "impl_incomplete" | "could_not_verify" | "verifier_paused";
|
|
29
36
|
export interface VerificationOutcome {
|
|
30
37
|
verdict: "PASS" | "FAIL" | "PARTIAL" | "unverified";
|
|
31
38
|
unverifiedReason?: UnverifiedReason;
|
|
@@ -33,6 +40,12 @@ export interface VerificationOutcome {
|
|
|
33
40
|
findings: string[];
|
|
34
41
|
evidence?: string;
|
|
35
42
|
verifierCost?: number;
|
|
43
|
+
verifierPause?: {
|
|
44
|
+
status: TaskResult["status"];
|
|
45
|
+
sessionId: string;
|
|
46
|
+
checkpointToken?: CheckpointToken;
|
|
47
|
+
checkpointGate?: CheckpointGate;
|
|
48
|
+
};
|
|
36
49
|
}
|
|
37
50
|
export interface VerificationResult extends TaskResult {
|
|
38
51
|
verification: VerificationOutcome;
|
package/dist/agents/verify.js
CHANGED
|
@@ -102,7 +102,8 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
102
102
|
signal: specBase.signal,
|
|
103
103
|
});
|
|
104
104
|
try {
|
|
105
|
-
|
|
105
|
+
const cost = (v.stats.costMicroUsd ?? 0) + (v.stats.nested?.costMicroUsd ?? 0);
|
|
106
|
+
return { verdict: v.structuredOutput, cost, ...(isDurablePause(v.status) ? { paused: v } : {}) };
|
|
106
107
|
}
|
|
107
108
|
finally {
|
|
108
109
|
if (!isDurablePause(v.status))
|
|
@@ -115,9 +116,43 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
115
116
|
let spend = 0;
|
|
116
117
|
let verifierSpend = 0;
|
|
117
118
|
for (let round = 1; round <= maxRounds; round++) {
|
|
119
|
+
if (round > 1) {
|
|
120
|
+
if (config.totalTimeoutMs != null && Date.now() - startedAt >= config.totalTimeoutMs)
|
|
121
|
+
break;
|
|
122
|
+
if (config.costCeilingMicroUsd != null && spend >= config.costCeilingMicroUsd)
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
118
125
|
const v = await runVerifier(current, round);
|
|
119
126
|
spend += v.cost;
|
|
120
127
|
verifierSpend += v.cost;
|
|
128
|
+
if (v.paused) {
|
|
129
|
+
outcome = {
|
|
130
|
+
verdict: "unverified",
|
|
131
|
+
unverifiedReason: "verifier_paused",
|
|
132
|
+
rounds: round,
|
|
133
|
+
findings: [],
|
|
134
|
+
verifierCost: verifierSpend,
|
|
135
|
+
verifierPause: {
|
|
136
|
+
status: v.paused.status,
|
|
137
|
+
sessionId: v.paused.sessionId,
|
|
138
|
+
...(v.paused.checkpointToken ? { checkpointToken: v.paused.checkpointToken } : {}),
|
|
139
|
+
...(v.paused.checkpointGate ? { checkpointGate: v.paused.checkpointGate } : {}),
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
config.onVerifierPause?.({
|
|
144
|
+
round,
|
|
145
|
+
status: v.paused.status,
|
|
146
|
+
sessionId: v.paused.sessionId,
|
|
147
|
+
...(v.paused.checkpointToken ? { checkpointToken: v.paused.checkpointToken } : {}),
|
|
148
|
+
...(v.paused.checkpointGate ? { checkpointGate: v.paused.checkpointGate } : {}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
}
|
|
153
|
+
config.onRound?.({ round, verdict: outcome.verdict, findings: outcome.findings });
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
121
156
|
const rawVerdict = v.verdict?.verdict ?? "unverified";
|
|
122
157
|
const verdict = rawVerdict === "PARTIAL" ? "unverified" : rawVerdict;
|
|
123
158
|
const unverifiedReason = !v.verdict ? "no_verdict" : rawVerdict === "PARTIAL" ? "could_not_verify" : undefined;
|
package/dist/bin/sema-tb.js
CHANGED
|
@@ -289,7 +289,7 @@ async function main() {
|
|
|
289
289
|
switch (e.kind) {
|
|
290
290
|
case "brain.call":
|
|
291
291
|
lastServedModel = e.model;
|
|
292
|
-
line(`[call] model=${e.model} in=${e.promptTokens} out=${e.completionTokens} cache=${e.cacheRead} latMs=${e.latencyMs}` +
|
|
292
|
+
line(`[call] model=${e.model} in=${e.promptTokens} tot=${e.totalInputTokens} out=${e.completionTokens} cache=${e.cacheRead} latMs=${e.latencyMs}` +
|
|
293
293
|
(e.firstTokenMs !== undefined ? ` ttftMs=${e.firstTokenMs}` : "") +
|
|
294
294
|
(e.stopReason !== undefined ? ` stop=${e.stopReason}` : "") +
|
|
295
295
|
(e.callCap !== undefined ? ` cap=${e.callCap}${e.capThinkingSkipped ? "(think-skip)" : ""}` : "") +
|
|
@@ -403,7 +403,7 @@ async function main() {
|
|
|
403
403
|
reason.flush();
|
|
404
404
|
turns++;
|
|
405
405
|
const u = ev.usage;
|
|
406
|
-
line(`[turn] ${turns}${u ? ` in=${u.inputTokens} out=${u.outputTokens} cache=${u.cacheReadTokens}` : ""}` +
|
|
406
|
+
line(`[turn] ${turns}${u ? ` in=${u.inputTokens} tot=${u.totalInputTokens} out=${u.outputTokens} cache=${u.cacheReadTokens}` : ""}` +
|
|
407
407
|
(ev.usageMissing ? " usageMissing=1" : "") +
|
|
408
408
|
(ev.stopReason !== undefined ? ` stop=${ev.stopReason}` : ""));
|
|
409
409
|
break;
|
|
@@ -11,14 +11,14 @@ class InMemoryBreakerState {
|
|
|
11
11
|
this.m.set(k, s);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
function errorAssistantMessage(model, code, detail) {
|
|
14
|
+
function errorAssistantMessage(model, code, detail, stopReason = "error") {
|
|
15
15
|
return {
|
|
16
16
|
role: "assistant",
|
|
17
17
|
content: [{ type: "text", text: "" }],
|
|
18
18
|
api: model.api,
|
|
19
19
|
provider: model.provider,
|
|
20
20
|
model: model.id,
|
|
21
|
-
stopReason
|
|
21
|
+
stopReason,
|
|
22
22
|
errorMessage: `[${code}] ${detail}`,
|
|
23
23
|
usage: ZERO_USAGE,
|
|
24
24
|
timestamp: Date.now(),
|
|
@@ -44,21 +44,33 @@ export function createCircuitBreakerBrain(inner, opts = {}) {
|
|
|
44
44
|
if (s.phase === "closed")
|
|
45
45
|
return { pass: true };
|
|
46
46
|
if (s.phase === "open") {
|
|
47
|
-
if (s.openedAt === undefined)
|
|
48
|
-
|
|
47
|
+
if (s.openedAt === undefined) {
|
|
48
|
+
state.set(key, { ...s, openedAt: now() });
|
|
49
|
+
return { pass: false, retryInMs: cooldownMs };
|
|
50
|
+
}
|
|
49
51
|
const elapsed = now() - s.openedAt;
|
|
50
52
|
if (elapsed >= cooldownMs) {
|
|
51
|
-
setState(key, s.phase, { ...s, phase: "half-open", halfOpenInFlight: 1 });
|
|
53
|
+
setState(key, s.phase, { ...s, phase: "half-open", halfOpenInFlight: 1, halfOpenAt: now() });
|
|
52
54
|
return { pass: true };
|
|
53
55
|
}
|
|
54
56
|
return { pass: false, retryInMs: cooldownMs - elapsed };
|
|
55
57
|
}
|
|
56
58
|
const inFlight = s.halfOpenInFlight ?? 0;
|
|
57
59
|
if (inFlight < halfOpenProbes) {
|
|
58
|
-
state.set(key, { ...s, halfOpenInFlight: inFlight + 1 });
|
|
60
|
+
state.set(key, { ...s, halfOpenInFlight: inFlight + 1, halfOpenAt: s.halfOpenAt ?? now() });
|
|
59
61
|
return { pass: true };
|
|
60
62
|
}
|
|
61
|
-
|
|
63
|
+
const leaseStart = s.halfOpenAt;
|
|
64
|
+
if (leaseStart === undefined) {
|
|
65
|
+
state.set(key, { ...s, halfOpenAt: now() });
|
|
66
|
+
return { pass: false, retryInMs: cooldownMs };
|
|
67
|
+
}
|
|
68
|
+
const held = now() - leaseStart;
|
|
69
|
+
if (held >= cooldownMs) {
|
|
70
|
+
state.set(key, { ...s, halfOpenInFlight: 1, halfOpenAt: now() });
|
|
71
|
+
return { pass: true };
|
|
72
|
+
}
|
|
73
|
+
return { pass: false, retryInMs: cooldownMs - held };
|
|
62
74
|
};
|
|
63
75
|
const record = (key, failedCode, viaProbe = true) => {
|
|
64
76
|
const s = snap(key);
|
|
@@ -118,9 +130,14 @@ export function createCircuitBreakerBrain(inner, opts = {}) {
|
|
|
118
130
|
}
|
|
119
131
|
}
|
|
120
132
|
catch (e) {
|
|
133
|
+
const aborted = options?.signal?.aborted === true || (e instanceof Error && e.name === "AbortError");
|
|
121
134
|
const code = e instanceof BrainError ? e.code : "network";
|
|
122
|
-
record(key, code, !bypass);
|
|
123
|
-
out.push({
|
|
135
|
+
record(key, aborted ? "http" : code, !bypass);
|
|
136
|
+
out.push({
|
|
137
|
+
type: "error",
|
|
138
|
+
reason: aborted ? "aborted" : "error",
|
|
139
|
+
error: errorAssistantMessage(model, code, e instanceof Error ? e.message : String(e), aborted ? "aborted" : "error"),
|
|
140
|
+
});
|
|
124
141
|
}
|
|
125
142
|
})();
|
|
126
143
|
return out;
|
package/dist/brain/degrading.js
CHANGED
|
@@ -91,8 +91,20 @@ export function passthroughStream(brain, model, context, options) {
|
|
|
91
91
|
void (async () => {
|
|
92
92
|
try {
|
|
93
93
|
const inner = await brain.stream(model, context, options);
|
|
94
|
-
|
|
94
|
+
let sawTerminal = false;
|
|
95
|
+
for await (const ev of inner) {
|
|
96
|
+
if (ev.type === "done" || ev.type === "error")
|
|
97
|
+
sawTerminal = true;
|
|
95
98
|
out.push(ev);
|
|
99
|
+
}
|
|
100
|
+
if (!sawTerminal) {
|
|
101
|
+
await inner.result();
|
|
102
|
+
out.push({
|
|
103
|
+
type: "error",
|
|
104
|
+
reason: "error",
|
|
105
|
+
error: errorMessageOf(model, new Error("brain stream ended without a terminal event"), false),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
96
108
|
}
|
|
97
109
|
catch (e) {
|
|
98
110
|
const aborted = options?.signal?.aborted === true ||
|
package/dist/brain/failover.js
CHANGED
|
@@ -53,6 +53,8 @@ export function createFailoverBrain(brains) {
|
|
|
53
53
|
...(lastFailedCode !== undefined ? { errorCode: lastFailedCode } : {}),
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
if (!sawContent && heldStart)
|
|
57
|
+
out.push(heldStart);
|
|
56
58
|
out.push(terminal ?? {
|
|
57
59
|
type: "done",
|
|
58
60
|
reason: message.stopReason,
|
package/dist/brain/repetition.js
CHANGED
|
@@ -11,6 +11,15 @@ const SEGMENT_MAX = 120;
|
|
|
11
11
|
function clipSegment(s) {
|
|
12
12
|
return s.length > SEGMENT_MAX ? s.slice(0, SEGMENT_MAX) : s;
|
|
13
13
|
}
|
|
14
|
+
function trailingRepeats(text, unit, cap) {
|
|
15
|
+
let n = 0;
|
|
16
|
+
let end = text.length;
|
|
17
|
+
while (n < cap && end - unit.length >= 0 && text.startsWith(unit, end - unit.length)) {
|
|
18
|
+
n++;
|
|
19
|
+
end -= unit.length;
|
|
20
|
+
}
|
|
21
|
+
return n;
|
|
22
|
+
}
|
|
14
23
|
function looksLikeCodeLineUnit(unit) {
|
|
15
24
|
if (!unit.includes("\n")) {
|
|
16
25
|
return false;
|
|
@@ -59,7 +68,7 @@ export function inspectDegenerate(text) {
|
|
|
59
68
|
if (!looksLikeCodeLineUnit(unit)) {
|
|
60
69
|
return { degenerate: true, cut: event, spared };
|
|
61
70
|
}
|
|
62
|
-
if (t === WINDOW && reps
|
|
71
|
+
if (t === WINDOW && reps * p >= t - p && trailingRepeats(text, unit, SATURATION_REPS) >= SATURATION_REPS) {
|
|
63
72
|
return { degenerate: true, cut: event, spared };
|
|
64
73
|
}
|
|
65
74
|
if (!sparedUnitLoop) {
|
package/dist/brain/routing.js
CHANGED
|
@@ -17,7 +17,11 @@ export function createRoutingBrain(routes, opts = {}) {
|
|
|
17
17
|
const by = opts.by ?? "provider";
|
|
18
18
|
const stream = (model, context, options) => {
|
|
19
19
|
const key = by === "api" ? model.api : model.provider;
|
|
20
|
-
const
|
|
20
|
+
const routed = Object.hasOwn(routes, key) ? routes[key] : undefined;
|
|
21
|
+
const matched = routed !== null && typeof routed === "object" && typeof routed.stream === "function"
|
|
22
|
+
? routed
|
|
23
|
+
: undefined;
|
|
24
|
+
const target = matched ?? opts.fallback;
|
|
21
25
|
if (!target) {
|
|
22
26
|
const out = createAssistantMessageEventStream();
|
|
23
27
|
out.push({
|
|
@@ -4,7 +4,11 @@ export function runWithStatusSink(emit, fn) {
|
|
|
4
4
|
return statusSinkStore.run({ emit }, fn);
|
|
5
5
|
}
|
|
6
6
|
export function emitBrainStatus(status) {
|
|
7
|
-
|
|
7
|
+
try {
|
|
8
|
+
statusSinkStore.getStore()?.emit(status);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
}
|
|
8
12
|
}
|
|
9
13
|
const telemetrySinkStore = new AsyncLocalStorage();
|
|
10
14
|
export function runWithBrainTelemetry(emit, fn) {
|
|
@@ -43,6 +43,9 @@ function emptyAssistant(model) {
|
|
|
43
43
|
function isAbortError(err) {
|
|
44
44
|
return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
45
45
|
}
|
|
46
|
+
function isWalltimeCutoff(err) {
|
|
47
|
+
return err instanceof Error && err.message === WALLTIME_CUTOFF_MESSAGE;
|
|
48
|
+
}
|
|
46
49
|
function sleep(ms, signal) {
|
|
47
50
|
return new Promise((resolve) => {
|
|
48
51
|
if (signal?.aborted)
|
|
@@ -85,7 +88,7 @@ export function runStreamingBrain(args) {
|
|
|
85
88
|
.catch((err) => {
|
|
86
89
|
const aborted = signal?.aborted === true || isAbortError(err);
|
|
87
90
|
terminalRetryPhase = "gave_up";
|
|
88
|
-
terminalRetryDetail = aborted ? "cancelled while retrying" : "retries exhausted";
|
|
91
|
+
terminalRetryDetail = aborted ? "cancelled while retrying" : isWalltimeCutoff(err) ? "wall-clock deadline reached while retrying" : "retries exhausted";
|
|
89
92
|
const errorMsg = emptyAssistant(model);
|
|
90
93
|
errorMsg.stopReason = aborted ? "aborted" : "error";
|
|
91
94
|
errorMsg.errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -3,7 +3,7 @@ const FENCE_RE = /```(?:json|tool_call|tool|function)?\s*([\s\S]*?)```/gi;
|
|
|
3
3
|
function asRecord(v) {
|
|
4
4
|
return v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
5
5
|
}
|
|
6
|
-
function extractCall(obj, toolNames) {
|
|
6
|
+
function extractCall(obj, toolNames, framed) {
|
|
7
7
|
const rec = asRecord(obj);
|
|
8
8
|
if (!rec) {
|
|
9
9
|
return undefined;
|
|
@@ -13,16 +13,32 @@ function extractCall(obj, toolNames) {
|
|
|
13
13
|
if (typeof name !== "string" || !toolNames.has(name)) {
|
|
14
14
|
return undefined;
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const rawArgs = rec.arguments ?? rec.parameters ?? rec.input ?? rec.args ?? fn?.arguments;
|
|
17
|
+
let args;
|
|
18
|
+
if (rawArgs === undefined) {
|
|
19
|
+
if (!framed)
|
|
20
|
+
return undefined;
|
|
21
|
+
args = {};
|
|
22
|
+
}
|
|
23
|
+
else if (typeof rawArgs === "string") {
|
|
24
|
+
let parsed;
|
|
18
25
|
try {
|
|
19
|
-
|
|
26
|
+
parsed = JSON.parse(rawArgs);
|
|
20
27
|
}
|
|
21
28
|
catch {
|
|
22
|
-
|
|
29
|
+
return undefined;
|
|
23
30
|
}
|
|
31
|
+
const rec2 = asRecord(parsed);
|
|
32
|
+
if (!rec2)
|
|
33
|
+
return undefined;
|
|
34
|
+
args = rec2;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const rec2 = asRecord(rawArgs);
|
|
38
|
+
if (!rec2)
|
|
39
|
+
return undefined;
|
|
40
|
+
args = rec2;
|
|
24
41
|
}
|
|
25
|
-
const args = asRecord(rawArgs) ?? {};
|
|
26
42
|
return {
|
|
27
43
|
type: "toolCall",
|
|
28
44
|
id: `repair_${name}_${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -30,28 +46,30 @@ function extractCall(obj, toolNames) {
|
|
|
30
46
|
arguments: args,
|
|
31
47
|
};
|
|
32
48
|
}
|
|
33
|
-
function parseBlock(raw, toolNames, out) {
|
|
49
|
+
function parseBlock(raw, toolNames, out, framed) {
|
|
34
50
|
let parsed;
|
|
35
51
|
try {
|
|
36
52
|
parsed = JSON.parse(raw.trim());
|
|
37
53
|
}
|
|
38
54
|
catch {
|
|
39
|
-
return
|
|
55
|
+
return "none";
|
|
40
56
|
}
|
|
41
57
|
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
42
|
-
let
|
|
58
|
+
let promoted = 0;
|
|
43
59
|
for (const item of items) {
|
|
44
|
-
const call = extractCall(item, toolNames);
|
|
60
|
+
const call = extractCall(item, toolNames, framed);
|
|
45
61
|
if (call) {
|
|
46
62
|
out.push(call);
|
|
47
|
-
|
|
63
|
+
promoted++;
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
|
-
|
|
66
|
+
if (promoted === 0)
|
|
67
|
+
return "none";
|
|
68
|
+
return promoted === items.length ? "all" : "some";
|
|
51
69
|
}
|
|
52
70
|
export function repairTextToolCalls(text, toolNames) {
|
|
53
71
|
const toolCalls = [];
|
|
54
|
-
let cleaned = text.replace(TAG_RE, (m, _tag, inner) => parseBlock(inner, toolNames, toolCalls) ? "" : m);
|
|
55
|
-
cleaned = cleaned.replace(FENCE_RE, (m, inner) => parseBlock(inner, toolNames, toolCalls) ? "" : m);
|
|
72
|
+
let cleaned = text.replace(TAG_RE, (m, _tag, inner) => parseBlock(inner, toolNames, toolCalls, true) === "all" ? "" : m);
|
|
73
|
+
cleaned = cleaned.replace(FENCE_RE, (m, inner) => parseBlock(inner, toolNames, toolCalls, false) === "all" ? "" : m);
|
|
56
74
|
return { toolCalls, cleanedText: cleaned.trim() };
|
|
57
75
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, CompactionError, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, formatPersistedOutputRefs, prepareCompaction, shouldCompact, } from "../internal/harness.js";
|
|
1
|
+
import { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, CompactionError, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, formatFileOperations, formatPersistedOutputRefs, prepareCompaction, shouldCompact, } from "../internal/harness.js";
|
|
2
2
|
import { fileArgPath } from "../tools/fs/safety.js";
|
|
3
3
|
import { contextEditFrontier } from "./context-edit.js";
|
|
4
4
|
import { selectCompactionEpoch } from "../prompt-assembly/epoch.js";
|
|
@@ -17,6 +17,10 @@ export function sanitizeCompactionInstructions(text) {
|
|
|
17
17
|
? cps.slice(0, COMPACTION_INSTRUCTIONS_MAX_CHARS).join("")
|
|
18
18
|
: defused;
|
|
19
19
|
}
|
|
20
|
+
const WORKING_FILE_BREAKOUT_TAGS = ["working-file", "summary"];
|
|
21
|
+
function escapeWorkingFilePathAttr(path) {
|
|
22
|
+
return path.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/[\r\n]+/g, " ");
|
|
23
|
+
}
|
|
20
24
|
export const COMPACTION_THROUGHPUT_PRIOR_TOKENS_PER_MS = 3;
|
|
21
25
|
const COMPACTION_THROUGHPUT_EWMA_ALPHA = 0.3;
|
|
22
26
|
const COMPACTION_WALLTIME_ABORT_FLAG = "semaCompactionWalltimeAbort";
|
|
@@ -69,8 +73,11 @@ export async function maybeCompact(opts) {
|
|
|
69
73
|
const anchorStale = est.usageTokens > 0 && est.tokens > structuralTokens * STALE_ANCHOR_STRUCTURAL_MARGIN + overhead;
|
|
70
74
|
const tokens = anchorStale || est.usageTokens === 0 ? structuralTokens + overhead : est.tokens;
|
|
71
75
|
const window = opts.model.autoCompactTokens ?? opts.model.contextTokens ?? opts.model.contextWindow;
|
|
76
|
+
const windowKnown = Number.isFinite(window) && window > 0;
|
|
72
77
|
const settings = sanitizeCompactionSettings(rawSettings, window);
|
|
73
|
-
const contextUsage =
|
|
78
|
+
const contextUsage = windowKnown
|
|
79
|
+
? { usedTokens: tokens, windowTokens: window, compactAtTokens: window - settings.reserveTokens }
|
|
80
|
+
: undefined;
|
|
74
81
|
const wantsCompact = shouldCompact(tokens, window, settings);
|
|
75
82
|
const naturalTrigger = wantsCompact && tokens >= (opts.minTokens ?? 0);
|
|
76
83
|
const force = opts.force || (opts.forceUnderThreshold === true && !naturalTrigger);
|
|
@@ -118,7 +125,7 @@ export async function maybeCompact(opts) {
|
|
|
118
125
|
: hookSanitized
|
|
119
126
|
: specInstructions;
|
|
120
127
|
const maxReuse = opts.maxConsecutiveProviderReuse ?? 3;
|
|
121
|
-
const forceRealSummary = (opts.consecutiveProviderReuse ?? 0) >= maxReuse;
|
|
128
|
+
const forceRealSummary = maxReuse > 0 && (opts.consecutiveProviderReuse ?? 0) >= maxReuse;
|
|
122
129
|
let providerSummary;
|
|
123
130
|
if (opts.summaryProvider && !forceRealSummary && !prep.value.isSplitTurn) {
|
|
124
131
|
const providerModifiedFiles = computeFileLists(prep.value.fileOps).modifiedFilesByRecency;
|
|
@@ -151,7 +158,9 @@ export async function maybeCompact(opts) {
|
|
|
151
158
|
summary = providerSummary;
|
|
152
159
|
firstKeptEntryId = prep.value.firstKeptEntryId;
|
|
153
160
|
summaryTokensBefore = prep.value.tokensBefore;
|
|
154
|
-
|
|
161
|
+
const providerFileLists = computeFileLists(prep.value.fileOps);
|
|
162
|
+
details = providerFileLists;
|
|
163
|
+
summary += formatFileOperations(providerFileLists.readFiles, providerFileLists.modifiedFiles);
|
|
155
164
|
if (prep.value.invokedSkills.length > 0) {
|
|
156
165
|
details.invokedSkills = prep.value.invokedSkills;
|
|
157
166
|
}
|
|
@@ -330,7 +339,7 @@ export async function maybeCompact(opts) {
|
|
|
330
339
|
const rawCandidateFiles = recentlyRead.length > 0 ? recentlyRead : (details?.modifiedFilesByRecency ?? details?.modifiedFiles);
|
|
331
340
|
const maxFilesForSelection = Math.max(1, att?.maxFiles ?? 3);
|
|
332
341
|
const excludedReadStatePreserveKeys = [];
|
|
333
|
-
const candidateFiles = Array.isArray(rawCandidateFiles) && rawCandidateFiles.length > 0
|
|
342
|
+
const candidateFiles = att !== undefined && Array.isArray(rawCandidateFiles) && rawCandidateFiles.length > 0
|
|
334
343
|
? await (async () => {
|
|
335
344
|
const keptTailReadPathsRaw = new Set();
|
|
336
345
|
const firstKeptIdx = branch.findIndex((e) => e.id === firstKeptEntryId);
|
|
@@ -417,7 +426,8 @@ export async function maybeCompact(opts) {
|
|
|
417
426
|
if (att && Array.isArray(candidateFiles) && candidateFiles.length > 0) {
|
|
418
427
|
const maxFiles = maxFilesForSelection;
|
|
419
428
|
const perFileCap = Math.max(200, att.maxCharsPerFile ?? 16_000);
|
|
420
|
-
|
|
429
|
+
const windowShareChars = windowKnown ? Math.floor(window * 0.15) * cpt : Number.POSITIVE_INFINITY;
|
|
430
|
+
let remaining = Math.min(maxFiles * perFileCap, windowShareChars);
|
|
421
431
|
const blocks = [];
|
|
422
432
|
for (const path of candidateFiles.slice(0, maxFiles)) {
|
|
423
433
|
if (remaining <= 0)
|
|
@@ -437,7 +447,7 @@ export async function maybeCompact(opts) {
|
|
|
437
447
|
? `${content.slice(0, cap)}\n[... ${content.length - cap} more characters truncated — read the file for the rest]`
|
|
438
448
|
: content;
|
|
439
449
|
remaining -= clipped.length;
|
|
440
|
-
blocks.push(`<working-file path="${path}">\n${clipped}\n</working-file>`);
|
|
450
|
+
blocks.push(`<working-file path="${escapeWorkingFilePathAttr(path)}">\n${sanitizeUntrustedText(clipped, WORKING_FILE_BREAKOUT_TAGS)}\n</working-file>`);
|
|
441
451
|
attachedFiles.push({ path, chars: Math.min(content.length, cap), truncated: wasClipped });
|
|
442
452
|
if (!wasClipped)
|
|
443
453
|
attachedComplete.push({ path, content });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { extractSymbols } from "../tools/fs/repo-map.js";
|
|
2
|
+
import { parseSkillToolEntry } from "./skill-tool-specifier.js";
|
|
2
3
|
export function decideAutoPromote(i) {
|
|
3
4
|
if (i.tripwire.escalate)
|
|
4
5
|
return "needs_human";
|
|
@@ -58,7 +59,7 @@ export function deriveTripwire(artifactText, declared, profileTokens) {
|
|
|
58
59
|
const reasons = [];
|
|
59
60
|
let escalate = false;
|
|
60
61
|
try {
|
|
61
|
-
const allow = new Set(declared.allowTools ?? []);
|
|
62
|
+
const allow = new Set((declared.allowTools ?? []).map((entry) => parseSkillToolEntry(entry).name));
|
|
62
63
|
const symbols = extractSymbols(artifactText);
|
|
63
64
|
for (const sym of symbols) {
|
|
64
65
|
if (!allow.has(sym)) {
|
|
@@ -30,6 +30,7 @@ export declare function riskSeverity(axes: {
|
|
|
30
30
|
shell?: boolean;
|
|
31
31
|
}): 1 | 2 | 3 | 4 | 5;
|
|
32
32
|
export declare const MAX_TOOL_INPUT_PREVIEW_CHARS = 512;
|
|
33
|
+
export declare const MAX_PENDING_STEER_CHARS = 16000;
|
|
33
34
|
export declare function buildRiskDescriptor(input: {
|
|
34
35
|
toolName: string;
|
|
35
36
|
args: unknown;
|
|
@@ -25,6 +25,7 @@ const MAX_TOUCHED_PATHS = 8;
|
|
|
25
25
|
const MAX_DIGEST_KEYS = 16;
|
|
26
26
|
const MAX_DIGEST_SCAN_KEYS = 256;
|
|
27
27
|
export const MAX_TOOL_INPUT_PREVIEW_CHARS = 512;
|
|
28
|
+
export const MAX_PENDING_STEER_CHARS = 16_000;
|
|
28
29
|
function isPlainRecord(x) {
|
|
29
30
|
try {
|
|
30
31
|
if (x === null || typeof x !== "object" || Array.isArray(x))
|
|
@@ -130,26 +131,40 @@ export const MAX_SUPPORTED_CHECKPOINT_VERSION = 4;
|
|
|
130
131
|
export function checkpointVersionOf(cp) {
|
|
131
132
|
return cp.version ?? 0;
|
|
132
133
|
}
|
|
134
|
+
function finiteBound(v) {
|
|
135
|
+
return v !== undefined && Number.isFinite(v) ? v : undefined;
|
|
136
|
+
}
|
|
137
|
+
function finiteSpend(v) {
|
|
138
|
+
return v !== undefined && Number.isFinite(v) ? v : undefined;
|
|
139
|
+
}
|
|
133
140
|
export function debitLedger(prior, slice, total, opts) {
|
|
134
141
|
return {
|
|
135
|
-
totalBudgetMicroUsd: prior?.totalBudgetMicroUsd ?? total?.totalBudgetMicroUsd,
|
|
136
|
-
totalWalltimeSec: prior?.totalWalltimeSec ?? total?.totalWalltimeSec,
|
|
137
|
-
spentMicroUsd: (prior?.spentMicroUsd ?? 0) + Math.max(0, slice.costMicroUsd),
|
|
138
|
-
spentTokens: (prior?.spentTokens ?? 0) + Math.max(0, slice.tokens),
|
|
139
|
-
spentTurns: (prior?.spentTurns ?? 0) + Math.max(0, slice.turns),
|
|
140
|
-
spentWalltimeMs: (prior?.spentWalltimeMs ?? 0) + Math.max(0, slice.walltimeMs ?? 0),
|
|
142
|
+
totalBudgetMicroUsd: finiteBound(prior?.totalBudgetMicroUsd) ?? finiteBound(total?.totalBudgetMicroUsd),
|
|
143
|
+
totalWalltimeSec: finiteBound(prior?.totalWalltimeSec) ?? finiteBound(total?.totalWalltimeSec),
|
|
144
|
+
spentMicroUsd: (finiteSpend(prior?.spentMicroUsd) ?? 0) + Math.max(0, finiteSpend(slice.costMicroUsd) ?? 0),
|
|
145
|
+
spentTokens: (finiteSpend(prior?.spentTokens) ?? 0) + Math.max(0, finiteSpend(slice.tokens) ?? 0),
|
|
146
|
+
spentTurns: (finiteSpend(prior?.spentTurns) ?? 0) + Math.max(0, finiteSpend(slice.turns) ?? 0),
|
|
147
|
+
spentWalltimeMs: (finiteSpend(prior?.spentWalltimeMs) ?? 0) + Math.max(0, finiteSpend(slice.walltimeMs) ?? 0),
|
|
141
148
|
sliceCount: (prior?.sliceCount ?? 0) + (opts?.countSlice === false ? 0 : 1),
|
|
142
149
|
};
|
|
143
150
|
}
|
|
144
151
|
export function remainingBudgetMicroUsd(ledger) {
|
|
145
|
-
|
|
152
|
+
const total = ledger?.totalBudgetMicroUsd;
|
|
153
|
+
if (total === undefined || !Number.isFinite(total))
|
|
146
154
|
return undefined;
|
|
147
|
-
|
|
155
|
+
const spent = ledger?.spentMicroUsd;
|
|
156
|
+
if (spent !== undefined && !Number.isFinite(spent))
|
|
157
|
+
return 0;
|
|
158
|
+
return Math.max(0, total - (spent ?? 0));
|
|
148
159
|
}
|
|
149
160
|
export function remainingWalltimeMs(ledger) {
|
|
150
|
-
|
|
161
|
+
const total = ledger?.totalWalltimeSec;
|
|
162
|
+
if (total === undefined || !Number.isFinite(total))
|
|
151
163
|
return undefined;
|
|
152
|
-
|
|
164
|
+
const spent = ledger?.spentWalltimeMs;
|
|
165
|
+
if (spent !== undefined && !Number.isFinite(spent))
|
|
166
|
+
return 0;
|
|
167
|
+
return Math.max(0, total * 1000 - (spent ?? 0));
|
|
153
168
|
}
|
|
154
169
|
export function summarizeCheckpoint(cp) {
|
|
155
170
|
const riskDescriptor = cp.gate.kind === "human" || cp.gate.kind === "irreversible_ask" ? cp.gate.riskDescriptor : undefined;
|
|
@@ -217,6 +232,9 @@ export function validatePendingSteer(steer) {
|
|
|
217
232
|
if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(steer.text)) {
|
|
218
233
|
throw new CheckpointError("steering.invalid_content", "steering text must not contain control characters");
|
|
219
234
|
}
|
|
235
|
+
if (steer.text.length > MAX_PENDING_STEER_CHARS) {
|
|
236
|
+
throw new CheckpointError("steering.invalid_content", `steering text must be at most ${MAX_PENDING_STEER_CHARS} characters (got ${steer.text.length})`);
|
|
237
|
+
}
|
|
220
238
|
return { text: steer.text, trusted: steer.trusted };
|
|
221
239
|
}
|
|
222
240
|
export class InMemoryCheckpointStore {
|
|
@@ -3,9 +3,9 @@ import { runMemoryConsolidation, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATI
|
|
|
3
3
|
function maxMarker(a, b) {
|
|
4
4
|
return a === undefined || b > a ? b : a;
|
|
5
5
|
}
|
|
6
|
-
function cappedHighWater(seen,
|
|
6
|
+
function cappedHighWater(seen, blockers) {
|
|
7
7
|
let floor;
|
|
8
|
-
for (const id of
|
|
8
|
+
for (const id of blockers)
|
|
9
9
|
if (floor === undefined || id < floor)
|
|
10
10
|
floor = id;
|
|
11
11
|
let hw;
|
|
@@ -66,7 +66,8 @@ export async function consolidateScope(scope, deps, opts = {}) {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
const seen = [...batchIds, ...(stats?.addedIds ?? [])];
|
|
69
|
-
const
|
|
69
|
+
const unfedTail = pending.slice(maxNotes).map((h) => h.id);
|
|
70
|
+
const highWater = cappedHighWater(seen, [...(stats?.failedIds ?? []), ...unfedTail]);
|
|
70
71
|
if (highWater !== undefined && highWater !== cursor && (cursor === undefined || highWater > cursor)) {
|
|
71
72
|
await store.setConsolidationCursor(scope, highWater);
|
|
72
73
|
}
|
|
@@ -114,5 +114,8 @@ export function clearStaleToolResults(messages, opts) {
|
|
|
114
114
|
}
|
|
115
115
|
export function editBudget(model) {
|
|
116
116
|
const window = model.autoCompactTokens ?? model.contextTokens ?? model.contextWindow;
|
|
117
|
+
if (!Number.isFinite(window) || window <= 0) {
|
|
118
|
+
return Number.POSITIVE_INFINITY;
|
|
119
|
+
}
|
|
117
120
|
return contextEditFrontier(window);
|
|
118
121
|
}
|
|
@@ -116,5 +116,8 @@ export function dropOrphanToolResults(messages) {
|
|
|
116
116
|
}
|
|
117
117
|
export function guardBudget(model) {
|
|
118
118
|
const window = model.contextTokens ?? model.contextWindow;
|
|
119
|
+
if (!Number.isFinite(window) || window <= 0) {
|
|
120
|
+
return Number.POSITIVE_INFINITY;
|
|
121
|
+
}
|
|
119
122
|
return Math.max(window - GUARD_HEADROOM_TOKENS, Math.floor(window * GUARD_FRACTION));
|
|
120
123
|
}
|
|
@@ -12,4 +12,9 @@ export declare function addWorktree(baseEnv: ExecutionEnv, opts: AddWorktreeOpti
|
|
|
12
12
|
};
|
|
13
13
|
worktreeDir: string;
|
|
14
14
|
}>;
|
|
15
|
-
export declare function pruneWorktrees(baseEnv: ExecutionEnv, repoRoot: string): Promise<
|
|
15
|
+
export declare function pruneWorktrees(baseEnv: ExecutionEnv, repoRoot: string): Promise<{
|
|
16
|
+
ok: true;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
detail: string;
|
|
20
|
+
}>;
|