@sema-agent/core 7.56.0 → 7.57.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 +541 -0
- package/LICENSE +1 -1
- package/dist/agents/org-delivery-gate.js +3 -4
- package/dist/agents/peer-held-queue.d.ts +69 -49
- package/dist/agents/peer-held-queue.js +255 -106
- package/dist/agents/peer-session-drain.js +63 -43
- package/dist/agents/send-message-tool.js +3 -2
- package/dist/agents/subagent.js +8 -8
- package/dist/brain/anthropic.js +16 -8
- package/dist/brain/open-responses.js +10 -8
- package/dist/brain/openai.js +11 -9
- package/dist/brain/provider-dialect.d.ts +3 -2
- package/dist/brain/provider-dialect.js +2 -2
- package/dist/brain/request-params.d.ts +6 -0
- package/dist/brain/request-params.js +14 -1
- package/dist/brain/stream-engine.d.ts +9 -0
- package/dist/brain/stream-engine.js +24 -10
- package/dist/brain/stream-shared.d.ts +5 -4
- package/dist/brain/stream-shared.js +4 -4
- package/dist/core/allow-entry.d.ts +3 -10
- package/dist/core/allow-entry.js +17 -25
- package/dist/core/approval-object.d.ts +21 -5
- package/dist/core/approval-object.js +44 -17
- package/dist/core/ask-origin.d.ts +2 -2
- package/dist/core/auto-compaction.d.ts +71 -6
- package/dist/core/auto-compaction.js +42 -26
- package/dist/core/background-agent-store.d.ts +6 -2
- package/dist/core/background-agent-store.js +1 -1
- package/dist/core/checkpoint-requirements.d.ts +7 -1
- package/dist/core/checkpoint-requirements.js +2 -0
- package/dist/core/checkpoint-store.d.ts +17 -29
- package/dist/core/checkpoint-store.js +19 -11
- package/dist/core/cross-boundary-taint.js +4 -2
- package/dist/core/engine-notice.d.ts +3 -2
- package/dist/core/engine-notice.js +6 -0
- package/dist/core/file-history-store.d.ts +14 -8
- package/dist/core/file-history-store.js +14 -4
- package/dist/core/gate-lanes.js +2 -3
- package/dist/core/hooks.d.ts +2 -2
- package/dist/core/mailbox-store.d.ts +62 -1
- package/dist/core/mailbox-store.js +59 -0
- package/dist/core/mcp-name-judge.d.ts +46 -0
- package/dist/core/mcp-name-judge.js +61 -0
- package/dist/core/mcp-probe.d.ts +1 -1
- package/dist/core/mcp-probe.js +2 -5
- package/dist/core/mcp.js +10 -30
- package/dist/core/memory-engine/consolidation-driver.js +4 -3
- package/dist/core/memory-engine/distiller.js +26 -17
- package/dist/core/memory-engine/layout.js +5 -2
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/permission-rule-consent.d.ts +5 -3
- package/dist/core/permission-rule-consent.js +20 -15
- package/dist/core/permission-rule-model.d.ts +43 -2
- package/dist/core/permission-rule-model.js +52 -8
- package/dist/core/permission-rule-org.js +1 -1
- package/dist/core/protocol-http-auth.d.ts +16 -2
- package/dist/core/protocol-http-auth.js +42 -11
- package/dist/core/protocol-naming.d.ts +20 -0
- package/dist/core/protocol-naming.js +20 -3
- package/dist/core/read-only-shell.d.ts +20 -5
- package/dist/core/read-only-shell.js +29 -14
- package/dist/core/remote-env.d.ts +37 -1
- package/dist/core/remote-env.js +11 -0
- package/dist/core/resume-startup-cause.d.ts +24 -2
- package/dist/core/resume-startup-cause.js +20 -1
- package/dist/core/roles.js +50 -7
- package/dist/core/runner/compact-session.d.ts +10 -0
- package/dist/core/runner/compact-session.js +202 -0
- package/dist/core/runner/compaction-call-options.d.ts +2 -2
- package/dist/core/runner/compaction-seams.d.ts +21 -1
- package/dist/core/runner/compaction-seams.js +11 -8
- package/dist/core/runner/contracts.d.ts +23 -16
- package/dist/core/runner/derived-route-fallback.d.ts +13 -0
- package/dist/core/runner/derived-route-fallback.js +6 -0
- package/dist/core/runner/env-capability-census.d.ts +3 -2
- package/dist/core/runner/env-capability-census.js +9 -3
- package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
- package/dist/core/runner/permission-rule-lanes.js +17 -11
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
- package/dist/core/runner/prepare-hands-readface.js +2 -0
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +2 -2
- package/dist/core/runner/prepare-policy-chain.js +2 -4
- package/dist/core/runner/prepare-task.d.ts +4 -1
- package/dist/core/runner/prepare-task.js +23 -27
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
- package/dist/core/runner/rewind-files.d.ts +2 -5
- package/dist/core/runner/rewind-files.js +9 -51
- package/dist/core/runner/run-leg.js +10 -21
- package/dist/core/runner/run-notification-lane.d.ts +2 -0
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/run-recovery-lanes.js +3 -4
- package/dist/core/runner/run-settle-and-teardown.js +5 -11
- package/dist/core/runner/run-terminal-adoption.js +3 -4
- package/dist/core/runner/run-turn-boundary.js +3 -5
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +28 -34
- package/dist/core/runner/send-message-continuation.js +1 -1
- package/dist/core/runner/session-verb-doors.d.ts +28 -0
- package/dist/core/runner/session-verb-doors.js +48 -0
- package/dist/core/runner/session-write-ledger.d.ts +30 -0
- package/dist/core/runner/session-write-ledger.js +101 -0
- package/dist/core/runner/stream-settle-backstop.js +11 -14
- package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
- package/dist/core/runner/sub-agent-deny-lists.js +21 -10
- package/dist/core/runner/teardown-bounded.d.ts +1 -0
- package/dist/core/runner/teardown-bounded.js +1 -0
- package/dist/core/runner-deps.d.ts +2 -2
- package/dist/core/secret-env.d.ts +4 -0
- package/dist/core/secret-env.js +3 -0
- package/dist/core/session-policy-store.d.ts +7 -2
- package/dist/core/session-policy-store.js +1 -1
- package/dist/core/shell-wrapper-projection.d.ts +1 -1
- package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/store-contracts/file-history-store-contract.js +9 -0
- package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
- package/dist/core/task-registry-agent.d.ts +8 -4
- package/dist/core/task-registry-agent.js +14 -8
- package/dist/core/task-registry-shared.d.ts +35 -6
- package/dist/core/task-registry-shared.js +11 -0
- package/dist/core/task-registry.d.ts +1 -1
- package/dist/core/task-registry.js +1 -1
- package/dist/core/tool-catalog-entries.d.ts +6 -0
- package/dist/core/tool-catalog-entries.js +1 -0
- package/dist/core/tool-name-aliases.d.ts +32 -2
- package/dist/core/tool-name-aliases.js +23 -0
- package/dist/core/tool-name-readings.d.ts +4 -0
- package/dist/core/tool-name-readings.js +1 -0
- package/dist/core/tool-policy.d.ts +10 -6
- package/dist/core/tool-policy.js +4 -7
- package/dist/core/trace.d.ts +4 -1
- package/dist/core/trace.js +2 -1
- package/dist/core/untrusted-egress.d.ts +20 -0
- package/dist/core/untrusted-egress.js +77 -3
- package/dist/engine/harness/agent-harness.d.ts +7 -5
- package/dist/engine/harness/agent-harness.js +11 -2
- package/dist/engine/loop/agent-loop.d.ts +1 -4
- package/dist/engine/loop/agent-loop.js +26 -4
- package/dist/index.d.ts +12 -7
- package/dist/index.js +7 -4
- package/dist/internal/harness.d.ts +2 -1
- package/dist/internal/harness.js +1 -0
- package/dist/primitives/credential-ref.d.ts +89 -0
- package/dist/primitives/credential-ref.js +125 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/stores/file/checkpoint-store.d.ts +0 -4
- package/dist/stores/file/checkpoint-store.js +0 -1
- package/dist/stores/file/file-history-store.js +12 -4
- package/dist/stores/file/mailbox-store.d.ts +2 -1
- package/dist/stores/file/mailbox-store.js +26 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
- package/dist/tools/fs/bash-readonly-classifier.js +5 -10
- package/dist/tools/fs/fs-bash.js +10 -3
- package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
- package/dist/tools/fs/pattern-kill-guard.js +68 -36
- package/dist/tools/fs/shell-read-check.d.ts +10 -0
- package/dist/tools/fs/shell-read-check.js +7 -1
- package/dist/tools/fs/shell-read-identity.d.ts +12 -9
- package/dist/tools/fs/shell-read-identity.js +9 -4
- package/dist/tools/web.js +9 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +79 -3
- package/wire-changes/core-stores-sql@7.57.0.json +29 -0
- package/wire-changes/core@7.57.0.json +1487 -0
- package/wire-changes/mods-kernel@7.57.0.json +29 -0
- package/wire-changes/primitives@7.57.0.json +245 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
2
|
import { lexedWordOf, readShellCommand } from "./shell-run-candidates.js";
|
|
3
|
+
import { unlistedReaderSentence } from "../tools/fs/bash-readonly-classifier.js";
|
|
3
4
|
import { descriptorsOfPlan, newLineDescriptors, standardDeviceUnrebound } from "../tools/fs/shell-read-device.js";
|
|
4
5
|
import { planShellReads } from "../tools/fs/shell-read-plan.js";
|
|
5
6
|
import { writesOnlyStandardDevices } from "../tools/fs/shell-read-write-targets.js";
|
|
6
7
|
import { FIND_ACTION_PRIMARIES, FIND_NEWER_PRIMARY, FIND_VALUE_PRIMARIES, FLAG_VALUE_ACCEPTS, READ_ONLY_BARE_ONLY, READ_ONLY_BARE_PROGRAMS, READ_ONLY_COMMAND_TABLE, READ_ONLY_ENV_NAMES, READ_ONLY_EXACT_FORMS, READ_ONLY_GLOB_PROGRAMS, XARGS_READ_ONLY_TARGETS, dockerRetargetDanger, } from "./read-only-shell-table.js";
|
|
7
8
|
const NOT = (reason) => ({ readOnly: false, reason });
|
|
9
|
+
const READ_ONLY = { readOnly: true };
|
|
8
10
|
export const CD_GIT_COMPOUND_SENTENCE = "This command changes directory before running a version-control command, which can pick up untrusted hooks or repository configuration from the target directory. Approve only if you trust it.";
|
|
9
11
|
export function changesDirectoryBeforeGit(shape, cwd) {
|
|
10
12
|
const programs = shape.segments.map((s) => peelBuiltinPrefix(s.argv.slice(leadingAssignmentCount(s.argv)).map((w) => w.text))[0] ?? "");
|
|
@@ -20,7 +22,6 @@ export function changesDirectoryBeforeGit(shape, cwd) {
|
|
|
20
22
|
const trim = (p) => p.replace(/\/+$/, "") || "/";
|
|
21
23
|
return trim(posix.resolve(cwd, target.text)) !== trim(cwd);
|
|
22
24
|
}
|
|
23
|
-
const READ_ONLY = { readOnly: true };
|
|
24
25
|
const BARE_PROGRAMS = new Set(READ_ONLY_BARE_PROGRAMS.filter((p) => !p.includes(" ")));
|
|
25
26
|
const BARE_MULTIWORD = READ_ONLY_BARE_PROGRAMS.filter((p) => p.includes(" ")).map((p) => p.split(" "));
|
|
26
27
|
const GLOB_PROGRAMS = new Set(READ_ONLY_GLOB_PROGRAMS);
|
|
@@ -41,7 +42,11 @@ const PRINTF_ESCAPE = new RegExp(`%[^%a-zA-Z]*${PRINTF_LENGTH}\\\\[0-7xX]`);
|
|
|
41
42
|
const PRINTF_NUMERIC = new RegExp(`%[-+ 0#']*[0-9.*]*${PRINTF_LENGTH}[diouxXeEfFgGaAn]`);
|
|
42
43
|
const JQ_DANGER_FLAG = /^['"]?-[a-zA-Z]*[fL]/;
|
|
43
44
|
const JQ_DANGER_TEXT = /--from-file|--rawfile|--slurpfile|--run-tests|--library-path|\benv\b|\$ENV\b|\binclude\b|\bimport\b/;
|
|
44
|
-
export function readOnlyShellVerdict(command,
|
|
45
|
+
export function readOnlyShellVerdict(command, opts = {}) {
|
|
46
|
+
const { cwd, bound, readers } = opts;
|
|
47
|
+
if (readers !== undefined && typeof readers.has !== "function")
|
|
48
|
+
throw new TypeError("readOnlyShellVerdict: `readers` must be a Set of program names");
|
|
49
|
+
const shape = opts.shape ?? readShellCommand(command);
|
|
45
50
|
const line = lineStructureObjection(shape);
|
|
46
51
|
if (line !== undefined)
|
|
47
52
|
return NOT(line);
|
|
@@ -66,12 +71,15 @@ export function readOnlyShellVerdict(command, shape = readShellCommand(command),
|
|
|
66
71
|
let boundOfText;
|
|
67
72
|
const bindings = () => (boundOfText ??= (typeof bound === "function" ? bound() : bound) ?? lineDescriptorsOfText(command));
|
|
68
73
|
for (const seg of shape.segments) {
|
|
69
|
-
const verdict = segmentVerdict(seg, descriptors, bindings);
|
|
74
|
+
const verdict = segmentVerdict(seg, descriptors, bindings, readers);
|
|
70
75
|
if (!verdict.readOnly)
|
|
71
76
|
return verdict;
|
|
72
77
|
}
|
|
73
78
|
return READ_ONLY;
|
|
74
79
|
}
|
|
80
|
+
function unlistedObjection(program, readers) {
|
|
81
|
+
return readers !== undefined && !readers.has(program) ? NOT(unlistedReaderSentence(program)) : undefined;
|
|
82
|
+
}
|
|
75
83
|
const CD_GIT_REASON = "a compound that changes directory and runs git";
|
|
76
84
|
function lineStructureObjection(shape) {
|
|
77
85
|
if (shape.grouped)
|
|
@@ -144,16 +152,11 @@ function peelBuiltinPrefix(words) {
|
|
|
144
152
|
return t;
|
|
145
153
|
t = t.slice(r);
|
|
146
154
|
}
|
|
147
|
-
else if (t[0] === "noglob") {
|
|
148
|
-
if (t.length <= 1)
|
|
149
|
-
return t;
|
|
150
|
-
t = t.slice(1);
|
|
151
|
-
}
|
|
152
155
|
else
|
|
153
156
|
return t;
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
|
-
function segmentVerdict(seg, line, bindings) {
|
|
159
|
+
function segmentVerdict(seg, line, bindings, readers) {
|
|
157
160
|
for (const r of seg.redirections) {
|
|
158
161
|
const target = r.target.text;
|
|
159
162
|
if (!READING_REDIRECTS.has(r.op) && !standardDeviceUnrebound(target, line, []) && !(r.op === ">&" && /^\d+$/.test(target)))
|
|
@@ -174,6 +177,9 @@ function segmentVerdict(seg, line, bindings) {
|
|
|
174
177
|
return NOT("a bare assignment runs no program");
|
|
175
178
|
const words = peelBuiltinPrefix(run.map((w) => w.text));
|
|
176
179
|
const program = words[0];
|
|
180
|
+
const unlisted = unlistedObjection(program, readers);
|
|
181
|
+
if (unlisted !== undefined)
|
|
182
|
+
return unlisted;
|
|
177
183
|
if (run.some((w) => expansionKind(w) === "glob"))
|
|
178
184
|
return GLOB_PROGRAMS.has(program) ? READ_ONLY : NOT(`\`${program}\` with a glob argument — the glob may name anything`);
|
|
179
185
|
if (DEVICE_WRITERS.has(program) && writesOnlyStandardDevices(program, words.slice(1), bindings()))
|
|
@@ -181,7 +187,7 @@ function segmentVerdict(seg, line, bindings) {
|
|
|
181
187
|
const structural = structuralReading(words);
|
|
182
188
|
if (structural !== null)
|
|
183
189
|
return structural ? READ_ONLY : NOT(`\`${program}\` with these arguments is not a read-only form`);
|
|
184
|
-
return tableReading(words, run);
|
|
190
|
+
return tableReading(words, run, readers);
|
|
185
191
|
}
|
|
186
192
|
export function lineDescriptorsOfText(command) {
|
|
187
193
|
return descriptorsOfPlan(planShellReads(command, { readers: new Set(), cwd: "/", roots: [] }));
|
|
@@ -248,7 +254,7 @@ function printfReading(words) {
|
|
|
248
254
|
}
|
|
249
255
|
return true;
|
|
250
256
|
}
|
|
251
|
-
function tableReading(words, run) {
|
|
257
|
+
function tableReading(words, run, readers) {
|
|
252
258
|
const program = words[0];
|
|
253
259
|
const hit = TABLE_ROWS.find((r) => words.length >= r.words.length && r.words.every((w, i) => words[i] === w));
|
|
254
260
|
if (hit !== undefined) {
|
|
@@ -259,7 +265,8 @@ function tableReading(words, run) {
|
|
|
259
265
|
if (a.includes("{") && (a.includes(",") || a.includes("..")))
|
|
260
266
|
return NOT(`\`${program}\`: a brace pattern argument`);
|
|
261
267
|
}
|
|
262
|
-
|
|
268
|
+
const named = [];
|
|
269
|
+
if (!flagWalk(args, hit.row, program, named))
|
|
263
270
|
return NOT(`\`${program}\`: an option outside its read-only set (or a malformed option value)`);
|
|
264
271
|
if (hit.row.wordsShape !== undefined ? !hit.row.wordsShape(args) : run.some((w) => w.raw.includes("`")))
|
|
265
272
|
return NOT(`\`${program}\`: arguments outside its read-only form`);
|
|
@@ -267,6 +274,11 @@ function tableReading(words, run) {
|
|
|
267
274
|
return NOT(`\`${program}\`: a line break inside an argument`);
|
|
268
275
|
if (hit.row.dangerous?.(args) === true)
|
|
269
276
|
return NOT(`\`${program}\`: an argument form upstream marks as writing, executing or resolving`);
|
|
277
|
+
for (const target of named) {
|
|
278
|
+
const unlisted = unlistedObjection(target, readers);
|
|
279
|
+
if (unlisted !== undefined)
|
|
280
|
+
return unlisted;
|
|
281
|
+
}
|
|
270
282
|
return READ_ONLY;
|
|
271
283
|
}
|
|
272
284
|
if (program === "uniq" && words.slice(1).every((w, i, all) => /^-[a-zA-Z]+$/.test(w) || /^--[a-zA-Z-]+(=\S+)?$/.test(w) || (i > 0 && /^-[fsw]$/.test(all[i - 1]) && /^\d+$/.test(w))))
|
|
@@ -296,7 +308,7 @@ function jqReading(args) {
|
|
|
296
308
|
}
|
|
297
309
|
return true;
|
|
298
310
|
}
|
|
299
|
-
function flagWalk(args, row, program) {
|
|
311
|
+
function flagWalk(args, row, program, named) {
|
|
300
312
|
let i = 0;
|
|
301
313
|
while (i < args.length) {
|
|
302
314
|
let word = args[i];
|
|
@@ -309,7 +321,10 @@ function flagWalk(args, row, program) {
|
|
|
309
321
|
i++;
|
|
310
322
|
word = args[i];
|
|
311
323
|
}
|
|
312
|
-
|
|
324
|
+
if (!XARGS_TARGETS.includes(word))
|
|
325
|
+
return false;
|
|
326
|
+
named.push(word);
|
|
327
|
+
return true;
|
|
313
328
|
}
|
|
314
329
|
if (word === "--") {
|
|
315
330
|
if (row.respectsDoubleDash !== false)
|
|
@@ -323,7 +323,7 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
|
|
|
323
323
|
* `{isolation:true, suspendable:true}` adapter); **partial-capability** adapters (SSH/ADB: `{false, false}`
|
|
324
324
|
* — a real machine/device, not snapshotable and not isolated) declare it here. **Required + explicit** so
|
|
325
325
|
* the orchestrator never has to guess: there is no safe default (assuming isolation when there is none is a
|
|
326
|
-
* security hole; assuming suspendable crashes on `suspendVM`). Use {@link isSuspendable}/{@link isIsolated}
|
|
326
|
+
* security hole; assuming suspendable crashes on `suspendVM`). Use {@link isSuspendable}/{@link isIsolated}/{@link processTableOf}
|
|
327
327
|
* rather than the structural {@link isRemoteExecutionEnv} (method presence ≠ semantics — a stub `suspendVM`
|
|
328
328
|
* that returns `"unsupported"` still satisfies the structural check). Extensible: add fields as new
|
|
329
329
|
* partial-capability classes appear.
|
|
@@ -355,6 +355,29 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
|
|
|
355
355
|
* Read STRICTLY through {@link canonicalPathIsAuthoritative}, like every other capability whose
|
|
356
356
|
* `true` changes what a containment leg does; a wrong-SHAPE value reads absent and is announced. */
|
|
357
357
|
canonicalPathAuthoritative?: boolean;
|
|
358
|
+
/** True iff the commands this env runs are processes on a table that holds THIS process — the table the
|
|
359
|
+
* engine itself and the processes above it are on, so a kill the model writes there can reach them.
|
|
360
|
+
* `true` for an adapter that spawns the commands as this process's own descendants (a host lane), and
|
|
361
|
+
* for one whose commands run in a PID namespace that contains this process's (the host of the container
|
|
362
|
+
* the engine runs in); `false` only for one whose commands run where this process is not: another
|
|
363
|
+
* machine (SSH host, device, cloud sandbox) or a PID namespace this process is not in (a sandbox
|
|
364
|
+
* container, a pod). Not {@link isolation}: a non-isolated SSH host is a real machine and still another
|
|
365
|
+
* table. The self-preservation guard of the Bash tool asks its question exactly when this is `true`
|
|
366
|
+
* (and for every env that is not remote at all), and does not ask where it is `false`.
|
|
367
|
+
*
|
|
368
|
+
* What a `true` buys is the guard's question exactly as a NodeExecutionEnv gets it, limits included: on this
|
|
369
|
+
* process's own table, of the platform's `pgrep`, with the kill's words as written, from this process's own
|
|
370
|
+
* working directory. It is put as for this process's descendants, so for commands on the same table that
|
|
371
|
+
* are not (SSH to `localhost`) BSD's `pgrep` leaves its OWN caller's ancestors out where the real kill would
|
|
372
|
+
* not, and from an enclosing namespace the question sees only this process's namespace, not the ancestors
|
|
373
|
+
* above it.
|
|
374
|
+
*
|
|
375
|
+
* REQUIRED, with no default: either reading of an absent value is wrong somewhere (assuming `true`
|
|
376
|
+
* refuses an ordinary `pkill` in a sandbox because a word matched this engine here; assuming `false`
|
|
377
|
+
* lets a host lane's `pkill` end the session that issued it). So an absent or non-boolean value is
|
|
378
|
+
* announced once per run at the env's admission point and the guard refuses every pattern kill that needs the question on
|
|
379
|
+
* that env (`pkill -P $$` runs), naming this declaration, until the adapter states it. Read through {@link processTableOf}. */
|
|
380
|
+
sharesProcessTable: boolean;
|
|
358
381
|
};
|
|
359
382
|
/** Identity of the connected workspace (design/48 §5 gap 1). Synchronous: it is data the env already holds. */
|
|
360
383
|
workspaceHandle(): WorkspaceHandle;
|
|
@@ -634,3 +657,16 @@ export declare function boundaryIsTheSandbox(env: ExecutionEnv): boolean;
|
|
|
634
657
|
* has no case to fire on.
|
|
635
658
|
*/
|
|
636
659
|
export declare function canonicalPathIsAuthoritative(env: ExecutionEnv): boolean;
|
|
660
|
+
/**
|
|
661
|
+
* Which process table this env's commands run on, as its adapter declares it
|
|
662
|
+
* ({@link RemoteExecutionEnv.capabilities}.sharesProcessTable):
|
|
663
|
+
* · `"this"` — a table that holds this process: a remote env that declares `true`, and every env that is not
|
|
664
|
+
* remote. The second is the base contract's in-process env (its commands are this process's children); an
|
|
665
|
+
* adapter that runs commands elsewhere without the remote seam has no block to declare in and is read the
|
|
666
|
+
* same way, which errs toward asking (a refusal of a kill that could not have reached this process);
|
|
667
|
+
* · `"other"` — a remote env that declares `false`;
|
|
668
|
+
* · `"undeclared"` — a remote env whose declaration is absent, not a boolean, or cannot be read (a throwing
|
|
669
|
+
* getter or Proxy trap). There is no default to fold it to, so the caller refuses what it cannot answer.
|
|
670
|
+
* Total: it never throws. It does not announce — the admission census does, once per run.
|
|
671
|
+
*/
|
|
672
|
+
export declare function processTableOf(env: ExecutionEnv): "this" | "other" | "undeclared";
|
package/dist/core/remote-env.js
CHANGED
|
@@ -67,3 +67,14 @@ export function boundaryIsTheSandbox(env) {
|
|
|
67
67
|
export function canonicalPathIsAuthoritative(env) {
|
|
68
68
|
return isRemoteExecutionEnv(env) ? env.capabilities.canonicalPathAuthoritative === true : false;
|
|
69
69
|
}
|
|
70
|
+
export function processTableOf(env) {
|
|
71
|
+
try {
|
|
72
|
+
if (!isRemoteExecutionEnv(env))
|
|
73
|
+
return "this";
|
|
74
|
+
const declared = env.capabilities.sharesProcessTable;
|
|
75
|
+
return declared === true ? "this" : declared === false ? "other" : "undeclared";
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return "undeclared";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -84,6 +84,28 @@ export type ReopenReasonTableCoversEveryCause = AssertAllKeysHandled<Exclude<Res
|
|
|
84
84
|
* decision is untouched, and the retry replays it.
|
|
85
85
|
*/
|
|
86
86
|
export declare function reopenReasonOf(cause: ResumeStartupCause | undefined): ReopenReason;
|
|
87
|
-
/**
|
|
88
|
-
|
|
87
|
+
/**
|
|
88
|
+
* What happened to a claimed row whose approved action never started — the one closed outcome every exit of such a leg reports
|
|
89
|
+
* (the thrown-prepare backstop, the returned-leg settle, the post-claim abort): `reopened` under a reason (the row is pending again),
|
|
90
|
+
* `declined` (the store's `reopen` answered `false`: the row is no longer the resolved row this leg claimed — missing, expired, its
|
|
91
|
+
* decided action already disposed, or pending again — which the boolean does not say), `unknown` (the reopen threw: the write may have
|
|
92
|
+
* committed and only its acknowledgement been lost, so the row's state cannot be proven from here).
|
|
93
|
+
*/
|
|
94
|
+
export type UnstartedRow = {
|
|
95
|
+
readonly kind: "reopened";
|
|
96
|
+
readonly reason: ReopenReason;
|
|
97
|
+
} | {
|
|
98
|
+
readonly kind: "declined";
|
|
99
|
+
} | {
|
|
100
|
+
readonly kind: "unknown";
|
|
101
|
+
readonly thrown: unknown;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* THE compensation of a claimed leg whose approved action never started: the claim's reversal (`reopen`, bound to the row) asked once,
|
|
105
|
+
* under `reason`, and read into {@link UnstartedRow} — its answer, never a code sniffed off whatever it threw (a store's own error may
|
|
106
|
+
* carry any code, and a throw after a committed write is not a refusal). Every exit calls it; none re-reads the outcome another way.
|
|
107
|
+
*/
|
|
108
|
+
export declare function compensateUnstarted(reopen: (reason: ReopenReason) => Promise<"reopened" | "declined">, reason: ReopenReason): Promise<UnstartedRow>;
|
|
109
|
+
/** The sentence for {@link UnstartedRow} — one per outcome, the same on every exit; the exit puts its own lead before it. */
|
|
110
|
+
export declare function unstartedRowSentence(row: UnstartedRow): string;
|
|
89
111
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { egressThrownText } from "./untrusted-egress.js";
|
|
1
2
|
export const REOPEN_REASONS = Object.freeze(["env_failed", "tool_unavailable", "constraint_rejected", "config_refused"]);
|
|
2
3
|
export const RESUME_STARTUP_CAUSES = Object.freeze(["environment", "session_gone", "transcript_corrupt", "tool_unavailable", "configuration", "decision_rejected"]);
|
|
3
4
|
const CAUSE_OF_CODE = new Map([
|
|
@@ -44,7 +45,25 @@ const REOPEN_REASON_OF_CAUSE = {
|
|
|
44
45
|
export function reopenReasonOf(cause) {
|
|
45
46
|
return cause === undefined ? "env_failed" : REOPEN_REASON_OF_CAUSE[cause];
|
|
46
47
|
}
|
|
47
|
-
export function
|
|
48
|
+
export async function compensateUnstarted(reopen, reason) {
|
|
49
|
+
try {
|
|
50
|
+
return (await reopen(reason)) === "reopened" ? { kind: "reopened", reason } : { kind: "declined" };
|
|
51
|
+
}
|
|
52
|
+
catch (thrown) {
|
|
53
|
+
return { kind: "unknown", thrown };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function unstartedRowSentence(row) {
|
|
57
|
+
switch (row.kind) {
|
|
58
|
+
case "reopened":
|
|
59
|
+
return reopenedUnstartedSentence(row.reason);
|
|
60
|
+
case "declined":
|
|
61
|
+
return "the approved action never started, and the store DECLINED to reopen the checkpoint — it is no longer the resolved row this leg claimed (missing, expired, its decided action already disposed, or pending again): read the row before deciding; if it is resolved or gone, a retry needs a fresh approval";
|
|
62
|
+
case "unknown":
|
|
63
|
+
return `the approved action never started, and reopening the checkpoint FAILED IN FLIGHT (${egressThrownText("same-principal", row.thrown)}) — its state UNKNOWN, unprovable from here: re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function reopenedUnstartedSentence(reason) {
|
|
48
67
|
switch (reason) {
|
|
49
68
|
case "env_failed":
|
|
50
69
|
return "the approved action never started, so the checkpoint was REOPENED (pending): the same approval can be resumed again, do not ask for a fresh one";
|
package/dist/core/roles.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { selectModel } from "./select-model.js";
|
|
2
2
|
import { namesRoleBase } from "../prompt-assembly/role-base.js";
|
|
3
3
|
import { readPromptRole } from "../prompt-assembly/select.js";
|
|
4
|
+
import { readMember } from "./thrown-value.js";
|
|
4
5
|
import { MODEL_ROLES } from "./types.js";
|
|
5
6
|
export { MODEL_ROLES } from "./types.js";
|
|
6
7
|
function catalogModel(ref, models) {
|
|
@@ -13,13 +14,36 @@ function modelBindingRefusal(field, reason, message) {
|
|
|
13
14
|
e.detail = { field, reason };
|
|
14
15
|
return e;
|
|
15
16
|
}
|
|
17
|
+
const ROLE_RECORD_KEYS = new Set(["model", "select", "thinking", "promptRole", "systemPrompt"]);
|
|
18
|
+
function isModelShaped(s) {
|
|
19
|
+
try {
|
|
20
|
+
if (typeof s === "function" || isList(s)) {
|
|
21
|
+
Reflect.ownKeys(s);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (Reflect.ownKeys(s).some((k) => typeof k !== "string" || !ROLE_RECORD_KEYS.has(k)))
|
|
25
|
+
return true;
|
|
26
|
+
return readMember(s, "id") !== undefined || readMember(s, "api") !== undefined;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
16
32
|
function isBindingObject(value) {
|
|
17
33
|
return (typeof value === "object" && value !== null) || typeof value === "function";
|
|
18
34
|
}
|
|
19
35
|
function bindingShown(value) {
|
|
20
36
|
if (typeof value === "string")
|
|
21
37
|
return JSON.stringify(value.length > 80 ? `${value.slice(0, 80)}…` : value);
|
|
22
|
-
return value === null ? "null" :
|
|
38
|
+
return value === null ? "null" : isList(value) ? "a list" : `a ${typeof value}`;
|
|
39
|
+
}
|
|
40
|
+
function isList(value) {
|
|
41
|
+
try {
|
|
42
|
+
return Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
23
47
|
}
|
|
24
48
|
function unknownModelRefRefusal(ref, seat) {
|
|
25
49
|
return modelBindingRefusal(seat, "unknown_model_ref", `Unknown model ref ${bindingShown(ref)} in ${seat}. Provide it in RunnerDeps.models or pass a Model object.`);
|
|
@@ -30,6 +54,7 @@ function modelOfBinding(value, models, seat, binds) {
|
|
|
30
54
|
const held = catalogModel(value, models);
|
|
31
55
|
if (held === undefined)
|
|
32
56
|
throw unknownModelRefRefusal(value, seat);
|
|
57
|
+
assertModelObject(held, seat, `${bindingShown(value)} (a catalog entry)`);
|
|
33
58
|
return held;
|
|
34
59
|
}
|
|
35
60
|
return value;
|
|
@@ -37,9 +62,19 @@ function modelOfBinding(value, models, seat, binds) {
|
|
|
37
62
|
function assertModelBindingShape(value, seat, binds) {
|
|
38
63
|
if (typeof value === "string")
|
|
39
64
|
return;
|
|
40
|
-
if (typeof value !== "object" || value === null ||
|
|
65
|
+
if (typeof value !== "object" || value === null || isList(value)) {
|
|
41
66
|
throw modelBindingRefusal(seat, "not_a_model_ref", `${seat} is ${bindingShown(value)}, not a model reference — ${binds}.`);
|
|
42
67
|
}
|
|
68
|
+
assertModelObject(value, seat, "a Model object", binds === ROLE_BINDS ? " At a role seat an object with a key beyond model / select / thinking / promptRole / systemPrompt is read as a Model, not as a role record." : "");
|
|
69
|
+
}
|
|
70
|
+
function assertModelObject(value, seat, what, note = "") {
|
|
71
|
+
const id = readMember(value, "id");
|
|
72
|
+
const api = readMember(value, "api");
|
|
73
|
+
const [member, got] = typeof id !== "string" || id === "" ? ["id", id] : typeof api !== "string" ? ["api", api] : [undefined, undefined];
|
|
74
|
+
if (member === undefined)
|
|
75
|
+
return;
|
|
76
|
+
const shown = got === undefined ? "absent" : bindingShown(got);
|
|
77
|
+
throw modelBindingRefusal(seat, "not_a_model_ref", `${seat} is ${what} whose \`${member}\` is ${shown} — a Model carries a non-empty string \`id\` and a string \`api\`.${note}`);
|
|
43
78
|
}
|
|
44
79
|
const ROLE_BINDS = "a role binds a catalog model name or a Model object";
|
|
45
80
|
const roleModelSeat = (named, seat) => (typeof named === "string" ? seat : `${seat}.model`);
|
|
@@ -158,7 +193,10 @@ function roleTierModel(role, models) {
|
|
|
158
193
|
if (!models || !models[TIERS_ACTIVE])
|
|
159
194
|
return undefined;
|
|
160
195
|
const tier = ROLE_TIER_DEFAULTS[role];
|
|
161
|
-
|
|
196
|
+
const m = tier !== undefined ? models[tier] : undefined;
|
|
197
|
+
if (m)
|
|
198
|
+
assertModelObject(m, `tiers.${String(tier)}`, "the catalog entry the tier word names");
|
|
199
|
+
return m;
|
|
162
200
|
}
|
|
163
201
|
const FALLBACK = {
|
|
164
202
|
default: [],
|
|
@@ -178,7 +216,7 @@ function specParts(s, seat = "a role seat") {
|
|
|
178
216
|
if (!isBindingObject(s)) {
|
|
179
217
|
throw modelBindingRefusal(seat, "not_a_model_ref", `${seat} is ${bindingShown(s)}, not a model reference — a role binds a catalog model name, a Model object or a { model, select, thinking, systemPrompt } record.`);
|
|
180
218
|
}
|
|
181
|
-
if (
|
|
219
|
+
if (isModelShaped(s)) {
|
|
182
220
|
return { model: s };
|
|
183
221
|
}
|
|
184
222
|
const cfg = s;
|
|
@@ -192,7 +230,7 @@ function toConfig(s) {
|
|
|
192
230
|
if (s === undefined) {
|
|
193
231
|
return undefined;
|
|
194
232
|
}
|
|
195
|
-
if (typeof s === "string" || (
|
|
233
|
+
if (typeof s === "string" || isModelShaped(s)) {
|
|
196
234
|
return { model: s };
|
|
197
235
|
}
|
|
198
236
|
return s;
|
|
@@ -248,8 +286,13 @@ function partsModel(parts, models, seat = "a role seat") {
|
|
|
248
286
|
if (needs != null && !Array.isArray(needs)) {
|
|
249
287
|
throw modelBindingRefusal(`${seat}.select`, "not_a_model_ref", `${seat}.select.needs is ${bindingShown(needs)}, not a list of capabilities (vision / reasoning).`);
|
|
250
288
|
}
|
|
251
|
-
const held = models != null && typeof models === "object" ? Object.
|
|
252
|
-
|
|
289
|
+
const held = models != null && typeof models === "object" ? Object.entries(models).filter(([, m]) => Boolean(m)) : [];
|
|
290
|
+
for (const [key, m] of held)
|
|
291
|
+
assertModelObject(m, `${seat}.select`, `the catalog entry ${bindingShown(key)} the criteria read`);
|
|
292
|
+
const picked = selectModel(held.map(([, m]) => m), parts.select);
|
|
293
|
+
if (picked !== undefined)
|
|
294
|
+
assertModelObject(picked, `${seat}.select`, "the catalog entry the criteria pick");
|
|
295
|
+
return picked;
|
|
253
296
|
}
|
|
254
297
|
return undefined;
|
|
255
298
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type CompactSessionRequest, type CompactSessionResult } from "../auto-compaction.js";
|
|
2
|
+
import { type SessionStore } from "../session.js";
|
|
3
|
+
import type { RunnerDeps } from "../types.js";
|
|
4
|
+
import { type SessionLockSeats } from "./session-verb-doors.js";
|
|
5
|
+
/** What the Runner lends the compaction: its current deps, its session store, and its lock. */
|
|
6
|
+
export interface CompactSessionSeats extends SessionLockSeats {
|
|
7
|
+
deps: RunnerDeps;
|
|
8
|
+
sessions: SessionStore;
|
|
9
|
+
}
|
|
10
|
+
export declare function compactIdleSession(seats: CompactSessionSeats, request: CompactSessionRequest): Promise<CompactSessionResult>;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { convertToLlm, uuidv7 } from "../../internal/harness.js";
|
|
2
|
+
import { compactionFailureReason, compactionPassReport, DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, manualCompactOutcomeOf, maybeCompact, mintCompactedEvent } from "../auto-compaction.js";
|
|
3
|
+
import { contextOverlayFor } from "../context-settings.js";
|
|
4
|
+
import { admitLegMods, legFacadeSeats } from "../mods/stations.js";
|
|
5
|
+
import { malformedPricingField } from "../pricing.js";
|
|
6
|
+
import { resolveTaskModel } from "../roles.js";
|
|
7
|
+
import { StoredSession } from "../session.js";
|
|
8
|
+
import { emitTrace } from "../trace.js";
|
|
9
|
+
import { egressThrownText } from "../untrusted-egress.js";
|
|
10
|
+
import { emitInputTruncated, legCompactionSettings } from "./compaction-call-options.js";
|
|
11
|
+
import { compactionHookOptions, summaryProviderOptions } from "./compaction-seams.js";
|
|
12
|
+
import { compactionSummaryFallback, compactionSummaryIsDerived } from "./derived-route-fallback.js";
|
|
13
|
+
import { resolveCompactionModel } from "./prepare-config-doors.js";
|
|
14
|
+
import { compactionActivationSnapshot } from "./prepare-tool-disclosure-mount.js";
|
|
15
|
+
import { derivedLegBrain, sideCallBookOf } from "./side-call-account.js";
|
|
16
|
+
import { acquireExistingSession, claimIdleSession, readPlainRequest, shownValue } from "./session-verb-doors.js";
|
|
17
|
+
const VERB = "Runner.compactSession";
|
|
18
|
+
const REQUEST_KEYS = new Set(["sessionId", "model", "instructions", "compaction", "compactionModel", "getApiKeyAndHeaders", "signal"]);
|
|
19
|
+
const SETTINGS_KEYS = new Set(["enabled", "reserveTokens", "keepRecentTokens", "instructions", "clampTolerance"]);
|
|
20
|
+
export async function compactIdleSession(seats, request) {
|
|
21
|
+
const req = screenCompactSessionRequest(request);
|
|
22
|
+
const { deps } = seats;
|
|
23
|
+
const resolved = resolveTaskModel({ model: req.model }, deps);
|
|
24
|
+
const named = resolveCompactionModel(req.compactionModel !== undefined ? { compactionModel: req.compactionModel } : {}, deps, "compactSession.compactionModel");
|
|
25
|
+
if (req.signal?.aborted === true)
|
|
26
|
+
return { outcome: "mooted", events: [] };
|
|
27
|
+
const release = await claimIdleSession(seats, req.sessionId, () => sessionBusyRefusal(req.sessionId));
|
|
28
|
+
try {
|
|
29
|
+
const acquired = await acquireExistingSession(seats.sessions, req.sessionId, VERB, "nothing was compacted");
|
|
30
|
+
const legMods = await admitLegMods({ spec: {}, deps, sessionId: req.sessionId });
|
|
31
|
+
return await compactOnSession(req, deps, new StoredSession(acquired.session.getStorage()), resolved, named, legMods);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
release();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function compactOnSession(req, deps, session, resolved, named, legMods) {
|
|
38
|
+
const { sessionId } = req;
|
|
39
|
+
const events = [];
|
|
40
|
+
const ident = () => ({ eventId: uuidv7() });
|
|
41
|
+
const passReport = compactionPassReport({ push: (e) => events.push(e) }, ident);
|
|
42
|
+
const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
|
|
43
|
+
const telemetry = { unpricedSpend: false, tracer: deps.tracer, taskId: sessionId };
|
|
44
|
+
const accountBook = sideCallBookOf(stats, { telemetry, pricing: () => deps.pricing, noteUnevaluablePriceTable: (p) => void (malformedPricingField(p) !== undefined && (telemetry.unpricedSpend = true)), catalog: deps.models });
|
|
45
|
+
let calls = 0;
|
|
46
|
+
const book = (lane, model, reply) => {
|
|
47
|
+
calls += 1;
|
|
48
|
+
accountBook(lane, model, reply);
|
|
49
|
+
};
|
|
50
|
+
const answer = (outcome) => ({
|
|
51
|
+
outcome,
|
|
52
|
+
events,
|
|
53
|
+
...(calls > 0
|
|
54
|
+
? {
|
|
55
|
+
usage: {
|
|
56
|
+
tokens: stats.tokens,
|
|
57
|
+
totalInputTokens: stats.totalInputTokens,
|
|
58
|
+
promptTokens: stats.promptTokens,
|
|
59
|
+
cachedTokens: stats.cachedTokens,
|
|
60
|
+
cacheWriteTokens: stats.cacheWriteTokens,
|
|
61
|
+
outputTokens: stats.outputTokens,
|
|
62
|
+
costMicroUsd: stats.costMicroUsd,
|
|
63
|
+
...(stats.usageMissing === true ? { usageMissing: true } : {}),
|
|
64
|
+
...(telemetry.unpricedSpend ? { unpriced: true } : {}),
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
: {}),
|
|
68
|
+
});
|
|
69
|
+
const trace = deps.tracer;
|
|
70
|
+
try {
|
|
71
|
+
const compactionModel = compactionSummaryIsDerived(req.compactionModel !== undefined, named) ? await compactionSummaryFallback({ derived: named, primary: resolved.model, brain: deps.brain, getApiKeyAndHeaders: req.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId }) : named;
|
|
72
|
+
const branch = await session.getBranch();
|
|
73
|
+
const contextMessages = convertToLlm((await session.buildContext()).messages);
|
|
74
|
+
bindSessionFacade(legMods, { deps, req, model: resolved.model, messages: contextMessages, book });
|
|
75
|
+
const overlay = contextOverlayFor(deps.contextSettings, resolved.modelRef, deps);
|
|
76
|
+
const activeTools = compactionActivationSnapshot(branch);
|
|
77
|
+
const thinking = resolved.thinking ?? resolved.model.defaultThinking;
|
|
78
|
+
const comp = await maybeCompact({
|
|
79
|
+
session,
|
|
80
|
+
model: resolved.model,
|
|
81
|
+
...(overlay !== undefined ? { contextOverlay: overlay } : {}),
|
|
82
|
+
...(compactionModel !== undefined ? { compactionModel } : {}),
|
|
83
|
+
brain: derivedLegBrain(() => deps.brain, "compaction", book),
|
|
84
|
+
...(req.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: req.getApiKeyAndHeaders } : {}),
|
|
85
|
+
...(thinking !== undefined ? { thinking } : {}),
|
|
86
|
+
settings: legCompactionSettings(req.compaction !== undefined ? { compaction: req.compaction } : {}, overlay),
|
|
87
|
+
customInstructions: req.instructions ?? req.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
|
|
88
|
+
...(req.signal !== undefined ? { signal: req.signal, manualCancelSignal: req.signal } : {}),
|
|
89
|
+
minTokens: 0,
|
|
90
|
+
force: true,
|
|
91
|
+
onInputTruncated: emitInputTruncated(trace, sessionId),
|
|
92
|
+
...(activeTools.length > 0 ? { activeTools } : {}),
|
|
93
|
+
...summaryProviderOptions({ deps }, 0),
|
|
94
|
+
...compactionHookOptions({ deps }, { modBus: legMods.bus, hookIdentity: undefined, sessionId }, "manual", req.signal),
|
|
95
|
+
onStarted: passReport.onStarted,
|
|
96
|
+
});
|
|
97
|
+
const outcome = manualCompactOutcomeOf(comp);
|
|
98
|
+
if (comp.compacted) {
|
|
99
|
+
events.push(mintCompactedEvent(comp, "manual", ident()));
|
|
100
|
+
return answer(outcome);
|
|
101
|
+
}
|
|
102
|
+
if (comp.blocked)
|
|
103
|
+
emitTrace(trace, () => ({ kind: "compaction.blocked", version: 1, taskId: sessionId, trigger: "manual", ts: Date.now() }));
|
|
104
|
+
else if (comp.disabled)
|
|
105
|
+
emitTrace(trace, () => ({ kind: "compaction.disabled", version: 1, taskId: sessionId, ts: Date.now() }));
|
|
106
|
+
else
|
|
107
|
+
emitTrace(trace, () => ({ kind: "compaction.noop", version: 1, taskId: sessionId, ts: Date.now() }));
|
|
108
|
+
events.push({ type: "compaction_outcome", outcome: outcome, trigger: "manual", ...ident() });
|
|
109
|
+
return answer(outcome);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (isCompactionManualCancel(err)) {
|
|
113
|
+
emitTrace(trace, () => ({ kind: "compaction.mooted", version: 1, taskId: sessionId, reason: "cancelled", ts: Date.now() }));
|
|
114
|
+
events.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "cancelled", ...passReport.end, ...ident() });
|
|
115
|
+
return answer("mooted");
|
|
116
|
+
}
|
|
117
|
+
deps.onError?.(asError(err), { phase: "compaction", sessionId });
|
|
118
|
+
const reason = compactionFailureReason(err);
|
|
119
|
+
emitTrace(trace, () => ({ kind: "compaction.failed", version: 1, taskId: sessionId, trigger: "manual", reason, ts: Date.now() }));
|
|
120
|
+
events.push({ type: "compaction_outcome", outcome: "failed", trigger: "manual", reason, ...passReport.end, ...ident() });
|
|
121
|
+
return answer("failed");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function bindSessionFacade(legMods, input) {
|
|
125
|
+
const unavailable = (method) => {
|
|
126
|
+
throw new Error(`${method}: not available on a session-level compaction (${VERB}) — there is no run, so no execution environment, working directory, tool roster or agent listing`);
|
|
127
|
+
};
|
|
128
|
+
const seats = legFacadeSeats({
|
|
129
|
+
deps: input.deps,
|
|
130
|
+
spec: input.req.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: input.req.getApiKeyAndHeaders } : {},
|
|
131
|
+
sessionId: input.req.sessionId,
|
|
132
|
+
model: input.model,
|
|
133
|
+
executionEnv: { exec: () => unavailable("process.run") },
|
|
134
|
+
taskRoot: "",
|
|
135
|
+
cwdRef: undefined,
|
|
136
|
+
lastBrainContext: () => ({ messages: input.messages }),
|
|
137
|
+
activeTools: new Set(),
|
|
138
|
+
agentListing: undefined,
|
|
139
|
+
book: (answeredBy, reply) => input.book("mod", answeredBy, reply),
|
|
140
|
+
toolCall: () => unavailable("tool.call"),
|
|
141
|
+
});
|
|
142
|
+
legMods.bindFacade({
|
|
143
|
+
...seats,
|
|
144
|
+
cwd: () => unavailable("session.cwd"),
|
|
145
|
+
root: () => unavailable("session.root"),
|
|
146
|
+
tools: () => unavailable("tool.list"),
|
|
147
|
+
agents: () => unavailable("agent.list"),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function asError(err) {
|
|
151
|
+
return err instanceof Error ? err : new Error(egressThrownText("same-principal", err));
|
|
152
|
+
}
|
|
153
|
+
function screenCompactSessionRequest(request) {
|
|
154
|
+
const bad = (what) => {
|
|
155
|
+
const e = new Error(`${VERB}: ${what} — nothing was compacted. The request is { sessionId, model, instructions?, compaction?, compactionModel?, getApiKeyAndHeaders?, signal? }.`);
|
|
156
|
+
e.code = "compact.invalid_request";
|
|
157
|
+
throw e;
|
|
158
|
+
};
|
|
159
|
+
const { sessionId, model, instructions, compaction, compactionModel, getApiKeyAndHeaders, signal } = readPlainRequest(request, REQUEST_KEYS, bad);
|
|
160
|
+
if (typeof sessionId !== "string" || sessionId === "")
|
|
161
|
+
return bad(`sessionId must be a non-empty string, got ${shownValue(sessionId)}`);
|
|
162
|
+
if (model === undefined)
|
|
163
|
+
return bad("model is required — the model the session's next turn runs on, whose window bounds the pass");
|
|
164
|
+
if (instructions !== undefined && typeof instructions !== "string")
|
|
165
|
+
return bad(`instructions must be a string when given, got ${shownValue(instructions)}`);
|
|
166
|
+
if (getApiKeyAndHeaders !== undefined && typeof getApiKeyAndHeaders !== "function")
|
|
167
|
+
return bad(`getApiKeyAndHeaders must be a function when given, got ${shownValue(getApiKeyAndHeaders)}`);
|
|
168
|
+
if (signal !== undefined && !(signal instanceof AbortSignal))
|
|
169
|
+
return bad(`signal must be an AbortSignal when given, got ${shownValue(signal)}`);
|
|
170
|
+
return {
|
|
171
|
+
sessionId,
|
|
172
|
+
model: model,
|
|
173
|
+
instructions,
|
|
174
|
+
compaction: compaction === undefined ? undefined : screenSettings(compaction, bad),
|
|
175
|
+
compactionModel: compactionModel,
|
|
176
|
+
getApiKeyAndHeaders: getApiKeyAndHeaders,
|
|
177
|
+
signal,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function screenSettings(raw, bad) {
|
|
181
|
+
const { enabled, reserveTokens, keepRecentTokens, instructions, clampTolerance } = readPlainRequest(raw, SETTINGS_KEYS, (what) => bad(`compaction: ${what}`));
|
|
182
|
+
if (enabled !== undefined && typeof enabled !== "boolean")
|
|
183
|
+
return bad(`compaction.enabled must be a boolean when given, got ${shownValue(enabled)}`);
|
|
184
|
+
for (const [name, v] of [["reserveTokens", reserveTokens], ["keepRecentTokens", keepRecentTokens], ["clampTolerance", clampTolerance]]) {
|
|
185
|
+
if (v !== undefined && typeof v !== "number")
|
|
186
|
+
return bad(`compaction.${name} must be a number when given, got ${shownValue(v)}`);
|
|
187
|
+
}
|
|
188
|
+
if (instructions !== undefined && typeof instructions !== "string")
|
|
189
|
+
return bad(`compaction.instructions must be a string when given, got ${shownValue(instructions)}`);
|
|
190
|
+
return {
|
|
191
|
+
...(enabled !== undefined ? { enabled: enabled } : {}),
|
|
192
|
+
...(reserveTokens !== undefined ? { reserveTokens: reserveTokens } : {}),
|
|
193
|
+
...(keepRecentTokens !== undefined ? { keepRecentTokens: keepRecentTokens } : {}),
|
|
194
|
+
...(instructions !== undefined ? { instructions: instructions } : {}),
|
|
195
|
+
...(clampTolerance !== undefined ? { clampTolerance: clampTolerance } : {}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function sessionBusyRefusal(sessionId) {
|
|
199
|
+
const e = new Error(`${VERB}: a task is running or queued on session "${sessionId}" in this Runner, or a write one of its tasks issued has not settled — compacting it now would rewrite the transcript under that work, so nothing was compacted; a running task takes the request through its own stream (TaskStream.compact), otherwise retry once it has ended and its writes have settled`);
|
|
200
|
+
e.code = "compact.session_busy";
|
|
201
|
+
return e;
|
|
202
|
+
}
|
|
@@ -156,7 +156,7 @@ export declare function emitInputTruncated(tracer: TracerHook | undefined, taskI
|
|
|
156
156
|
*/
|
|
157
157
|
export declare function legContextOverlayOption(prepared: Prepared, runner: RunnerDepsSeat): Pick<MaybeCompactOptions, "contextOverlay">;
|
|
158
158
|
/**
|
|
159
|
-
* The compaction SETTINGS a pass runs under — the assembly all three lanes share.
|
|
159
|
+
* The compaction SETTINGS a pass runs under — the assembly all three lanes and the out-of-run `Runner.compactSession` share.
|
|
160
160
|
*
|
|
161
161
|
* `{ ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction }` was hand-copied at each lane, and it is where the
|
|
162
162
|
* deployment's live `enabled` declaration belongs: `maybeCompact` has exactly one enablement gate (it reads
|
|
@@ -165,4 +165,4 @@ export declare function legContextOverlayOption(prepared: Prepared, runner: Runn
|
|
|
165
165
|
* compaction runs" a single expression ({@link compactionEnabled}: the request authored it > the deployment
|
|
166
166
|
* declared it > on).
|
|
167
167
|
*/
|
|
168
|
-
export declare function legCompactionSettings(spec: TaskSpec, overlay: ContextWindowOverlay | undefined): CompactionSettings;
|
|
168
|
+
export declare function legCompactionSettings(spec: Pick<TaskSpec, "compaction">, overlay: ContextWindowOverlay | undefined): CompactionSettings;
|