@sema-agent/core 2.13.0 → 4.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/CHANGELOG.md +2345 -0
- package/dist/agents/cascade.d.ts +6 -1
- package/dist/agents/cascade.js +76 -32
- package/dist/agents/cumulative-stats.d.ts +29 -0
- package/dist/agents/cumulative-stats.js +29 -0
- package/dist/agents/observer.d.ts +4 -0
- package/dist/agents/observer.js +11 -10
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +110 -3
- package/dist/agents/retain-ledger.d.ts +20 -2
- package/dist/agents/retain-ledger.js +38 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +20 -1
- package/dist/agents/subagent.js +66 -64
- package/dist/agents/teacher.d.ts +1 -9
- package/dist/agents/teacher.js +23 -24
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +39 -3
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/anthropic.js +24 -21
- 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/openai.js +24 -21
- 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 +5 -2
- package/dist/brain/stream-shared.d.ts +1 -0
- package/dist/brain/stream-shared.js +14 -0
- package/dist/brain/terminal-cause.d.ts +4 -0
- package/dist/brain/terminal-cause.js +12 -0
- package/dist/brain/tool-call-id.d.ts +1 -0
- package/dist/brain/tool-call-id.js +3 -0
- package/dist/brain/tool-call-repair.js +34 -15
- package/dist/core/auto-compaction.js +33 -32
- package/dist/core/background-agent-store.d.ts +22 -22
- package/dist/core/background-agent-store.js +19 -23
- package/dist/core/checkpoint-store.d.ts +7 -13
- package/dist/core/checkpoint-store.js +39 -15
- 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/file-snapshot-store.d.ts +8 -0
- package/dist/core/file-snapshot-store.js +4 -3
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +12 -1
- package/dist/core/hooks.d.ts +4 -1
- package/dist/core/hooks.js +35 -7
- 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 +65 -9
- package/dist/core/mailbox-store.d.ts +7 -10
- package/dist/core/mcp.d.ts +7 -10
- package/dist/core/mcp.js +7 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +96 -31
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +38 -39
- package/dist/core/memory-engine/layout.d.ts +8 -0
- package/dist/core/memory-engine/layout.js +71 -4
- package/dist/core/memory-engine/scope-contract.js +9 -3
- package/dist/core/memory-engine/types.d.ts +1 -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.d.ts +0 -1
- package/dist/core/runner/active-skill-scope.js +1 -15
- package/dist/core/runner/assemble-result.d.ts +3 -8
- package/dist/core/runner/assemble-result.js +3 -5
- package/dist/core/runner/compaction-call-options.d.ts +7 -0
- package/dist/core/runner/compaction-call-options.js +30 -0
- package/dist/core/runner/prepare-task.d.ts +6 -23
- package/dist/core/runner/prepare-task.js +31 -53
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +86 -178
- package/dist/core/runner/session-rule-policy.js +4 -1
- package/dist/core/runner/teardown-bounded.d.ts +7 -0
- package/dist/core/runner/teardown-bounded.js +36 -0
- package/dist/core/runner/tool-disclosure.d.ts +2 -5
- package/dist/core/runner/turn-attachments.d.ts +5 -15
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/safe-notify.d.ts +17 -0
- package/dist/core/safe-notify.js +59 -0
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +11 -4
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/stub-env.d.ts +2 -6
- package/dist/core/surrogate-safe-slice.d.ts +4 -0
- package/dist/core/surrogate-safe-slice.js +18 -0
- package/dist/core/task-registry-monitor.js +15 -14
- package/dist/core/task-registry-shared.d.ts +22 -0
- package/dist/core/task-registry-shared.js +16 -0
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +40 -57
- package/dist/core/tool-errors.js +5 -4
- package/dist/core/tool-name-aliases.js +2 -1
- package/dist/core/tool-policy.js +72 -19
- package/dist/core/tool-result-store.d.ts +1 -0
- package/dist/core/tool-result-store.js +17 -1
- package/dist/core/trace.d.ts +3 -9
- package/dist/core/types.d.ts +16 -12
- package/dist/core/untrusted-egress.js +7 -5
- package/dist/core/workflow-journal-store.d.ts +9 -20
- package/dist/core/workflow-run-store.d.ts +11 -10
- package/dist/core/workflow-run-store.js +22 -16
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/compaction/utils.js +4 -2
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +13 -33
- package/dist/engine/harness/types.d.ts +29 -67
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +11 -4
- package/dist/engine/loop/types.d.ts +11 -9
- 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/engine/session/memory-repo.d.ts +2 -6
- package/dist/engine/session/session.d.ts +5 -30
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +3 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/run-spec.js +7 -4
- package/dist/orchestration/run-workflow-tool.js +3 -35
- 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.d.ts +3 -11
- package/dist/orchestration/workflow.js +164 -204
- 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.d.ts +10 -9
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/background-agent-store.d.ts +3 -12
- package/dist/stores/file/background-agent-store.js +3 -41
- package/dist/stores/file/checkpoint-store.js +6 -6
- 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/mailbox-store.d.ts +2 -6
- package/dist/stores/file/session-policy-store.js +15 -1
- package/dist/stores/file/session-store.d.ts +4 -6
- package/dist/stores/file/session-store.js +28 -1
- package/dist/stores/file/workflow-journal-store.d.ts +3 -11
- package/dist/stores/file/workflow-run-store.d.ts +3 -7
- package/dist/stores/file/workflow-run-store.js +4 -17
- package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -0
- package/dist/tools/fs/bash-readonly-classifier.js +73 -21
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +407 -91
- package/dist/tools/fs/fs-pdf.d.ts +2 -12
- package/dist/tools/fs/fs-shared.js +2 -2
- package/dist/tools/fs/notebook.d.ts +2 -5
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +43 -14
- package/dist/tools/fs/search.js +5 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.d.ts +1 -0
- package/dist/tools/monitor.js +57 -9
- package/dist/tools/worktree.d.ts +4 -7
- package/dist/tools/worktree.js +235 -143
- package/package.json +9 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
1
2
|
const TAG_RE = /<(tool_call|function_call|tool_calls|function_calls)>\s*([\s\S]*?)\s*<\/\1>/gi;
|
|
2
3
|
const FENCE_RE = /```(?:json|tool_call|tool|function)?\s*([\s\S]*?)```/gi;
|
|
3
4
|
function asRecord(v) {
|
|
4
5
|
return v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
5
6
|
}
|
|
6
|
-
function extractCall(obj, toolNames) {
|
|
7
|
+
function extractCall(obj, toolNames, framed) {
|
|
7
8
|
const rec = asRecord(obj);
|
|
8
9
|
if (!rec) {
|
|
9
10
|
return undefined;
|
|
@@ -13,45 +14,63 @@ function extractCall(obj, toolNames) {
|
|
|
13
14
|
if (typeof name !== "string" || !toolNames.has(name)) {
|
|
14
15
|
return undefined;
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const rawArgs = rec.arguments ?? rec.parameters ?? rec.input ?? rec.args ?? fn?.arguments;
|
|
18
|
+
let args;
|
|
19
|
+
if (rawArgs === undefined) {
|
|
20
|
+
if (!framed)
|
|
21
|
+
return undefined;
|
|
22
|
+
args = {};
|
|
23
|
+
}
|
|
24
|
+
else if (typeof rawArgs === "string") {
|
|
25
|
+
let parsed;
|
|
18
26
|
try {
|
|
19
|
-
|
|
27
|
+
parsed = JSON.parse(rawArgs);
|
|
20
28
|
}
|
|
21
29
|
catch {
|
|
22
|
-
|
|
30
|
+
return undefined;
|
|
23
31
|
}
|
|
32
|
+
const rec2 = asRecord(parsed);
|
|
33
|
+
if (!rec2)
|
|
34
|
+
return undefined;
|
|
35
|
+
args = rec2;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const rec2 = asRecord(rawArgs);
|
|
39
|
+
if (!rec2)
|
|
40
|
+
return undefined;
|
|
41
|
+
args = rec2;
|
|
24
42
|
}
|
|
25
|
-
const args = asRecord(rawArgs) ?? {};
|
|
26
43
|
return {
|
|
27
44
|
type: "toolCall",
|
|
28
|
-
id: `repair_${name}_
|
|
45
|
+
id: mintFallbackToolCallId(`repair_${name}_`),
|
|
29
46
|
name,
|
|
30
47
|
arguments: args,
|
|
31
48
|
};
|
|
32
49
|
}
|
|
33
|
-
function parseBlock(raw, toolNames, out) {
|
|
50
|
+
function parseBlock(raw, toolNames, out, framed) {
|
|
34
51
|
let parsed;
|
|
35
52
|
try {
|
|
36
53
|
parsed = JSON.parse(raw.trim());
|
|
37
54
|
}
|
|
38
55
|
catch {
|
|
39
|
-
return
|
|
56
|
+
return "none";
|
|
40
57
|
}
|
|
41
58
|
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
42
|
-
let
|
|
59
|
+
let promoted = 0;
|
|
43
60
|
for (const item of items) {
|
|
44
|
-
const call = extractCall(item, toolNames);
|
|
61
|
+
const call = extractCall(item, toolNames, framed);
|
|
45
62
|
if (call) {
|
|
46
63
|
out.push(call);
|
|
47
|
-
|
|
64
|
+
promoted++;
|
|
48
65
|
}
|
|
49
66
|
}
|
|
50
|
-
|
|
67
|
+
if (promoted === 0)
|
|
68
|
+
return "none";
|
|
69
|
+
return promoted === items.length ? "all" : "some";
|
|
51
70
|
}
|
|
52
71
|
export function repairTextToolCalls(text, toolNames) {
|
|
53
72
|
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);
|
|
73
|
+
let cleaned = text.replace(TAG_RE, (m, _tag, inner) => parseBlock(inner, toolNames, toolCalls, true) === "all" ? "" : m);
|
|
74
|
+
cleaned = cleaned.replace(FENCE_RE, (m, inner) => parseBlock(inner, toolNames, toolCalls, false) === "all" ? "" : m);
|
|
56
75
|
return { toolCalls, cleanedText: cleaned.trim() };
|
|
57
76
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
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";
|
|
5
5
|
import { SEMA_DEFAULT_PACK } from "../prompt-assembly/packs/sema-default.js";
|
|
6
6
|
import { brainToRuntime } from "./runtime.js";
|
|
7
7
|
import { defuseFenceMarkers, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
8
|
+
import { createSafeNotifier } from "./safe-notify.js";
|
|
8
9
|
export const DEFAULT_COMPACTION_INSTRUCTIONS = "Preserve verbatim every code, identifier, name, number, file path, error message, credential " +
|
|
9
10
|
"reference, and explicit user instruction or decision mentioned anywhere in the conversation. " +
|
|
10
11
|
"When in doubt, keep the specific detail rather than generalizing it.";
|
|
@@ -17,6 +18,10 @@ export function sanitizeCompactionInstructions(text) {
|
|
|
17
18
|
? cps.slice(0, COMPACTION_INSTRUCTIONS_MAX_CHARS).join("")
|
|
18
19
|
: defused;
|
|
19
20
|
}
|
|
21
|
+
const WORKING_FILE_BREAKOUT_TAGS = ["working-file", "summary"];
|
|
22
|
+
function escapeWorkingFilePathAttr(path) {
|
|
23
|
+
return path.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/[\r\n]+/g, " ");
|
|
24
|
+
}
|
|
20
25
|
export const COMPACTION_THROUGHPUT_PRIOR_TOKENS_PER_MS = 3;
|
|
21
26
|
const COMPACTION_THROUGHPUT_EWMA_ALPHA = 0.3;
|
|
22
27
|
const COMPACTION_WALLTIME_ABORT_FLAG = "semaCompactionWalltimeAbort";
|
|
@@ -69,8 +74,11 @@ export async function maybeCompact(opts) {
|
|
|
69
74
|
const anchorStale = est.usageTokens > 0 && est.tokens > structuralTokens * STALE_ANCHOR_STRUCTURAL_MARGIN + overhead;
|
|
70
75
|
const tokens = anchorStale || est.usageTokens === 0 ? structuralTokens + overhead : est.tokens;
|
|
71
76
|
const window = opts.model.autoCompactTokens ?? opts.model.contextTokens ?? opts.model.contextWindow;
|
|
77
|
+
const windowKnown = Number.isFinite(window) && window > 0;
|
|
72
78
|
const settings = sanitizeCompactionSettings(rawSettings, window);
|
|
73
|
-
const contextUsage =
|
|
79
|
+
const contextUsage = windowKnown
|
|
80
|
+
? { usedTokens: tokens, windowTokens: window, compactAtTokens: window - settings.reserveTokens }
|
|
81
|
+
: undefined;
|
|
74
82
|
const wantsCompact = shouldCompact(tokens, window, settings);
|
|
75
83
|
const naturalTrigger = wantsCompact && tokens >= (opts.minTokens ?? 0);
|
|
76
84
|
const force = opts.force || (opts.forceUnderThreshold === true && !naturalTrigger);
|
|
@@ -89,6 +97,7 @@ export async function maybeCompact(opts) {
|
|
|
89
97
|
return { contextUsage, compacted: false, noop: true };
|
|
90
98
|
}
|
|
91
99
|
const trigger = opts.trigger ?? "auto";
|
|
100
|
+
const notifier = createSafeNotifier();
|
|
92
101
|
let hookInstructions;
|
|
93
102
|
if (opts.preCompact) {
|
|
94
103
|
let pre;
|
|
@@ -118,7 +127,7 @@ export async function maybeCompact(opts) {
|
|
|
118
127
|
: hookSanitized
|
|
119
128
|
: specInstructions;
|
|
120
129
|
const maxReuse = opts.maxConsecutiveProviderReuse ?? 3;
|
|
121
|
-
const forceRealSummary = (opts.consecutiveProviderReuse ?? 0) >= maxReuse;
|
|
130
|
+
const forceRealSummary = maxReuse > 0 && (opts.consecutiveProviderReuse ?? 0) >= maxReuse;
|
|
122
131
|
let providerSummary;
|
|
123
132
|
if (opts.summaryProvider && !forceRealSummary && !prep.value.isSplitTurn) {
|
|
124
133
|
const providerModifiedFiles = computeFileLists(prep.value.fileOps).modifiedFilesByRecency;
|
|
@@ -151,7 +160,9 @@ export async function maybeCompact(opts) {
|
|
|
151
160
|
summary = providerSummary;
|
|
152
161
|
firstKeptEntryId = prep.value.firstKeptEntryId;
|
|
153
162
|
summaryTokensBefore = prep.value.tokensBefore;
|
|
154
|
-
|
|
163
|
+
const providerFileLists = computeFileLists(prep.value.fileOps);
|
|
164
|
+
details = providerFileLists;
|
|
165
|
+
summary += formatFileOperations(providerFileLists.readFiles, providerFileLists.modifiedFiles);
|
|
155
166
|
if (prep.value.invokedSkills.length > 0) {
|
|
156
167
|
details.invokedSkills = prep.value.invokedSkills;
|
|
157
168
|
}
|
|
@@ -174,11 +185,7 @@ export async function maybeCompact(opts) {
|
|
|
174
185
|
}
|
|
175
186
|
const contentTokens = dry !== undefined ? Math.ceil((dry.conversationChars + (prep.value.previousSummary?.length ?? 0)) / cpt) : 0;
|
|
176
187
|
const observeWindowSafety = (info) => {
|
|
177
|
-
|
|
178
|
-
opts.onWindowSafety?.(info);
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
}
|
|
188
|
+
notifier.notify(() => opts.onWindowSafety?.(info), "compaction.onWindowSafety");
|
|
182
189
|
};
|
|
183
190
|
if (wantsWindowSafety && dry !== undefined) {
|
|
184
191
|
const tolerance = settings.clampTolerance ?? DEFAULT_CLAMP_TOLERANCE;
|
|
@@ -330,7 +337,7 @@ export async function maybeCompact(opts) {
|
|
|
330
337
|
const rawCandidateFiles = recentlyRead.length > 0 ? recentlyRead : (details?.modifiedFilesByRecency ?? details?.modifiedFiles);
|
|
331
338
|
const maxFilesForSelection = Math.max(1, att?.maxFiles ?? 3);
|
|
332
339
|
const excludedReadStatePreserveKeys = [];
|
|
333
|
-
const candidateFiles = Array.isArray(rawCandidateFiles) && rawCandidateFiles.length > 0
|
|
340
|
+
const candidateFiles = att !== undefined && Array.isArray(rawCandidateFiles) && rawCandidateFiles.length > 0
|
|
334
341
|
? await (async () => {
|
|
335
342
|
const keptTailReadPathsRaw = new Set();
|
|
336
343
|
const firstKeptIdx = branch.findIndex((e) => e.id === firstKeptEntryId);
|
|
@@ -417,7 +424,8 @@ export async function maybeCompact(opts) {
|
|
|
417
424
|
if (att && Array.isArray(candidateFiles) && candidateFiles.length > 0) {
|
|
418
425
|
const maxFiles = maxFilesForSelection;
|
|
419
426
|
const perFileCap = Math.max(200, att.maxCharsPerFile ?? 16_000);
|
|
420
|
-
|
|
427
|
+
const windowShareChars = windowKnown ? Math.floor(window * 0.15) * cpt : Number.POSITIVE_INFINITY;
|
|
428
|
+
let remaining = Math.min(maxFiles * perFileCap, windowShareChars);
|
|
421
429
|
const blocks = [];
|
|
422
430
|
for (const path of candidateFiles.slice(0, maxFiles)) {
|
|
423
431
|
if (remaining <= 0)
|
|
@@ -437,7 +445,7 @@ export async function maybeCompact(opts) {
|
|
|
437
445
|
? `${content.slice(0, cap)}\n[... ${content.length - cap} more characters truncated — read the file for the rest]`
|
|
438
446
|
: content;
|
|
439
447
|
remaining -= clipped.length;
|
|
440
|
-
blocks.push(`<working-file path="${path}">\n${clipped}\n</working-file>`);
|
|
448
|
+
blocks.push(`<working-file path="${escapeWorkingFilePathAttr(path)}">\n${sanitizeUntrustedText(clipped, WORKING_FILE_BREAKOUT_TAGS)}\n</working-file>`);
|
|
441
449
|
attachedFiles.push({ path, chars: Math.min(content.length, cap), truncated: wasClipped });
|
|
442
450
|
if (!wasClipped)
|
|
443
451
|
attachedComplete.push({ path, content });
|
|
@@ -488,27 +496,20 @@ export async function maybeCompact(opts) {
|
|
|
488
496
|
}
|
|
489
497
|
catch {
|
|
490
498
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
}
|
|
499
|
+
const messagesCompacted = prep.value.messagesToSummarize.length;
|
|
500
|
+
notifier.notify(() => opts.onCompaction?.({
|
|
501
|
+
messagesCompacted,
|
|
502
|
+
freedTokens,
|
|
503
|
+
tokensBefore: summaryTokensBefore,
|
|
504
|
+
reused,
|
|
505
|
+
}), "compaction.onCompaction");
|
|
501
506
|
if (opts.postCompact) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
catch {
|
|
511
|
-
}
|
|
507
|
+
await notifier.notifyAsync(() => opts.postCompact?.({
|
|
508
|
+
trigger,
|
|
509
|
+
summary,
|
|
510
|
+
tokensBefore: summaryTokensBefore,
|
|
511
|
+
...(postTriggerTokens !== undefined ? { tokensAfter: postTriggerTokens } : {}),
|
|
512
|
+
}), "compaction.postCompact");
|
|
512
513
|
}
|
|
513
514
|
const endAt = Date.now();
|
|
514
515
|
return {
|
|
@@ -73,18 +73,24 @@ export declare function canAccessAgentRecord(record: Pick<BackgroundAgentRecord,
|
|
|
73
73
|
sessionId?: string;
|
|
74
74
|
}): boolean;
|
|
75
75
|
export declare const STALE_RUNNING_REAP_ATTRIBUTION = "the host process was interrupted while this agent was running \u2014 its in-process state was lost. Check its worktree / output file for partial work before assuming the task landed (stale running row reaped).";
|
|
76
|
+
export interface BackgroundAgentUpdateIfExpect {
|
|
77
|
+
rev: number;
|
|
78
|
+
status?: BackgroundAgentRecord["status"];
|
|
79
|
+
parkedCheckpointToken?: string | null;
|
|
80
|
+
parkClaimId?: string | null;
|
|
81
|
+
}
|
|
82
|
+
export interface BackgroundAgentReapOptions {
|
|
83
|
+
maxAgeMs?: number;
|
|
84
|
+
keep?: number;
|
|
85
|
+
staleRunningMaxAgeMs?: number;
|
|
86
|
+
}
|
|
76
87
|
export interface BackgroundAgentStore {
|
|
77
88
|
put(record: BackgroundAgentRecord): Promise<void>;
|
|
78
89
|
get(handle: string, scope: string): Promise<BackgroundAgentRecord | null>;
|
|
79
90
|
update(handle: string, scope: string, record: BackgroundAgentRecord, expect: {
|
|
80
91
|
rev: number;
|
|
81
92
|
}): Promise<boolean>;
|
|
82
|
-
updateIf(handle: string, scope: string, record: BackgroundAgentRecord, expect:
|
|
83
|
-
rev: number;
|
|
84
|
-
status?: BackgroundAgentRecord["status"];
|
|
85
|
-
parkedCheckpointToken?: string | null;
|
|
86
|
-
parkClaimId?: string | null;
|
|
87
|
-
}): Promise<boolean>;
|
|
93
|
+
updateIf(handle: string, scope: string, record: BackgroundAgentRecord, expect: BackgroundAgentUpdateIfExpect): Promise<boolean>;
|
|
88
94
|
listBySession(scope: string, sessionId: string, opts?: {
|
|
89
95
|
status?: BackgroundAgentRecord["status"];
|
|
90
96
|
limit?: number;
|
|
@@ -97,11 +103,7 @@ export interface BackgroundAgentStore {
|
|
|
97
103
|
rev: number;
|
|
98
104
|
}): Promise<boolean>;
|
|
99
105
|
listScopes?(): Promise<string[]>;
|
|
100
|
-
reap(scope: string, now: number, opts?:
|
|
101
|
-
maxAgeMs?: number;
|
|
102
|
-
keep?: number;
|
|
103
|
-
staleRunningMaxAgeMs?: number;
|
|
104
|
-
}): Promise<number>;
|
|
106
|
+
reap(scope: string, now: number, opts?: BackgroundAgentReapOptions): Promise<number>;
|
|
105
107
|
}
|
|
106
108
|
export declare function reconcileParkedAgents(stores: {
|
|
107
109
|
agentStore: BackgroundAgentStore;
|
|
@@ -114,6 +116,13 @@ export declare function reconcileParkedAgents(stores: {
|
|
|
114
116
|
failed: number;
|
|
115
117
|
rolledBack: number;
|
|
116
118
|
}>;
|
|
119
|
+
export interface BackgroundAgentQuery {
|
|
120
|
+
scope: string;
|
|
121
|
+
sessionId?: string;
|
|
122
|
+
status?: BackgroundAgentRecord["status"];
|
|
123
|
+
limit?: number;
|
|
124
|
+
}
|
|
125
|
+
export declare function queryBackgroundAgents(rows: Iterable<BackgroundAgentRecord>, query: BackgroundAgentQuery): BackgroundAgentRowSummary[];
|
|
117
126
|
export declare class InMemoryBackgroundAgentStore implements BackgroundAgentStore {
|
|
118
127
|
private rows;
|
|
119
128
|
private key;
|
|
@@ -122,12 +131,7 @@ export declare class InMemoryBackgroundAgentStore implements BackgroundAgentStor
|
|
|
122
131
|
update(handle: string, scope: string, record: BackgroundAgentRecord, expect: {
|
|
123
132
|
rev: number;
|
|
124
133
|
}): Promise<boolean>;
|
|
125
|
-
updateIf(handle: string, scope: string, record: BackgroundAgentRecord, expect:
|
|
126
|
-
rev: number;
|
|
127
|
-
status?: BackgroundAgentRecord["status"];
|
|
128
|
-
parkedCheckpointToken?: string | null;
|
|
129
|
-
parkClaimId?: string | null;
|
|
130
|
-
}): Promise<boolean>;
|
|
134
|
+
updateIf(handle: string, scope: string, record: BackgroundAgentRecord, expect: BackgroundAgentUpdateIfExpect): Promise<boolean>;
|
|
131
135
|
listBySession(scope: string, sessionId: string, opts?: {
|
|
132
136
|
status?: BackgroundAgentRecord["status"];
|
|
133
137
|
limit?: number;
|
|
@@ -140,9 +144,5 @@ export declare class InMemoryBackgroundAgentStore implements BackgroundAgentStor
|
|
|
140
144
|
rev: number;
|
|
141
145
|
}): Promise<boolean>;
|
|
142
146
|
listScopes(): Promise<string[]>;
|
|
143
|
-
reap(scope: string, now: number, opts?:
|
|
144
|
-
maxAgeMs?: number;
|
|
145
|
-
keep?: number;
|
|
146
|
-
staleRunningMaxAgeMs?: number;
|
|
147
|
-
}): Promise<number>;
|
|
147
|
+
reap(scope: string, now: number, opts?: BackgroundAgentReapOptions): Promise<number>;
|
|
148
148
|
}
|
|
@@ -173,6 +173,23 @@ function toSummary(r) {
|
|
|
173
173
|
...(r.usage !== undefined ? { usage: structuredClone(r.usage) } : {}),
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
|
+
export function queryBackgroundAgents(rows, query) {
|
|
177
|
+
const out = [];
|
|
178
|
+
for (const r of rows) {
|
|
179
|
+
if (r.scope !== query.scope)
|
|
180
|
+
continue;
|
|
181
|
+
if (query.sessionId !== undefined) {
|
|
182
|
+
const anchor = r.sessionScoped ? r.owner : r.parentSessionId;
|
|
183
|
+
if (anchor !== query.sessionId && r.rootSessionId !== query.sessionId)
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (query.status !== undefined && r.status !== query.status)
|
|
187
|
+
continue;
|
|
188
|
+
out.push(toSummary(r));
|
|
189
|
+
}
|
|
190
|
+
out.sort((a, b) => b.spawnedAt - a.spawnedAt);
|
|
191
|
+
return query.limit !== undefined ? out.slice(0, Math.max(0, query.limit)) : out;
|
|
192
|
+
}
|
|
176
193
|
export class InMemoryBackgroundAgentStore {
|
|
177
194
|
rows = new Map();
|
|
178
195
|
key(handle, scope) {
|
|
@@ -220,31 +237,10 @@ export class InMemoryBackgroundAgentStore {
|
|
|
220
237
|
return this.update(handle, scope, record, { rev: expect.rev });
|
|
221
238
|
}
|
|
222
239
|
async listBySession(scope, sessionId, opts) {
|
|
223
|
-
|
|
224
|
-
for (const r of this.rows.values()) {
|
|
225
|
-
if (r.scope !== scope)
|
|
226
|
-
continue;
|
|
227
|
-
const anchor = r.sessionScoped ? r.owner : r.parentSessionId;
|
|
228
|
-
if (anchor !== sessionId && r.rootSessionId !== sessionId)
|
|
229
|
-
continue;
|
|
230
|
-
if (opts?.status !== undefined && r.status !== opts.status)
|
|
231
|
-
continue;
|
|
232
|
-
out.push(toSummary(r));
|
|
233
|
-
}
|
|
234
|
-
out.sort((a, b) => b.spawnedAt - a.spawnedAt);
|
|
235
|
-
return opts?.limit !== undefined ? out.slice(0, Math.max(0, opts.limit)) : out;
|
|
240
|
+
return queryBackgroundAgents(this.rows.values(), { scope, sessionId, status: opts?.status, limit: opts?.limit });
|
|
236
241
|
}
|
|
237
242
|
async listByScope(scope, opts) {
|
|
238
|
-
|
|
239
|
-
for (const r of this.rows.values()) {
|
|
240
|
-
if (r.scope !== scope)
|
|
241
|
-
continue;
|
|
242
|
-
if (opts?.status !== undefined && r.status !== opts.status)
|
|
243
|
-
continue;
|
|
244
|
-
out.push(toSummary(r));
|
|
245
|
-
}
|
|
246
|
-
out.sort((a, b) => b.spawnedAt - a.spawnedAt);
|
|
247
|
-
return opts?.limit !== undefined ? out.slice(0, Math.max(0, opts.limit)) : out;
|
|
243
|
+
return queryBackgroundAgents(this.rows.values(), { scope, status: opts?.status, limit: opts?.limit });
|
|
248
244
|
}
|
|
249
245
|
async delete(handle, scope, expect) {
|
|
250
246
|
const k = this.key(handle, scope);
|
|
@@ -3,7 +3,8 @@ import type { RepairBundle } from "../agents/repair-loop.js";
|
|
|
3
3
|
import type { WorkspaceHandle } from "./remote-env.js";
|
|
4
4
|
import type { ConsolidationNote } from "./runner/memory-consolidation.js";
|
|
5
5
|
import type { SessionPermissionRules } from "./session-policy-store.js";
|
|
6
|
-
import type { TaskResult } from "./types.js";
|
|
6
|
+
import type { NestedUsageAccum, TaskResult } from "./types.js";
|
|
7
|
+
import type { ActiveWorktreeSession } from "../internal/harness-types.js";
|
|
7
8
|
export type CheckpointToken = string & {
|
|
8
9
|
readonly __brand: "CheckpointToken";
|
|
9
10
|
};
|
|
@@ -30,6 +31,7 @@ export declare function riskSeverity(axes: {
|
|
|
30
31
|
shell?: boolean;
|
|
31
32
|
}): 1 | 2 | 3 | 4 | 5;
|
|
32
33
|
export declare const MAX_TOOL_INPUT_PREVIEW_CHARS = 512;
|
|
34
|
+
export declare const MAX_PENDING_STEER_CHARS = 16000;
|
|
33
35
|
export declare function buildRiskDescriptor(input: {
|
|
34
36
|
toolName: string;
|
|
35
37
|
args: unknown;
|
|
@@ -111,12 +113,7 @@ export interface CheckpointState {
|
|
|
111
113
|
value?: unknown;
|
|
112
114
|
set?: boolean;
|
|
113
115
|
};
|
|
114
|
-
nestedStats:
|
|
115
|
-
tokens: number;
|
|
116
|
-
turns: number;
|
|
117
|
-
tasks: number;
|
|
118
|
-
costMicroUsd: number;
|
|
119
|
-
};
|
|
116
|
+
nestedStats: NestedUsageAccum;
|
|
120
117
|
consolidationNotes?: ConsolidationNote[];
|
|
121
118
|
readFileState?: Array<[string, ReadEntry]>;
|
|
122
119
|
repairBundle?: RepairBundle;
|
|
@@ -130,12 +127,7 @@ export interface CheckpointState {
|
|
|
130
127
|
description?: string;
|
|
131
128
|
}>;
|
|
132
129
|
handsCwd?: string;
|
|
133
|
-
activeWorktree?:
|
|
134
|
-
worktreeDir: string;
|
|
135
|
-
originalCwd: string;
|
|
136
|
-
baseSha: string;
|
|
137
|
-
entered?: boolean;
|
|
138
|
-
};
|
|
130
|
+
activeWorktree?: ActiveWorktreeSession;
|
|
139
131
|
inheritedGate?: {
|
|
140
132
|
ancestorRules?: ReadonlyArray<{
|
|
141
133
|
sessionId: string;
|
|
@@ -272,6 +264,8 @@ export declare function validatePendingSteer(steer: {
|
|
|
272
264
|
text: string;
|
|
273
265
|
trusted: boolean;
|
|
274
266
|
};
|
|
267
|
+
export declare function checkpointRowMatches(cp: Checkpoint | undefined, scope: string, status: Checkpoint["status"]): cp is Checkpoint;
|
|
268
|
+
export declare function checkpointOccMatches(cp: Checkpoint, expect?: ResolveExpectation): boolean;
|
|
275
269
|
export type CheckpointFaultMode = "resolve-after-commit" | "resolve-before-commit";
|
|
276
270
|
export declare class InMemoryCheckpointStore implements CheckpointStore {
|
|
277
271
|
private cps;
|
|
@@ -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,8 +232,17 @@ 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
|
}
|
|
240
|
+
export function checkpointRowMatches(cp, scope, status) {
|
|
241
|
+
return cp !== undefined && cp.scope === scope && cp.status === status;
|
|
242
|
+
}
|
|
243
|
+
export function checkpointOccMatches(cp, expect) {
|
|
244
|
+
return expect === undefined || (cp.rev ?? 0) === expect.rev;
|
|
245
|
+
}
|
|
222
246
|
export class InMemoryCheckpointStore {
|
|
223
247
|
cps = new Map();
|
|
224
248
|
fault = null;
|
|
@@ -238,10 +262,10 @@ export class InMemoryCheckpointStore {
|
|
|
238
262
|
throw new Error("injected fault: resolve before commit");
|
|
239
263
|
}
|
|
240
264
|
const cp = this.cps.get(token);
|
|
241
|
-
if (!cp
|
|
265
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
242
266
|
return false;
|
|
243
267
|
}
|
|
244
|
-
if (
|
|
268
|
+
if (!checkpointOccMatches(cp, expect)) {
|
|
245
269
|
return false;
|
|
246
270
|
}
|
|
247
271
|
cp.status = "resolved";
|
|
@@ -258,7 +282,7 @@ export class InMemoryCheckpointStore {
|
|
|
258
282
|
}
|
|
259
283
|
async reopen(token, scope, reason) {
|
|
260
284
|
const cp = this.cps.get(token);
|
|
261
|
-
if (!cp
|
|
285
|
+
if (!checkpointRowMatches(cp, scope, "resolved")) {
|
|
262
286
|
return false;
|
|
263
287
|
}
|
|
264
288
|
cp.status = "pending";
|
|
@@ -269,7 +293,7 @@ export class InMemoryCheckpointStore {
|
|
|
269
293
|
async setPendingSteer(token, scope, steer) {
|
|
270
294
|
const clean = validatePendingSteer(steer);
|
|
271
295
|
const cp = this.cps.get(token);
|
|
272
|
-
if (!cp
|
|
296
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
273
297
|
return false;
|
|
274
298
|
}
|
|
275
299
|
cp.state.pendingSteer = clean;
|
|
@@ -277,7 +301,7 @@ export class InMemoryCheckpointStore {
|
|
|
277
301
|
}
|
|
278
302
|
async expire(token, scope) {
|
|
279
303
|
const cp = this.cps.get(token);
|
|
280
|
-
if (!cp
|
|
304
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
281
305
|
return false;
|
|
282
306
|
}
|
|
283
307
|
cp.status = "expired";
|
|
@@ -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
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
declare function relUnder(root: string, child: string): string | null;
|
|
2
|
+
declare function joinUnder(root: string, rel: string): string;
|
|
1
3
|
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
2
4
|
export interface FileSnapshotError {
|
|
3
5
|
code: "too_large" | "enumerate_failed" | "read_failed" | "restore_failed" | "not_found" | "aborted";
|
|
@@ -51,3 +53,9 @@ export declare class InMemoryFileSnapshotStore implements FileSnapshotStore {
|
|
|
51
53
|
importManifest(scope: string, key: string, manifest: Map<string, string>, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileSnapshotResult>;
|
|
52
54
|
reap(scope: string, keepKeys: string[]): Promise<number>;
|
|
53
55
|
}
|
|
56
|
+
export declare const __testables: {
|
|
57
|
+
joinUnder: typeof joinUnder;
|
|
58
|
+
relUnder: typeof relUnder;
|
|
59
|
+
stripTrailSep: (p: string) => string;
|
|
60
|
+
};
|
|
61
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAbsolutePathForm, isWinFormPath } from "../tools/fs/safety.js";
|
|
2
3
|
const normSlash = (p) => p.replace(/\\/g, "/");
|
|
3
|
-
const stripTrailSep = (p) => p.replace(/[\\/]+$/, "");
|
|
4
|
+
const stripTrailSep = (p) => (isWinFormPath(p) ? p.replace(/[\\/]+$/, "") : p.replace(/\/+$/, ""));
|
|
4
5
|
const foldDrive = (p) => (/^[A-Za-z]:(\/|$)/.test(p) ? p[0].toLowerCase() + p.slice(1) : p);
|
|
5
|
-
const isWinFormPath = (p) => /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("\\\\") || (!p.startsWith("/") && p.includes("\\"));
|
|
6
6
|
function relUnder(root, child) {
|
|
7
7
|
const fold = isWinFormPath(root) ? normSlash : (p) => p;
|
|
8
8
|
const r = foldDrive(fold(stripTrailSep(root)));
|
|
@@ -18,7 +18,7 @@ function joinUnder(root, rel) {
|
|
|
18
18
|
return stripTrailSep(root) + (win ? "\\" : "/") + (win ? rel.replace(/\//g, "\\") : rel);
|
|
19
19
|
}
|
|
20
20
|
function isUnsafeRelPath(relN) {
|
|
21
|
-
return relN
|
|
21
|
+
return isAbsolutePathForm(relN) || /^[A-Za-z]:/.test(relN) || relN.split("/").includes("..");
|
|
22
22
|
}
|
|
23
23
|
export const DEFAULT_SNAPSHOT_BOUNDS = {
|
|
24
24
|
maxFiles: 10000,
|
|
@@ -256,3 +256,4 @@ export class InMemoryFileSnapshotStore {
|
|
|
256
256
|
return removed;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
+
export const __testables = { joinUnder, relUnder, stripTrailSep };
|
|
@@ -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
|
+
}>;
|
|
@@ -59,5 +59,16 @@ export async function addWorktree(baseEnv, opts) {
|
|
|
59
59
|
return { env, worktreeDir };
|
|
60
60
|
}
|
|
61
61
|
export async function pruneWorktrees(baseEnv, repoRoot) {
|
|
62
|
-
|
|
62
|
+
try {
|
|
63
|
+
const r = await baseEnv.exec("git worktree prune", { cwd: repoRoot });
|
|
64
|
+
if (r.ok && r.value.exitCode === 0)
|
|
65
|
+
return { ok: true };
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
detail: r.ok ? (r.value.stderr || r.value.stdout || `exit ${r.value.exitCode}`).trim() : String(r.error),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
return { ok: false, detail: String(err) };
|
|
73
|
+
}
|
|
63
74
|
}
|