@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
package/dist/core/tool-errors.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Value } from "typebox/value";
|
|
2
2
|
const MAX_ERROR_CHARS = 10_000;
|
|
3
3
|
const HALF = MAX_ERROR_CHARS / 2;
|
|
4
|
+
import { isHighSurrogate, isLowSurrogate, sliceHeadSafe, sliceTailSafe } from "./surrogate-safe-slice.js";
|
|
4
5
|
export function truncateError(s) {
|
|
5
6
|
if (s.length <= MAX_ERROR_CHARS)
|
|
6
7
|
return s;
|
|
7
|
-
const
|
|
8
|
-
|
|
8
|
+
const head = sliceHeadSafe(s, HALF);
|
|
9
|
+
const tail = sliceTailSafe(s, HALF);
|
|
10
|
+
const removed = s.length - head.length - tail.length;
|
|
11
|
+
return `${head}\n\n... [${removed} characters truncated] ...\n\n${tail}`;
|
|
9
12
|
}
|
|
10
|
-
const isHighSurrogate = (c) => c >= 0xd800 && c <= 0xdbff;
|
|
11
|
-
const isLowSurrogate = (c) => c >= 0xdc00 && c <= 0xdfff;
|
|
12
13
|
export function clipWithFilePointer(s, limit, fullOutputPath) {
|
|
13
14
|
if (s.length <= limit)
|
|
14
15
|
return s;
|
|
@@ -52,7 +52,8 @@ export const TOMBSTONED_TOOLS = {
|
|
|
52
52
|
const MCP_PREFIX = "mcp__";
|
|
53
53
|
const MCP_NAME_SEP = "__";
|
|
54
54
|
export function canonicalToolName(name) {
|
|
55
|
-
const
|
|
55
|
+
const mapped = Object.hasOwn(OLD_TO_NEW, name) ? OLD_TO_NEW[name] : undefined;
|
|
56
|
+
const renamed = typeof mapped === "string" ? mapped : name;
|
|
56
57
|
if (renamed.includes(MCP_NAME_SEP) && !renamed.startsWith(MCP_PREFIX))
|
|
57
58
|
return MCP_PREFIX + renamed;
|
|
58
59
|
return renamed;
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import { isAbsolute, join, sep } from "node:path";
|
|
2
|
+
import { isAbsolute, join, normalize as normalizePath, sep } from "node:path";
|
|
3
3
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
4
|
+
import { writeTargetPath } from "../tools/fs/safety.js";
|
|
4
5
|
import { canonicalToolName } from "./tool-name-aliases.js";
|
|
5
6
|
export function decisionText(d) {
|
|
6
7
|
return d.message ?? d.reason;
|
|
7
8
|
}
|
|
8
9
|
const ALLOW = { action: "allow" };
|
|
9
10
|
function withTimeout(p, ms, onTimeout) {
|
|
10
|
-
if (
|
|
11
|
+
if (ms === undefined) {
|
|
11
12
|
return p;
|
|
12
13
|
}
|
|
13
14
|
return new Promise((resolve, reject) => {
|
|
@@ -24,7 +25,6 @@ function withTimeout(p, ms, onTimeout) {
|
|
|
24
25
|
return;
|
|
25
26
|
settle(resolve, onTimeout());
|
|
26
27
|
}, ms);
|
|
27
|
-
timer.unref?.();
|
|
28
28
|
p.then((v) => settle(resolve, v), (e) => settle(reject, e));
|
|
29
29
|
});
|
|
30
30
|
}
|
|
@@ -50,6 +50,9 @@ export function createAllowDenyPolicy(opts) {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
export function createApprovalPolicy(opts) {
|
|
53
|
+
if (opts.approvalTimeoutMs !== undefined && !Number.isFinite(opts.approvalTimeoutMs)) {
|
|
54
|
+
throw new Error(`createApprovalPolicy: approvalTimeoutMs must be a finite number of ms (got ${opts.approvalTimeoutMs}) — omit it to wait indefinitely`);
|
|
55
|
+
}
|
|
53
56
|
const need = new Set(opts.requireApproval.map(canonicalToolName));
|
|
54
57
|
const deny = new Set((opts.deny ?? []).map(canonicalToolName));
|
|
55
58
|
const auto = new Set((opts.autoAllow ?? []).map(canonicalToolName));
|
|
@@ -106,7 +109,7 @@ export function combinePolicies(...policies) {
|
|
|
106
109
|
current = { ...current, args: d.updatedInput };
|
|
107
110
|
rewrite = d;
|
|
108
111
|
}
|
|
109
|
-
if (d.action === "ask" &&
|
|
112
|
+
if (d.action === "ask" && (asked === undefined || (d.requiresRealApproval === true && asked.requiresRealApproval !== true))) {
|
|
110
113
|
asked = d;
|
|
111
114
|
}
|
|
112
115
|
}
|
|
@@ -119,10 +122,13 @@ export function combinePolicies(...policies) {
|
|
|
119
122
|
};
|
|
120
123
|
}
|
|
121
124
|
export const COARSE_SHELL_TOOLS = ["Bash", "Monitor"];
|
|
125
|
+
function canonicalToolNameSet(tools) {
|
|
126
|
+
return new Set((tools ?? COARSE_SHELL_TOOLS).map(canonicalToolName));
|
|
127
|
+
}
|
|
122
128
|
export function createCoarseCommandNamePolicy(opts) {
|
|
123
129
|
const allow = opts.allow ? new Set(opts.allow) : undefined;
|
|
124
130
|
const deny = new Set(opts.deny ?? []);
|
|
125
|
-
const shellTools =
|
|
131
|
+
const shellTools = canonicalToolNameSet(opts.tools);
|
|
126
132
|
const defaultAction = opts.defaultAction ?? "ask";
|
|
127
133
|
const fallback = (reason) => defaultAction === "deny"
|
|
128
134
|
? { action: "deny", reason, decisionReason: "rule" }
|
|
@@ -154,6 +160,7 @@ const DELETE_HEADS = new Set(["rm", "xargs", "Remove-Item"]);
|
|
|
154
160
|
const DELETE_WRAPPERS = new Set([
|
|
155
161
|
"sudo", "command", "nohup", "time", "do", "then", "else",
|
|
156
162
|
"env", "nice", "ionice", "exec", "doas", "setsid", "stdbuf", "chroot", "eval",
|
|
163
|
+
"sh", "bash", "zsh", "dash", "ksh", "ash", "timeout", "case",
|
|
157
164
|
]);
|
|
158
165
|
const STATEMENT_SPLIT = /[;&|\n]+/;
|
|
159
166
|
const stripTokenEdges = (t) => t.replace(/^[("'{]+|[)"'}]+$/g, "");
|
|
@@ -209,7 +216,7 @@ export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVE
|
|
|
209
216
|
}
|
|
210
217
|
return false;
|
|
211
218
|
};
|
|
212
|
-
const statements = command.split(STATEMENT_SPLIT);
|
|
219
|
+
const statements = command.replace(/\\\r?\n/g, " ").split(STATEMENT_SPLIT);
|
|
213
220
|
for (const stmt of statements) {
|
|
214
221
|
const tokens = stmt.trim().split(/\s+/).filter(Boolean);
|
|
215
222
|
let i = 0;
|
|
@@ -230,6 +237,10 @@ export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVE
|
|
|
230
237
|
let sawWrapper = false;
|
|
231
238
|
while (i < tokens.length) {
|
|
232
239
|
const t = stripTokenEdges(tokens[i]).replace(/^\\/, "");
|
|
240
|
+
if (t === "") {
|
|
241
|
+
i++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
233
244
|
if (DELETE_WRAPPERS.has(t)) {
|
|
234
245
|
sawWrapper = true;
|
|
235
246
|
i++;
|
|
@@ -237,8 +248,9 @@ export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVE
|
|
|
237
248
|
i++;
|
|
238
249
|
continue;
|
|
239
250
|
}
|
|
240
|
-
if (
|
|
241
|
-
|
|
251
|
+
if (/\)$/.test(tokens[i]) && !DELETE_HEADS.has(t)) {
|
|
252
|
+
sawWrapper = true;
|
|
253
|
+
i++;
|
|
242
254
|
continue;
|
|
243
255
|
}
|
|
244
256
|
break;
|
|
@@ -368,7 +380,7 @@ function hasRecursiveForce(argvTail) {
|
|
|
368
380
|
}
|
|
369
381
|
export function createUnverifiableDeletePolicy(opts) {
|
|
370
382
|
const safeVars = new Set([...UNVERIFIABLE_DELETE_SAFE_VARS, ...(opts?.safeVars ?? [])]);
|
|
371
|
-
const shellTools =
|
|
383
|
+
const shellTools = canonicalToolNameSet(opts?.tools);
|
|
372
384
|
return {
|
|
373
385
|
check(req) {
|
|
374
386
|
if (!shellTools.has(canonicalToolName(req.toolName)))
|
|
@@ -390,10 +402,30 @@ export function createUnverifiableDeletePolicy(opts) {
|
|
|
390
402
|
},
|
|
391
403
|
};
|
|
392
404
|
}
|
|
405
|
+
function pathSegments(p) {
|
|
406
|
+
return p.split(/[\\/]/).filter(Boolean);
|
|
407
|
+
}
|
|
408
|
+
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
409
|
+
const foldPathCase = (s) => (CASE_INSENSITIVE_FS ? s.toLowerCase() : s);
|
|
410
|
+
function lexicalPath(p, home) {
|
|
411
|
+
const norm = p.normalize("NFC");
|
|
412
|
+
const expanded = norm === "~" ? home : /^~[\\/]/.test(norm) ? join(home, norm.slice(2)) : norm;
|
|
413
|
+
return normalizePath(expanded);
|
|
414
|
+
}
|
|
415
|
+
const RELATIVE_TAIL_MIN_SEGMENTS = 2;
|
|
416
|
+
function relativeContinuesDirTail(segs, dirSegs) {
|
|
417
|
+
const from = Math.min(RELATIVE_TAIL_MIN_SEGMENTS, dirSegs.length);
|
|
418
|
+
for (let k = from; k <= Math.min(segs.length, dirSegs.length); k++) {
|
|
419
|
+
const tail = dirSegs.slice(dirSegs.length - k);
|
|
420
|
+
if (tail.every((s, i) => s === segs[i]))
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
393
425
|
export function createTranscriptIntegrityPolicy(opts) {
|
|
394
426
|
const home = homedir();
|
|
395
427
|
const defaultRoot = (process.env.AGENT_DATA_DIR ?? join(home, ".ai-agent")).normalize("NFC");
|
|
396
|
-
const dirs = (opts?.sessionsDirs ?? [join(defaultRoot, "sessions")]).map((d) => d
|
|
428
|
+
const dirs = (opts?.sessionsDirs ?? [join(defaultRoot, "sessions")]).map((d) => lexicalPath(d, home).replace(/(?<=.)[\\/]+$/, ""));
|
|
397
429
|
const textualForms = dirs.flatMap((d) => {
|
|
398
430
|
const forms = [d];
|
|
399
431
|
if (d.startsWith(home + sep)) {
|
|
@@ -403,13 +435,35 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
403
435
|
return forms;
|
|
404
436
|
});
|
|
405
437
|
const readAllow = new Set(opts?.readAllow ?? BASH_READONLY_DEFAULT_ALLOW);
|
|
406
|
-
const shellTools =
|
|
438
|
+
const shellTools = canonicalToolNameSet(opts?.tools);
|
|
439
|
+
const dirSegs = dirs.map((d) => pathSegments(d).map(foldPathCase));
|
|
407
440
|
const inProtectedDir = (p) => {
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
if (
|
|
411
|
-
return
|
|
412
|
-
|
|
441
|
+
const abs = lexicalPath(p, home);
|
|
442
|
+
const segs = pathSegments(abs).map(foldPathCase);
|
|
443
|
+
if (isAbsolute(abs)) {
|
|
444
|
+
return dirSegs.some((d) => d.length <= segs.length && d.every((s, i) => s === segs[i]));
|
|
445
|
+
}
|
|
446
|
+
return dirSegs.some((d) => relativeContinuesDirTail(segs, d));
|
|
447
|
+
};
|
|
448
|
+
const referencesProtectedDir = (cmd) => {
|
|
449
|
+
const hay = foldPathCase(cmd);
|
|
450
|
+
if (textualForms.some((f) => hay.includes(foldPathCase(f))))
|
|
451
|
+
return true;
|
|
452
|
+
for (const raw of cmd.split(/[\s;&|<>()]+/)) {
|
|
453
|
+
if (raw === "")
|
|
454
|
+
continue;
|
|
455
|
+
const unquoted = raw.replace(/["'`]/g, "");
|
|
456
|
+
if (unquoted === "")
|
|
457
|
+
continue;
|
|
458
|
+
const expanded = unquoted.startsWith("${HOME}")
|
|
459
|
+
? home + unquoted.slice("${HOME}".length)
|
|
460
|
+
: unquoted.startsWith("$HOME")
|
|
461
|
+
? home + unquoted.slice("$HOME".length)
|
|
462
|
+
: unquoted;
|
|
463
|
+
if (inProtectedDir(expanded))
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
return false;
|
|
413
467
|
};
|
|
414
468
|
const askReason = (what) => ({
|
|
415
469
|
action: "ask",
|
|
@@ -428,7 +482,7 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
428
482
|
if (typeof command !== "string")
|
|
429
483
|
return ALLOW;
|
|
430
484
|
const cmd = command.normalize("NFC");
|
|
431
|
-
if (!
|
|
485
|
+
if (!referencesProtectedDir(cmd))
|
|
432
486
|
return ALLOW;
|
|
433
487
|
const parsed = parseLeadingCommandName(command);
|
|
434
488
|
if ("name" in parsed && readAllow.has(parsed.name))
|
|
@@ -438,8 +492,7 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
438
492
|
: "the command references the transcript directory and is not a single read-only command (fail-closed)");
|
|
439
493
|
}
|
|
440
494
|
if (toolName === "Write" || toolName === "Edit" || toolName === "NotebookEdit") {
|
|
441
|
-
const
|
|
442
|
-
const p = a?.file_path ?? a?.notebook_path;
|
|
495
|
+
const p = writeTargetPath(toolName, req.args);
|
|
443
496
|
if (typeof p === "string" && inProtectedDir(p)) {
|
|
444
497
|
return askReason(`"${p}" is inside the session-transcript directory`);
|
|
445
498
|
}
|
|
@@ -45,6 +45,7 @@ export declare class ScopedToolResultStore implements ToolResultStore {
|
|
|
45
45
|
}
|
|
46
46
|
export declare function isVolatileOffloadStore(store: ToolResultStore): boolean;
|
|
47
47
|
export declare const OFFLOAD_TOOL_NAME = "ReadToolResult";
|
|
48
|
+
export declare function createOffloadPersist(store: ToolResultStore, sessionId: string): (toolCallId: string, fullText: string) => string;
|
|
48
49
|
export declare function offloadPagebackHint(ref: string, form: "preview" | "cleared", reachableTools?: ReadonlySet<string>): string;
|
|
49
50
|
export declare const PERSISTED_OUTPUT_PREFIX = "<persisted-output ref=";
|
|
50
51
|
export declare const DEFAULT_TOOL_RESULT_THRESHOLD_CHARS = 20000;
|
|
@@ -29,6 +29,10 @@ export class InMemoryToolResultStore {
|
|
|
29
29
|
totalChars = 0;
|
|
30
30
|
constructor(opts) {
|
|
31
31
|
this.opts = opts;
|
|
32
|
+
const cap = opts?.maxTotalChars;
|
|
33
|
+
if (cap !== undefined && Number.isNaN(cap)) {
|
|
34
|
+
throw new Error("InMemoryToolResultStore: maxTotalChars must be a number (got NaN) — omit it for an unbounded store");
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
put(ref, content) {
|
|
34
38
|
assertSafeToolResultRef(ref);
|
|
@@ -37,7 +41,7 @@ export class InMemoryToolResultStore {
|
|
|
37
41
|
this.map.set(ref, content);
|
|
38
42
|
this.totalChars += content.length;
|
|
39
43
|
const cap = this.opts?.maxTotalChars;
|
|
40
|
-
if (cap !== undefined && cap
|
|
44
|
+
if (cap !== undefined && Number.isFinite(cap)) {
|
|
41
45
|
for (const [oldRef, oldContent] of this.map) {
|
|
42
46
|
if (this.totalChars <= cap || oldRef === ref)
|
|
43
47
|
break;
|
|
@@ -93,6 +97,16 @@ export function isVolatileOffloadStore(store) {
|
|
|
93
97
|
return store instanceof InMemoryToolResultStore;
|
|
94
98
|
}
|
|
95
99
|
export const OFFLOAD_TOOL_NAME = "ReadToolResult";
|
|
100
|
+
export function createOffloadPersist(store, sessionId) {
|
|
101
|
+
return (toolCallId, fullText) => {
|
|
102
|
+
const ref = buildToolResultRef(sessionId, toolCallId);
|
|
103
|
+
void Promise.resolve(store.put(ref, fullText)).catch((err) => {
|
|
104
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
105
|
+
void Promise.resolve(store.put(ref, `[offload LOST at write time: ${cause} — the inline preview is all that survived]`)).catch(() => undefined);
|
|
106
|
+
});
|
|
107
|
+
return ref;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
96
110
|
export function offloadPagebackHint(ref, form, reachableTools) {
|
|
97
111
|
const activate = reachableTools !== undefined && !reachableTools.has(OFFLOAD_TOOL_NAME)
|
|
98
112
|
? `${OFFLOAD_TOOL_NAME} is not active yet — call ${TOOL_SEARCH_NAME} with {"query":"select:${OFFLOAD_TOOL_NAME}"} to activate it, then `
|
|
@@ -144,6 +158,8 @@ export function buildPreview(full, ref, sizes, reachableTools) {
|
|
|
144
158
|
`</persisted-output>`);
|
|
145
159
|
}
|
|
146
160
|
export function withToolResultOffload(tool, store, thresholdChars, sessionId, reachableTools) {
|
|
161
|
+
if (!(Number.isFinite(thresholdChars) && thresholdChars > 0))
|
|
162
|
+
return tool;
|
|
147
163
|
const wrappedExecute = async (toolCallId, params, signal, onUpdate) => {
|
|
148
164
|
const res = await tool.execute(toolCallId, params, signal, onUpdate);
|
|
149
165
|
if (totalTextChars(res.content) <= thresholdChars)
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TaskStatus, ToolEffect } from "./types.js";
|
|
2
2
|
import type { ThinkingLevel } from "../internal/harness-types.js";
|
|
3
|
+
import type { ToolManifestRow } from "../prompt-assembly/tool-catalog.js";
|
|
3
4
|
export interface TraceMechanismsSummary {
|
|
4
5
|
finalizeInjected?: true;
|
|
5
6
|
nudgesSent?: number;
|
|
@@ -42,15 +43,7 @@ export type TraceEvent = {
|
|
|
42
43
|
hash: string;
|
|
43
44
|
contentHash?: string;
|
|
44
45
|
}>;
|
|
45
|
-
tools?:
|
|
46
|
-
wireName: string;
|
|
47
|
-
aliases: string[];
|
|
48
|
-
contractId: string;
|
|
49
|
-
implementationRevision: string;
|
|
50
|
-
cardId: string;
|
|
51
|
-
shapeDigest: string;
|
|
52
|
-
wireSchemaDigest: string;
|
|
53
|
-
}>;
|
|
46
|
+
tools?: ToolManifestRow[];
|
|
54
47
|
totalChars: number;
|
|
55
48
|
snapshot?: {
|
|
56
49
|
cacheIdentity: string;
|
|
@@ -140,6 +133,7 @@ export type TraceEvent = {
|
|
|
140
133
|
provider?: string;
|
|
141
134
|
turn?: number;
|
|
142
135
|
promptTokens: number;
|
|
136
|
+
totalInputTokens: number;
|
|
143
137
|
completionTokens: number;
|
|
144
138
|
cacheRead: number;
|
|
145
139
|
cacheWrite: number;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -57,6 +57,19 @@ export type ToolReturn = string | {
|
|
|
57
57
|
terminate?: boolean;
|
|
58
58
|
isError?: boolean;
|
|
59
59
|
};
|
|
60
|
+
export interface NestedUsage {
|
|
61
|
+
tokens: number;
|
|
62
|
+
turns: number;
|
|
63
|
+
tasks: number;
|
|
64
|
+
costMicroUsd?: number;
|
|
65
|
+
}
|
|
66
|
+
export interface NestedUsageAccum {
|
|
67
|
+
tokens: number;
|
|
68
|
+
turns: number;
|
|
69
|
+
tasks: number;
|
|
70
|
+
costMicroUsd: number;
|
|
71
|
+
anyUnpriced: boolean;
|
|
72
|
+
}
|
|
60
73
|
export interface ToolExecuteContext {
|
|
61
74
|
toolCallId: string;
|
|
62
75
|
signal?: AbortSignal;
|
|
@@ -81,12 +94,7 @@ export interface ToolExecuteContext {
|
|
|
81
94
|
release: () => Promise<void>;
|
|
82
95
|
} | null>;
|
|
83
96
|
parentCwd?: string;
|
|
84
|
-
reportUsage?: (usage:
|
|
85
|
-
tokens: number;
|
|
86
|
-
turns: number;
|
|
87
|
-
tasks: number;
|
|
88
|
-
costMicroUsd?: number;
|
|
89
|
-
}) => void;
|
|
97
|
+
reportUsage?: (usage: NestedUsage) => void;
|
|
90
98
|
model?: Model;
|
|
91
99
|
thinkingLevel?: ThinkingLevel;
|
|
92
100
|
principal?: string;
|
|
@@ -412,12 +420,7 @@ export interface TaskResult {
|
|
|
412
420
|
outputTokens?: number;
|
|
413
421
|
cacheWriteTokensLong?: number;
|
|
414
422
|
costMicroUsd?: number;
|
|
415
|
-
nested?:
|
|
416
|
-
tokens: number;
|
|
417
|
-
turns: number;
|
|
418
|
-
tasks: number;
|
|
419
|
-
costMicroUsd?: number;
|
|
420
|
-
};
|
|
423
|
+
nested?: NestedUsage;
|
|
421
424
|
memory?: {
|
|
422
425
|
tokens: number;
|
|
423
426
|
costMicroUsd: number;
|
|
@@ -524,6 +527,7 @@ export type TaskEvent = ({
|
|
|
524
527
|
type: "turn_end";
|
|
525
528
|
usage?: {
|
|
526
529
|
inputTokens: number;
|
|
530
|
+
totalInputTokens: number;
|
|
527
531
|
outputTokens: number;
|
|
528
532
|
cacheReadTokens: number;
|
|
529
533
|
cacheWriteTokens: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
2
2
|
import { scrubSecrets } from "./arg-summary.js";
|
|
3
|
+
import { sliceHeadSafe } from "./surrogate-safe-slice.js";
|
|
3
4
|
const URI_SCHEME = String.raw `[A-Za-z][A-Za-z0-9+.\-_]{0,63}`;
|
|
4
5
|
const CREDENTIALS_IN_AUTHORITY = new RegExp(String.raw `(${URI_SCHEME}:\/\/)((?:[^/\s"'@]*@)+)`, "gi");
|
|
5
6
|
const URL_OF_ANY_PROTOCOL = new RegExp(String.raw `${URI_SCHEME}:\/\/[^\s"'\`]+`, "gi");
|
|
@@ -52,16 +53,17 @@ function boundedString(value, max, redact) {
|
|
|
52
53
|
if (s.length <= max)
|
|
53
54
|
return s;
|
|
54
55
|
const totalMarkers = countRedactionMarkers(s);
|
|
55
|
-
const plainSuffix = (
|
|
56
|
-
const enrichedSuffix = (
|
|
57
|
-
const lost = totalMarkers - countRedactionMarkers(
|
|
58
|
-
return lost > 0 ? `…[+${s.length -
|
|
56
|
+
const plainSuffix = (kept) => `…[+${s.length - kept.length} chars]`;
|
|
57
|
+
const enrichedSuffix = (kept) => {
|
|
58
|
+
const lost = totalMarkers - countRedactionMarkers(kept);
|
|
59
|
+
return lost > 0 ? `…[+${s.length - kept.length} chars, ${lost} redaction marker(s) among them]` : plainSuffix(kept);
|
|
59
60
|
};
|
|
60
61
|
const converge = (suffixFor) => {
|
|
61
62
|
let keep = max;
|
|
62
63
|
let out = "";
|
|
63
64
|
for (let i = 0; i < 4; i++) {
|
|
64
|
-
|
|
65
|
+
const kept = sliceHeadSafe(s, keep);
|
|
66
|
+
out = `${kept}${suffixFor(kept)}`;
|
|
65
67
|
if (out.length <= max)
|
|
66
68
|
return out;
|
|
67
69
|
if (keep === 0)
|
|
@@ -3,23 +3,20 @@ export interface WorkflowJournalEntry {
|
|
|
3
3
|
callKey: string;
|
|
4
4
|
result: TaskResult;
|
|
5
5
|
}
|
|
6
|
+
export interface ResumeClaimArgs {
|
|
7
|
+
sourceRunId: string;
|
|
8
|
+
newRunId: string;
|
|
9
|
+
scope: string;
|
|
10
|
+
}
|
|
6
11
|
export interface WorkflowJournalStore {
|
|
7
12
|
load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
|
|
8
13
|
append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
|
|
9
14
|
locator?(runId: string, scope: string): string | undefined;
|
|
10
|
-
resumeClaim?(input: {
|
|
11
|
-
sourceRunId: string;
|
|
12
|
-
newRunId: string;
|
|
13
|
-
scope: string;
|
|
14
|
-
}): Promise<{
|
|
15
|
+
resumeClaim?(input: ResumeClaimArgs): Promise<{
|
|
15
16
|
granted: boolean;
|
|
16
17
|
holder?: string;
|
|
17
18
|
}>;
|
|
18
|
-
releaseResumeClaim?(input:
|
|
19
|
-
sourceRunId: string;
|
|
20
|
-
newRunId: string;
|
|
21
|
-
scope: string;
|
|
22
|
-
}): Promise<void>;
|
|
19
|
+
releaseResumeClaim?(input: ResumeClaimArgs): Promise<void>;
|
|
23
20
|
}
|
|
24
21
|
export declare const MAX_JOURNAL_RESULT_BYTES: number;
|
|
25
22
|
export declare function oversizeJournalResult(serialized: string): boolean;
|
|
@@ -31,19 +28,11 @@ export declare class InMemoryWorkflowJournalStore implements WorkflowJournalStor
|
|
|
31
28
|
private readonly runs;
|
|
32
29
|
private readonly resumeClaims;
|
|
33
30
|
private claimsForScope;
|
|
34
|
-
resumeClaim(input: {
|
|
35
|
-
sourceRunId: string;
|
|
36
|
-
newRunId: string;
|
|
37
|
-
scope: string;
|
|
38
|
-
}): Promise<{
|
|
31
|
+
resumeClaim(input: ResumeClaimArgs): Promise<{
|
|
39
32
|
granted: boolean;
|
|
40
33
|
holder?: string;
|
|
41
34
|
}>;
|
|
42
|
-
releaseResumeClaim(input:
|
|
43
|
-
sourceRunId: string;
|
|
44
|
-
newRunId: string;
|
|
45
|
-
scope: string;
|
|
46
|
-
}): Promise<void>;
|
|
35
|
+
releaseResumeClaim(input: ResumeClaimArgs): Promise<void>;
|
|
47
36
|
load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
|
|
48
37
|
append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
|
|
49
38
|
}
|
|
@@ -21,22 +21,27 @@ export declare class WorkflowRunStoreError extends Error {
|
|
|
21
21
|
readonly code: "workflow_run.already_exists";
|
|
22
22
|
constructor(code: "workflow_run.already_exists", message: string);
|
|
23
23
|
}
|
|
24
|
+
export interface WorkflowRunListByScopeOptions {
|
|
25
|
+
status?: WorkflowRunStatus;
|
|
26
|
+
limit?: number;
|
|
27
|
+
session?: string;
|
|
28
|
+
}
|
|
24
29
|
export interface WorkflowRunStore {
|
|
25
30
|
put(id: string, run: WorkflowRun): Promise<void>;
|
|
26
31
|
get(id: string): Promise<WorkflowRun | null>;
|
|
27
32
|
update(id: string, scope: string, run: WorkflowRun, expect?: {
|
|
28
33
|
rev: number;
|
|
29
34
|
}): Promise<boolean>;
|
|
30
|
-
listByScope(scope: string, opts?:
|
|
31
|
-
status?: WorkflowRunStatus;
|
|
32
|
-
limit?: number;
|
|
33
|
-
session?: string;
|
|
34
|
-
}): Promise<WorkflowRunSummary[]>;
|
|
35
|
+
listByScope(scope: string, opts?: WorkflowRunListByScopeOptions): Promise<WorkflowRunSummary[]>;
|
|
35
36
|
reap(scope: string, now: number, opts?: {
|
|
36
37
|
maxAgeMs?: number;
|
|
37
38
|
keep?: number;
|
|
38
39
|
}): Promise<number>;
|
|
39
40
|
}
|
|
41
|
+
export declare function nextWorkflowRunOnUpdate(cur: WorkflowRun | undefined, id: string, scope: string, run: WorkflowRun, expect?: {
|
|
42
|
+
rev: number;
|
|
43
|
+
}): WorkflowRun | null;
|
|
44
|
+
export declare function queryWorkflowRuns(runs: Iterable<WorkflowRun>, scope: string, opts?: WorkflowRunListByScopeOptions): WorkflowRunSummary[];
|
|
40
45
|
export declare class InMemoryWorkflowRunStore implements WorkflowRunStore {
|
|
41
46
|
private runs;
|
|
42
47
|
put(id: string, run: WorkflowRun): Promise<void>;
|
|
@@ -44,11 +49,7 @@ export declare class InMemoryWorkflowRunStore implements WorkflowRunStore {
|
|
|
44
49
|
update(id: string, scope: string, run: WorkflowRun, expect?: {
|
|
45
50
|
rev: number;
|
|
46
51
|
}): Promise<boolean>;
|
|
47
|
-
listByScope(scope: string, opts?:
|
|
48
|
-
status?: WorkflowRunStatus;
|
|
49
|
-
limit?: number;
|
|
50
|
-
session?: string;
|
|
51
|
-
}): Promise<WorkflowRunSummary[]>;
|
|
52
|
+
listByScope(scope: string, opts?: WorkflowRunListByScopeOptions): Promise<WorkflowRunSummary[]>;
|
|
52
53
|
reap(scope: string, now: number, opts?: {
|
|
53
54
|
maxAgeMs?: number;
|
|
54
55
|
keep?: number;
|
|
@@ -28,6 +28,25 @@ export class WorkflowRunStoreError extends Error {
|
|
|
28
28
|
this.name = "WorkflowRunStoreError";
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
export function nextWorkflowRunOnUpdate(cur, id, scope, run, expect) {
|
|
32
|
+
if (!cur || cur.scope !== scope)
|
|
33
|
+
return null;
|
|
34
|
+
if (expect !== undefined && (cur.rev ?? 0) !== expect.rev)
|
|
35
|
+
return null;
|
|
36
|
+
const next = structuredClone(run);
|
|
37
|
+
next.id = id;
|
|
38
|
+
next.scope = scope;
|
|
39
|
+
next.rev = (cur.rev ?? 0) + 1;
|
|
40
|
+
return next;
|
|
41
|
+
}
|
|
42
|
+
export function queryWorkflowRuns(runs, scope, opts) {
|
|
43
|
+
const rows = [...runs].filter((r) => r.scope === scope &&
|
|
44
|
+
(opts?.status === undefined || r.status === opts.status) &&
|
|
45
|
+
(opts?.session === undefined || r.originatingSessionId === opts.session));
|
|
46
|
+
rows.sort((a, b) => b.createdAt - a.createdAt);
|
|
47
|
+
const limited = opts?.limit !== undefined ? rows.slice(0, Math.max(0, opts.limit)) : rows;
|
|
48
|
+
return limited.map(summarizeWorkflowRun);
|
|
49
|
+
}
|
|
31
50
|
export class InMemoryWorkflowRunStore {
|
|
32
51
|
runs = new Map();
|
|
33
52
|
async put(id, run) {
|
|
@@ -44,27 +63,14 @@ export class InMemoryWorkflowRunStore {
|
|
|
44
63
|
return run ? structuredClone(run) : null;
|
|
45
64
|
}
|
|
46
65
|
async update(id, scope, run, expect) {
|
|
47
|
-
const
|
|
48
|
-
if (!
|
|
66
|
+
const next = nextWorkflowRunOnUpdate(this.runs.get(id), id, scope, run, expect);
|
|
67
|
+
if (!next)
|
|
49
68
|
return false;
|
|
50
|
-
}
|
|
51
|
-
if (expect !== undefined && (cur.rev ?? 0) !== expect.rev) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
const next = structuredClone(run);
|
|
55
|
-
next.id = id;
|
|
56
|
-
next.scope = scope;
|
|
57
|
-
next.rev = (cur.rev ?? 0) + 1;
|
|
58
69
|
this.runs.set(id, next);
|
|
59
70
|
return true;
|
|
60
71
|
}
|
|
61
72
|
async listByScope(scope, opts) {
|
|
62
|
-
|
|
63
|
-
(opts?.status === undefined || r.status === opts.status) &&
|
|
64
|
-
(opts?.session === undefined || r.originatingSessionId === opts.session));
|
|
65
|
-
rows.sort((a, b) => b.createdAt - a.createdAt);
|
|
66
|
-
const limited = opts?.limit !== undefined ? rows.slice(0, Math.max(0, opts.limit)) : rows;
|
|
67
|
-
return limited.map(summarizeWorkflowRun);
|
|
73
|
+
return queryWorkflowRuns(this.runs.values(), scope, opts);
|
|
68
74
|
}
|
|
69
75
|
async reap(scope, now, opts) {
|
|
70
76
|
if (opts?.maxAgeMs === undefined && opts?.keep === undefined)
|
|
@@ -78,3 +78,4 @@ export interface CompactionPreparation {
|
|
|
78
78
|
export declare function prepareCompaction(pathEntries: SessionTreeEntry[], settings: CompactionSettings, charsPerToken?: number, windowTokens?: number): Result<CompactionPreparation | undefined, CompactionError>;
|
|
79
79
|
export { computeFileLists, serializeConversation } from "./utils.js";
|
|
80
80
|
export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string | undefined, headers?: Record<string, string>, customInstructions?: string, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn, runtime?: AgentCoreCompletionRuntimeDeps, charsPerToken?: number, onInputTruncated?: (info: SummarizationInputTruncation) => void, onPtlRetry?: () => void): Promise<Result<CompactionResult, CompactionError>>;
|
|
81
|
+
export declare function turnPrefixSummarizationPrompt(customInstructions?: string): string;
|
|
@@ -132,6 +132,9 @@ export function shouldCompact(contextTokens, contextWindow, settings) {
|
|
|
132
132
|
if (!settings.enabled) {
|
|
133
133
|
return false;
|
|
134
134
|
}
|
|
135
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
135
138
|
return contextTokens > contextWindow - settings.reserveTokens;
|
|
136
139
|
}
|
|
137
140
|
export function estimateTokens(message, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
|
|
@@ -539,7 +542,7 @@ export function dryRunSummarizationClamp(preparation, model, customInstructions,
|
|
|
539
542
|
if (isSplitTurn && turnPrefixMessages.length > 0) {
|
|
540
543
|
const text = serializeConversation(convertToLlm(turnPrefixMessages));
|
|
541
544
|
conversationChars += text.length;
|
|
542
|
-
const clamped = clampSummarizationConversation(text, model, cpt, SUMMARIZATION_SYSTEM_PROMPT.length +
|
|
545
|
+
const clamped = clampSummarizationConversation(text, model, cpt, SUMMARIZATION_SYSTEM_PROMPT.length + turnPrefixSummarizationPrompt(customInstructions).length + 128, Math.floor(0.5 * summaryBudget));
|
|
543
546
|
dropped += Math.max(0, clamped.droppedChars);
|
|
544
547
|
kept += clamped.keptChars;
|
|
545
548
|
}
|
|
@@ -879,7 +882,7 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
879
882
|
? generateSummary(messagesToSummarize, model, summaryBudget, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry)
|
|
880
883
|
:
|
|
881
884
|
Promise.resolve(ok(previousSummary ? stripFileOperationsFooter(previousSummary) : "No prior history.")),
|
|
882
|
-
generateTurnPrefixSummary(turnPrefixMessages, model, summaryBudget, apiKey, headers, signal, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry),
|
|
885
|
+
generateTurnPrefixSummary(turnPrefixMessages, model, summaryBudget, apiKey, headers, signal, customInstructions, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry),
|
|
883
886
|
]);
|
|
884
887
|
if (!historyResult.ok) {
|
|
885
888
|
return err(historyResult.error);
|
|
@@ -913,16 +916,22 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
913
916
|
},
|
|
914
917
|
});
|
|
915
918
|
}
|
|
916
|
-
|
|
919
|
+
export function turnPrefixSummarizationPrompt(customInstructions) {
|
|
920
|
+
return customInstructions
|
|
921
|
+
? `${TURN_PREFIX_SUMMARIZATION_PROMPT}\n\nAdditional Instructions:\n${customInstructions}`
|
|
922
|
+
: TURN_PREFIX_SUMMARIZATION_PROMPT;
|
|
923
|
+
}
|
|
924
|
+
async function generateTurnPrefixSummary(messages, model, summaryBudgetTokens, apiKey, headers, signal, customInstructions, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry) {
|
|
917
925
|
const llmMessages = convertToLlm(messages);
|
|
926
|
+
const basePrompt = turnPrefixSummarizationPrompt(customInstructions);
|
|
918
927
|
return summarizeWithPtlRetry({
|
|
919
928
|
label: "Turn prefix summarization",
|
|
920
929
|
truncationLabel: "turn_prefix",
|
|
921
930
|
llmMessages,
|
|
922
|
-
buildPromptText: (conversationText) => `<conversation>\n${conversationText}\n</conversation>\n\n${
|
|
931
|
+
buildPromptText: (conversationText) => `<conversation>\n${conversationText}\n</conversation>\n\n${basePrompt}`,
|
|
923
932
|
model,
|
|
924
933
|
cpt: charsPerToken ?? model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN,
|
|
925
|
-
fixedPromptChars: SUMMARIZATION_SYSTEM_PROMPT.length +
|
|
934
|
+
fixedPromptChars: SUMMARIZATION_SYSTEM_PROMPT.length + basePrompt.length + 128,
|
|
926
935
|
baseMaxTokens: Math.floor(0.5 * summaryBudgetTokens),
|
|
927
936
|
apiKey,
|
|
928
937
|
headers,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeLlmMessageContent } from "../harness/messages.js";
|
|
2
|
+
import { sliceHeadSafe } from "../../core/surrogate-safe-slice.js";
|
|
2
3
|
import { cutEnginePrefix, cutEngineSegments, flattenableUserText } from "../../core/untrusted-text.js";
|
|
3
4
|
import { fileArgPath } from "../../tools/fs/safety.js";
|
|
4
5
|
export function createFileOps() {
|
|
@@ -287,11 +288,12 @@ function truncateForSummary(text, maxChars) {
|
|
|
287
288
|
let keep = maxChars;
|
|
288
289
|
let out = "";
|
|
289
290
|
for (let i = 0; i < 4; i++) {
|
|
290
|
-
|
|
291
|
+
const kept = sliceHeadSafe(text, keep);
|
|
292
|
+
out = `${kept}\n\n[... ${text.length - kept.length} more characters truncated]`;
|
|
291
293
|
if (out.length <= maxChars)
|
|
292
294
|
break;
|
|
293
295
|
if (keep === 0)
|
|
294
|
-
return maxChars >= 1 ? `${text
|
|
296
|
+
return maxChars >= 1 ? `${sliceHeadSafe(text, maxChars - 1)}…` : "";
|
|
295
297
|
keep = Math.max(0, keep - (out.length - maxChars));
|
|
296
298
|
}
|
|
297
299
|
return out.length < text.length ? out : text;
|