@sema-agent/core 5.14.0 → 5.15.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 +122 -0
- package/dist/brain/errors.js +21 -1
- package/dist/brain/retry.d.ts +5 -0
- package/dist/brain/retry.js +16 -4
- package/dist/brain/stream-engine.js +7 -3
- package/dist/core/checkpoint-store.js +46 -26
- package/dist/core/memory-engine/engine.d.ts +3 -1
- package/dist/core/memory-engine/engine.js +4 -3
- package/dist/core/memory-recall.d.ts +1 -1
- package/dist/core/memory-recall.js +3 -2
- package/dist/core/runner/prepare-memory.d.ts +1 -0
- package/dist/core/runner/prepare-memory.js +3 -3
- package/dist/core/runner/prepare-task.d.ts +3 -0
- package/dist/core/runner/prepare-task.js +43 -12
- package/dist/core/runner/runtask.js +106 -30
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +65 -16
- package/dist/core/runner/turn-attachments.d.ts +4 -0
- package/dist/core/runner/turn-attachments.js +15 -2
- package/dist/core/task-tool-shape.js +4 -3
- package/dist/core/trace.d.ts +6 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/prompts/supervisor.d.ts +2 -2
- package/dist/prompts/supervisor.js +5 -4
- package/dist/tools/fs/fs-bash.d.ts +1 -0
- package/dist/tools/fs/fs-bash.js +1 -1
- package/dist/tools/fs/gh-rate-limit.d.ts +1 -1
- package/dist/tools/fs/gh-rate-limit.js +4 -3
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,129 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.15.0 — 2026-08-06
|
|
4
|
+
|
|
5
|
+
### BREAKING
|
|
6
|
+
|
|
7
|
+
- **`TaskSpec.toolMaterializeStrategy` defaults to `"swap"` again** (it defaulted to `"static"` for one
|
|
8
|
+
release window). Under `"static"` a deferred tool's placeholder never swaps: the tools block keeps
|
|
9
|
+
advertising `{"type":"object","properties":{}}` and the real schema reaches the model only as text in
|
|
10
|
+
the `ToolSearch` result. That is fine for a model whose tool arguments are free-form JSON, and
|
|
11
|
+
structurally unusable for one served with **constrained decoding**, where the advertised wire schema is
|
|
12
|
+
enforced at the sampler. On the mainstream implementations — grammar compiled from the declared
|
|
13
|
+
`properties` — `{}` becomes the only argument object the model can emit, so the engine's corrective
|
|
14
|
+
invalid-arguments rejection cannot be acted on and the run burns turns without converging. (A strictly
|
|
15
|
+
draft-faithful decoder would read the absent `additionalProperties` as permissive; the model still has
|
|
16
|
+
no schema to form other keys from, so that path degrades to guessing rather than converging.)
|
|
17
|
+
The condition for `"static"` is also a property of the WHOLE serving route rather than of one model —
|
|
18
|
+
the degrading brain can fall back across providers mid-run and `limits.degrade` can hand the run to a
|
|
19
|
+
different model outright, and neither rebuilds the disclosure strategy — so it is now a strict explicit
|
|
20
|
+
opt-in: set it only if every model the run can reach, fallback targets included, has been verified to
|
|
21
|
+
read schemas from result text. **Who is affected:** a deployment that relied on the default to keep its
|
|
22
|
+
provider prompt cache intact across activations now sees the pre-`"static"` behavior (activation calls
|
|
23
|
+
`setTools`, invalidating the cache suffix) unless it sets `toolMaterializeStrategy: "static"` itself.
|
|
24
|
+
**Pinned faces that move with the default:** the `tools_delta` attachment says "now available … Their
|
|
25
|
+
full schemas are loaded" (not "now active"), the `ToolSearch` description says activation "loads its
|
|
26
|
+
full parameter schema" (not "returns … in the result"), the `ToolSearch` result no longer inlines
|
|
27
|
+
`parameters:` per matched tool, and the post-activation tools block carries the real schema again — a
|
|
28
|
+
consumer pinning any of these under the default must re-pin. `SEMA_TOOL_MATERIALIZE_STRATEGY` still
|
|
29
|
+
fills an absent spec value, and an env-sourced `"static"` on a `deferSelfResolve: false` task still
|
|
30
|
+
degrades to `"swap"` (the explicit spec pairing stays refused with `config.tool_materialize_unreachable`).
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`prompt.assembled` reports the resolved deferred-tool disclosure strategy** —
|
|
35
|
+
`toolDisclosure: {deferredTools, strategy, source}` (new exported type `ToolDisclosureManifest`;
|
|
36
|
+
absent when the leg deferred nothing). `strategy` is the EFFECTIVE value after the direct-call-lane
|
|
37
|
+
interlock, and `source` names the seat that chose it: `"spec"` / `"env"` / `"default"` /
|
|
38
|
+
`"degraded_no_direct_lane"` (the interlock narrowed a `"static"` request to `"swap"`; that value
|
|
39
|
+
implies an env-sourced request, since the spec pairing is refused at prepare and the default is not
|
|
40
|
+
`"static"`). "Which strategy did this leg run under, and who chose it" was previously answerable only
|
|
41
|
+
by re-deriving spec > env > release default by hand.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **Model-visible text stops teaching retired tool names** (#181 census F batch). Three faces taught
|
|
46
|
+
names a model cannot call (RB-476-A retired alias resolution — a retired name is a loud roster
|
|
47
|
+
miss): the selective-recall manifest affordance said "call Recall" (a name core registers as
|
|
48
|
+
removed; the affordance's tool name is now caller-supplied via `composeSelectiveBody`'s new
|
|
49
|
+
optional `recallToolName` parameter, name-free when omitted); both `ORCHESTRATION_GUIDANCE`
|
|
50
|
+
blocks said `run_workflow` — on the deferred short form a complete dead link, since `ToolSearch`'s
|
|
51
|
+
`select:` arm matches exact wire names — and now interpolate `RUN_WORKFLOW_TOOL_NAME`
|
|
52
|
+
(`"Workflow"`); TaskOutput's lanes description and `task_id` parameter said `RunWorkflow` and now
|
|
53
|
+
say `Workflow`. A consumer pinning any of these strings must re-pin. A new gate
|
|
54
|
+
(`test/tool-name-literal-gate.test.ts`) scans every src string literal against the whole
|
|
55
|
+
`RETIRED_TOOL_NAMES` table with a frozen per-bucket ratchet, so this class cannot regrow silently.
|
|
56
|
+
- **The `# Memory` write instruction requires the Write tool on the roster** (behavior face). The
|
|
57
|
+
memory engine's injection gated the CC-verbatim write instruction on a writable scope alone;
|
|
58
|
+
a `handsReadOnly` (or hands-less) run with a memory `writeScope` was instructed to write with a
|
|
59
|
+
tool its roster does not carry. `MemoryEngine.inject` takes an optional `writeToolMounted`
|
|
60
|
+
(omitted ⇒ historic behavior for engine-direct hosts; the Runner passes the real hands-write-half
|
|
61
|
+
predicate), and the RB-276 index seed follows the same gate. **Who is affected:** a deployment
|
|
62
|
+
running memory-enabled tasks with `handsReadOnly: true` (or no execution env) no longer receives
|
|
63
|
+
the `# Memory` instruction or the index read-seed on those runs — the fenced index still injects.
|
|
64
|
+
- **The gh rate-limit hint's Monitor clause follows the real Monitor mount.** The SR-2 reminder
|
|
65
|
+
unconditionally closed with "use the Monitor tool"; Monitor mounts only when background task tools
|
|
66
|
+
do. `ghRateLimitHint` (and `HandsToolkitOptions`/`createBashTool` `monitorToolActive`) drop exactly
|
|
67
|
+
that clause when the caller declares Monitor unmounted; omitted keeps the byte-identical historic
|
|
68
|
+
wording.
|
|
69
|
+
- **`ToolSearch` no longer activates a name the roster has withdrawn.** The deferred registry is frozen
|
|
70
|
+
at prepare; the roster is not — an MCP refresh can take a deferred name off the mount. An exact
|
|
71
|
+
`select:` still resolved through the stale registry, added the name to the active set, "succeeded" at
|
|
72
|
+
a rematerialize that mounted nothing, and announced a tool that could not be called. Withdrawn names
|
|
73
|
+
are now reported as `No longer available: <names> — withdrawn by its provider and cannot be activated
|
|
74
|
+
or called`, ride the result's `details.missing`, and never enter the active set.
|
|
75
|
+
- **The deferred placeholder's corrective round is now bounded.** A shape-invalid direct call is answered
|
|
76
|
+
with the real declared schema so the corrected call is one turn away — which assumes the caller can act
|
|
77
|
+
on a schema once it has one. A caller whose arguments are constrained to the ADVERTISED schema cannot,
|
|
78
|
+
and re-sent the identical rejected arguments for as many turns as the budget allowed. After
|
|
79
|
+
`DEFERRED_NO_PROGRESS_LIMIT` (3) consecutive rejections of the IDENTICAL failure shape on one
|
|
80
|
+
placeholder, the lane stops re-teaching: it returns a terminal rejection that names the condition with
|
|
81
|
+
the stable token `tool.deferred_schema_incompatible` (in the model-facing text and on the tool result's
|
|
82
|
+
`details.noProgress`) and votes the tool batch terminated. Any other outcome — a different validation
|
|
83
|
+
error, or a successful call — resets the count, so the bound tracks being STUCK, not being wrong.
|
|
84
|
+
`TaskResult.errorCode` is unchanged: this terminal is reported on the tool result, not as a task
|
|
85
|
+
failure code.
|
|
86
|
+
- **The invalid-arguments rejection no longer overstates where its schema lives.** It used to say "use it
|
|
87
|
+
for this and later calls" under both strategies; under `"static"` the schema is carried by that one
|
|
88
|
+
result and is gone with the next compaction. The text now says either "the next request's tools list
|
|
89
|
+
will advertise it too" (swap) or "carried by THIS result only" plus the `ToolSearch` re-select spelling
|
|
90
|
+
(static). A declaration too large to inline is called an "ABRIDGED copy" instead of "full" — past the
|
|
91
|
+
model-facing bound the serialization comes back with its middle spliced out and is not valid JSON.
|
|
92
|
+
- **A declaration the static face cannot carry in text is exempted from the static face per tool.** Under
|
|
93
|
+
`"static"` the only in-context schema carrier is result text; a declaration that does not serialize, or
|
|
94
|
+
that the model-facing bound truncates, has no carrier there, and the tool was announced as activated
|
|
95
|
+
with a schema the model never received in usable form. Such a tool now materializes into the tools block
|
|
96
|
+
on activation (the rest of the run stays static), the `ToolSearch` line says where its declaration is,
|
|
97
|
+
and the result head stops promising schemas it did not print. The `tools_delta` boundary frame carries
|
|
98
|
+
the same exception (one `Exception: <names> — too large to inline…` line) instead of asserting compact
|
|
99
|
+
placeholders for a name whose full schema is visibly on the wire; with no exemptions in play the frame
|
|
100
|
+
is byte-identical to before. The exemption is read from the LIVE roster on every call, so an MCP
|
|
101
|
+
refresh that changes a declaration's size cannot leave the lane describing the old carrier — and the
|
|
102
|
+
exemption is MONOTONE within a leg: a declaration that shrinks back under the bound does not put the
|
|
103
|
+
compact placeholder back on a tool whose schema was only ever delivered through the tools block.
|
|
104
|
+
|
|
105
|
+
### Fixed (receive-time additions)
|
|
106
|
+
|
|
107
|
+
- Ruled at receive: the deferred no-progress terminal stays a TOOL-RESULT face (`tool.deferred_schema_incompatible`
|
|
108
|
+
marker + `details.noProgress`) and does NOT mint a `TaskResult.errorCode` — the run can continue on other
|
|
109
|
+
tools, so a run-level code would misclassify a per-tool condition.
|
|
110
|
+
- Ruled at receive: a malformed `SEMA_TOOL_MATERIALIZE_STRATEGY` still refuses the task loudly even when a
|
|
111
|
+
valid spec value is present — a deployment running with a broken env var must hear about it before the
|
|
112
|
+
first task that omits the spec value inherits the typo.
|
|
113
|
+
|
|
3
114
|
## 5.14.0 — 2026-08-06
|
|
4
115
|
|
|
116
|
+
> **Post-release addendum (2026-08-06, disclosure gap reported by a consumer):** the fail-open survey
|
|
117
|
+
> (shipped in 5.13.0's window, but its consequence surfaced against 5.14.0 pickups) hardened
|
|
118
|
+
> `canonicalizeTarget`'s existence probe — an ERRORED `exists` no longer reads as "does not exist yet"
|
|
119
|
+
> and instead reports `{ok:false, unresolvedSymlink:true}`. Combined with `createSensitivePathPolicy`'s
|
|
120
|
+
> deny on `unresolvedSymlink`, an execution env whose `exists` always throws (a stub/lexical env with
|
|
121
|
+
> no real filesystem) now sees every write target denied — the gate degrades from "guarded" to
|
|
122
|
+
> "closed". That is the intended fail-closed direction for a PROBE FAILURE, but such an env should
|
|
123
|
+
> answer honestly instead of throwing: return `ok(false)` ("this env cannot see a filesystem") and the
|
|
124
|
+
> gate resolves normally. Also: `DelegationTaskType` was named in the ship note but missed the package
|
|
125
|
+
> root; the export lands in the next release — type the `taskType` key as an open `string` until then.
|
|
126
|
+
|
|
5
127
|
### BREAKING
|
|
6
128
|
|
|
7
129
|
- **`CheckpointStore.setPendingSteer` APPENDS to a bounded ordered queue instead of overwriting a single seat**
|
package/dist/brain/errors.js
CHANGED
|
@@ -42,6 +42,25 @@ export function extractErrorCode(errorMessage) {
|
|
|
42
42
|
export function stripErrorCodePrefix(errorMessage) {
|
|
43
43
|
return errorMessage.replace(CODE_PREFIX_RE, "");
|
|
44
44
|
}
|
|
45
|
+
const STALE_CONNECTION_CODES = new Set([
|
|
46
|
+
"ECONNRESET",
|
|
47
|
+
"EPIPE",
|
|
48
|
+
"ConnectionClosed",
|
|
49
|
+
"ETIMEDOUT",
|
|
50
|
+
"ECONNABORTED",
|
|
51
|
+
"ERR_SOCKET_CLOSED",
|
|
52
|
+
"StreamSuspended",
|
|
53
|
+
]);
|
|
54
|
+
function hasStaleConnectionCode(e) {
|
|
55
|
+
let cur = e;
|
|
56
|
+
for (let depth = 0; depth < 5 && cur !== undefined && cur !== null; depth++) {
|
|
57
|
+
const code = typeof cur.code === "string" ? cur.code : undefined;
|
|
58
|
+
if (code !== undefined && STALE_CONNECTION_CODES.has(code))
|
|
59
|
+
return true;
|
|
60
|
+
cur = cur instanceof Error ? cur.cause : undefined;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
45
64
|
export function describeNetworkError(e) {
|
|
46
65
|
const top = e instanceof Error ? e.message : String(e);
|
|
47
66
|
const parts = [];
|
|
@@ -54,5 +73,6 @@ export function describeNetworkError(e) {
|
|
|
54
73
|
parts.push(piece);
|
|
55
74
|
cur = cur instanceof Error ? cur.cause : undefined;
|
|
56
75
|
}
|
|
57
|
-
|
|
76
|
+
const body = parts.length > 0 ? `${top} (${parts.join(" ← ")})` : top;
|
|
77
|
+
return hasStaleConnectionCode(e) ? `${body} — possibly a stale connection; a fresh one may succeed` : body;
|
|
58
78
|
}
|
package/dist/brain/retry.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export declare function parseRetryAfter(res: Response | undefined): number | undefined;
|
|
2
2
|
export declare function parseRateLimitReset(res: Response | undefined): number | undefined;
|
|
3
|
+
export interface ProviderWaitHint {
|
|
4
|
+
readonly ms: number;
|
|
5
|
+
readonly rawMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function providerWaitHint(res: Response | undefined): ProviderWaitHint | undefined;
|
|
3
8
|
export declare function retryBackoffMs(baseDelayMs: number, attempt: number, res?: Response, rand?: () => number): number;
|
package/dist/brain/retry.js
CHANGED
|
@@ -24,17 +24,29 @@ export function parseRateLimitReset(res) {
|
|
|
24
24
|
const ms = Math.round(epochSecs * 1000 - Date.now());
|
|
25
25
|
return ms > 0 ? ms : undefined;
|
|
26
26
|
}
|
|
27
|
-
function
|
|
27
|
+
function clampHint(rawMs) {
|
|
28
|
+
return rawMs > MAX_HEADER_WAIT_MS ? { ms: MAX_HEADER_WAIT_MS, rawMs } : { ms: rawMs };
|
|
29
|
+
}
|
|
30
|
+
export function providerWaitHint(res) {
|
|
28
31
|
const hints = [];
|
|
29
32
|
const retryAfter = parseRetryAfter(res);
|
|
30
33
|
if (retryAfter !== undefined)
|
|
31
|
-
hints.push(
|
|
34
|
+
hints.push(clampHint(retryAfter));
|
|
32
35
|
if (res?.status === RATE_LIMIT_STATUS) {
|
|
33
36
|
const reset = parseRateLimitReset(res);
|
|
34
37
|
if (reset !== undefined)
|
|
35
|
-
hints.push(
|
|
38
|
+
hints.push(clampHint(reset));
|
|
36
39
|
}
|
|
37
|
-
|
|
40
|
+
if (hints.length === 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
return hints.reduce((largest, next) => {
|
|
43
|
+
if (next.ms !== largest.ms)
|
|
44
|
+
return next.ms > largest.ms ? next : largest;
|
|
45
|
+
return (next.rawMs ?? next.ms) > (largest.rawMs ?? largest.ms) ? next : largest;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function providerWaitHintMs(res) {
|
|
49
|
+
return providerWaitHint(res)?.ms;
|
|
38
50
|
}
|
|
39
51
|
export function retryBackoffMs(baseDelayMs, attempt, res, rand = Math.random) {
|
|
40
52
|
const exp = Math.min(MAX_BACKOFF_MS, baseDelayMs * 2 ** attempt);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createAssistantMessageEventStream, } from "../internal/llm.js";
|
|
2
2
|
import { FLOOR_OUTPUT_TOKENS, parseContextOverflow, planOutputCapAdjustment } from "./context-overflow.js";
|
|
3
3
|
import { BrainError, classifyHttp, describeNetworkError } from "./errors.js";
|
|
4
|
-
import { retryBackoffMs } from "./retry.js";
|
|
4
|
+
import { providerWaitHint, retryBackoffMs } from "./retry.js";
|
|
5
5
|
import { emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
|
|
6
6
|
import { createConnectController, resolveStallTimeoutMs } from "./timeout.js";
|
|
7
7
|
const DEFAULT_MAX_RETRIES = 10;
|
|
@@ -372,6 +372,10 @@ export function runStreamingBrain(args) {
|
|
|
372
372
|
const retryable = shouldRetryHeaderVerdict(r) ?? retryableByStatus;
|
|
373
373
|
if (retryable && attempt < maxRetries) {
|
|
374
374
|
const delayMs = retryBackoffMs(baseDelay, attempt, r);
|
|
375
|
+
const waitHint = providerWaitHint(r);
|
|
376
|
+
const truncationNote = waitHint?.rawMs !== undefined
|
|
377
|
+
? ` (the requested wait was longer than the cap; honoring ${Math.ceil(waitHint.ms / 1000)}s instead of ${Math.ceil(waitHint.rawMs / 1000)}s)`
|
|
378
|
+
: "";
|
|
375
379
|
const statusPhase = r?.status === 429 ? "rate_limited" : netErr !== undefined ? "reconnecting" : "retrying";
|
|
376
380
|
emitBrainTelemetry({ kind: "retry", attempt: attempt + 1, phase: "connect" });
|
|
377
381
|
announcedRetry = true;
|
|
@@ -381,10 +385,10 @@ export function runStreamingBrain(args) {
|
|
|
381
385
|
await sleepAnnouncingRetry(delayMs, signal, (remainingMs) => ({
|
|
382
386
|
phase: statusPhase,
|
|
383
387
|
detail: statusPhase === "rate_limited"
|
|
384
|
-
?
|
|
388
|
+
? `rate limited, backing off${truncationNote}`
|
|
385
389
|
: statusPhase === "reconnecting"
|
|
386
390
|
? "connection lost, reconnecting"
|
|
387
|
-
:
|
|
391
|
+
: `transient error, retrying${truncationNote}`,
|
|
388
392
|
retryInSec: Math.ceil(remainingMs / 1000),
|
|
389
393
|
retryInMs: remainingMs,
|
|
390
394
|
attempt: attempt + 1,
|
|
@@ -243,6 +243,9 @@ export function winnerFromOutcome(outcome) {
|
|
|
243
243
|
}
|
|
244
244
|
const CHECKPOINT_CONTROL_CHARS_RE = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
245
245
|
export function validatePendingSteer(steer) {
|
|
246
|
+
if (steer === null || typeof steer !== "object") {
|
|
247
|
+
throw new CheckpointError("steering.invalid_content", `steering entry must be an object carrying text/trusted (got ${steer === null ? "null" : typeof steer})`);
|
|
248
|
+
}
|
|
246
249
|
const allowed = new Set(PENDING_STEER_FROZEN_FIELDS.filter((f) => f !== "seq"));
|
|
247
250
|
for (const key of Object.keys(steer)) {
|
|
248
251
|
if (!allowed.has(key)) {
|
|
@@ -250,53 +253,76 @@ export function validatePendingSteer(steer) {
|
|
|
250
253
|
`a field this worker does not know would be dropped silently on the parked leg`);
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
+
const text = steer.text;
|
|
257
|
+
const trusted = steer.trusted;
|
|
258
|
+
const actor = steer.actor;
|
|
259
|
+
const inputId = steer.inputId;
|
|
260
|
+
const priority = steer.priority;
|
|
261
|
+
if (typeof text !== "string") {
|
|
262
|
+
throw new CheckpointError("steering.invalid_content", "steering text must be a string");
|
|
263
|
+
}
|
|
264
|
+
if (typeof trusted !== "boolean") {
|
|
265
|
+
throw new CheckpointError("steering.invalid_content", "steering trusted must be a boolean stated by the caller");
|
|
266
|
+
}
|
|
267
|
+
if (inputId !== undefined && typeof inputId !== "string") {
|
|
268
|
+
throw new CheckpointError("steering.invalid_content", "steering inputId must be a string when supplied");
|
|
269
|
+
}
|
|
270
|
+
if (priority !== undefined && priority !== "now" && priority !== "next" && priority !== "later") {
|
|
271
|
+
throw new CheckpointError("steering.invalid_content", `steering priority "${typeof priority === "string" ? priority : priority === null ? "null" : typeof priority}" is outside the injection-priority domain ("now", "next", "later")`);
|
|
272
|
+
}
|
|
273
|
+
const actorTwin = actor === undefined ? undefined : captureActorAssertion(actor);
|
|
274
|
+
if (inputId !== undefined && (inputId === "" || inputId.length > MAX_STEER_INPUT_ID_CHARS)) {
|
|
256
275
|
throw new CheckpointError("steering.invalid_content", `steering inputId must be a non-empty string of at most ${MAX_STEER_INPUT_ID_CHARS} characters`);
|
|
257
276
|
}
|
|
258
|
-
if (
|
|
277
|
+
if (inputId === LEGACY_PENDING_STEER_INPUT_ID) {
|
|
259
278
|
throw new CheckpointError("steering.invalid_content", `steering inputId "${LEGACY_PENDING_STEER_INPUT_ID}" is reserved for a pre-queue parked steer and cannot be supplied by a caller`);
|
|
260
279
|
}
|
|
261
|
-
if (sanitizeUntrustedText(
|
|
280
|
+
if (sanitizeUntrustedText(text) !== text) {
|
|
262
281
|
throw new CheckpointError("steering.invalid_content", "steering text must not contain a system-reminder break-out tag");
|
|
263
282
|
}
|
|
264
|
-
if (CHECKPOINT_CONTROL_CHARS_RE.test(
|
|
283
|
+
if (CHECKPOINT_CONTROL_CHARS_RE.test(text)) {
|
|
265
284
|
throw new CheckpointError("steering.invalid_content", "steering text must not contain control characters");
|
|
266
285
|
}
|
|
267
|
-
if (
|
|
268
|
-
throw new CheckpointError("steering.invalid_content", `steering text must be at most ${MAX_PENDING_STEER_CHARS} characters (got ${
|
|
286
|
+
if (text.length > MAX_PENDING_STEER_CHARS) {
|
|
287
|
+
throw new CheckpointError("steering.invalid_content", `steering text must be at most ${MAX_PENDING_STEER_CHARS} characters (got ${text.length})`);
|
|
269
288
|
}
|
|
270
289
|
return {
|
|
271
|
-
text
|
|
272
|
-
trusted
|
|
273
|
-
...(
|
|
274
|
-
inputId:
|
|
275
|
-
...(
|
|
290
|
+
text,
|
|
291
|
+
trusted,
|
|
292
|
+
...(actorTwin !== undefined ? { actor: actorTwin } : {}),
|
|
293
|
+
inputId: inputId ?? uuidv7(),
|
|
294
|
+
...(priority !== undefined ? { priority: priority } : {}),
|
|
276
295
|
};
|
|
277
296
|
}
|
|
278
297
|
export const MAX_ACTOR_FIELD_CHARS = 256;
|
|
279
298
|
export const MAX_STEER_INPUT_ID_CHARS = 128;
|
|
280
299
|
export const ACTOR_ASSERTION_FROZEN_FIELDS = ["id", "hostAsserted", "issuer"];
|
|
281
|
-
function
|
|
300
|
+
function captureActorAssertion(actor) {
|
|
301
|
+
if (actor === null || typeof actor !== "object") {
|
|
302
|
+
throw new CheckpointError("steering.invalid_content", `actor must be an object carrying the attribution fields (got ${actor === null ? "null" : typeof actor})`);
|
|
303
|
+
}
|
|
304
|
+
const supplied = actor;
|
|
282
305
|
const allowed = new Set(ACTOR_ASSERTION_FROZEN_FIELDS);
|
|
283
306
|
for (const key of Object.keys(actor)) {
|
|
284
307
|
if (!allowed.has(key)) {
|
|
285
308
|
throw new CheckpointError("steering.invalid_content", `unknown actor field "${key}" — the persisted actor shape is frozen (${[...allowed].join(", ")})`);
|
|
286
309
|
}
|
|
287
310
|
}
|
|
288
|
-
|
|
311
|
+
const id = supplied.id;
|
|
312
|
+
const hostAsserted = supplied.hostAsserted;
|
|
313
|
+
const issuer = supplied.issuer;
|
|
314
|
+
if (typeof id !== "string" || id === "" || id.length > MAX_ACTOR_FIELD_CHARS) {
|
|
289
315
|
throw new CheckpointError("steering.invalid_content", `actor.id must be a non-empty namespaced string of at most ${MAX_ACTOR_FIELD_CHARS} characters (e.g. "slack:U123")`);
|
|
290
316
|
}
|
|
291
|
-
if (typeof
|
|
317
|
+
if (typeof hostAsserted !== "boolean") {
|
|
292
318
|
throw new CheckpointError("steering.invalid_content", "actor.hostAsserted must be a boolean stated by the host");
|
|
293
319
|
}
|
|
294
|
-
if (
|
|
320
|
+
if (issuer !== undefined && (typeof issuer !== "string" || issuer === "" || issuer.length > MAX_ACTOR_FIELD_CHARS)) {
|
|
295
321
|
throw new CheckpointError("steering.invalid_content", `actor.issuer must be a non-empty string of at most ${MAX_ACTOR_FIELD_CHARS} characters`);
|
|
296
322
|
}
|
|
297
323
|
for (const [field, value] of [
|
|
298
|
-
["id",
|
|
299
|
-
["issuer",
|
|
324
|
+
["id", id],
|
|
325
|
+
["issuer", issuer],
|
|
300
326
|
]) {
|
|
301
327
|
if (value === undefined)
|
|
302
328
|
continue;
|
|
@@ -304,13 +330,7 @@ function validateActorAssertion(actor) {
|
|
|
304
330
|
throw new CheckpointError("steering.invalid_content", `actor.${field} must not contain control characters or a system-reminder break-out tag`);
|
|
305
331
|
}
|
|
306
332
|
}
|
|
307
|
-
}
|
|
308
|
-
function freezeActorAssertion(actor) {
|
|
309
|
-
return {
|
|
310
|
-
id: actor.id,
|
|
311
|
-
hostAsserted: actor.hostAsserted,
|
|
312
|
-
...(actor.issuer !== undefined ? { issuer: actor.issuer } : {}),
|
|
313
|
-
};
|
|
333
|
+
return { id, hostAsserted, ...(issuer !== undefined ? { issuer } : {}) };
|
|
314
334
|
}
|
|
315
335
|
export function readPendingSteerQueue(state) {
|
|
316
336
|
const legacy = state.pendingSteer === undefined
|
|
@@ -49,7 +49,9 @@ export declare class MemoryEngine {
|
|
|
49
49
|
constructor(opts: MemoryEngineOptions);
|
|
50
50
|
private discloseAnnounceFailure;
|
|
51
51
|
materialize(scopes: readonly string[], writeScope: string | null): Promise<MemorySessionHandle>;
|
|
52
|
-
inject(handle: MemorySessionHandle
|
|
52
|
+
inject(handle: MemorySessionHandle, opts?: {
|
|
53
|
+
writeToolMounted?: boolean;
|
|
54
|
+
}): MemoryInjection;
|
|
53
55
|
gateWrite(handle: MemorySessionHandle, canonicalPath: string, content: string): {
|
|
54
56
|
ok: true;
|
|
55
57
|
} | {
|
|
@@ -188,14 +188,15 @@ export class MemoryEngine {
|
|
|
188
188
|
}
|
|
189
189
|
return handle;
|
|
190
190
|
}
|
|
191
|
-
inject(handle) {
|
|
192
|
-
const
|
|
191
|
+
inject(handle, opts) {
|
|
192
|
+
const writeChannel = handle.writeScope !== null && opts?.writeToolMounted !== false;
|
|
193
|
+
const instruction = writeChannel ? buildMemoryInstruction(handle.writableRoot) : "";
|
|
193
194
|
const indexPath = join(handle.writableRoot, MEMORY_INDEX_FILENAME);
|
|
194
195
|
const onDisk = readSafe(indexPath);
|
|
195
196
|
const indexText = onDisk !== undefined && onDisk.trim() !== "" ? onDisk : handle.indexText;
|
|
196
197
|
const truncated = truncateIndex(indexText);
|
|
197
198
|
const index = composeMemoryBlock(truncated, handle.writeScope ?? handle.scopes[0] ?? "memory");
|
|
198
|
-
const indexSeed =
|
|
199
|
+
const indexSeed = writeChannel && onDisk !== undefined && (onDisk.trim() === "" || truncated === onDisk)
|
|
199
200
|
? { path: indexPath, content: onDisk }
|
|
200
201
|
: undefined;
|
|
201
202
|
let announcements;
|
|
@@ -36,7 +36,7 @@ export type SelectiveRecallResult = {
|
|
|
36
36
|
export declare function resolveLinkedIds(headers: MemoryNoteHeader[], selected: MemoryNoteRecord[], max: number): string[];
|
|
37
37
|
export declare function buildManifestText(headers: MemoryNoteHeader[]): string;
|
|
38
38
|
export declare function validateSelectedIds(headers: MemoryNoteHeader[], ids: string[], max: number): string[];
|
|
39
|
-
export declare function composeSelectiveBody(manifestText: string, selected: MemoryNoteRecord[], nowMs: number, linked?: MemoryNoteRecord[], recallable?: boolean): string;
|
|
39
|
+
export declare function composeSelectiveBody(manifestText: string, selected: MemoryNoteRecord[], nowMs: number, linked?: MemoryNoteRecord[], recallable?: boolean, recallToolName?: string): string;
|
|
40
40
|
export declare function encodeSurfacedKey(scope: string, id: string): string;
|
|
41
41
|
export interface ScopedNoteHeader extends MemoryNoteHeader {
|
|
42
42
|
scope: string;
|
|
@@ -102,7 +102,7 @@ export function validateSelectedIds(headers, ids, max) {
|
|
|
102
102
|
}
|
|
103
103
|
return out;
|
|
104
104
|
}
|
|
105
|
-
export function composeSelectiveBody(manifestText, selected, nowMs, linked = [], recallable = true) {
|
|
105
|
+
export function composeSelectiveBody(manifestText, selected, nowMs, linked = [], recallable = true, recallToolName) {
|
|
106
106
|
const renderNote = (r, label) => {
|
|
107
107
|
const ageMs = nowMs - r.mtimeMs;
|
|
108
108
|
const verify = r.timestampMissing || ageMs > ONE_DAY_MS ? " — verify it's still current" : "";
|
|
@@ -124,8 +124,9 @@ export function composeSelectiveBody(manifestText, selected, nowMs, linked = [],
|
|
|
124
124
|
body = `${buf.subarray(0, cut).toString("utf8")}\n<selected notes truncated to ${SELECTED_BODY_MAX_BYTES} bytes>`;
|
|
125
125
|
}
|
|
126
126
|
const selectedSection = selected.length > 0 ? `\n\nRelevant notes:\n${body}` : "\n\n(no notes selected as relevant to this task)";
|
|
127
|
+
const recallCall = recallToolName !== undefined ? `call ${recallToolName}` : `search the memory-recall tool bound to this scope`;
|
|
127
128
|
const manifestSection = recallable
|
|
128
|
-
? `Memory index — to load a note in full,
|
|
129
|
+
? `Memory index — to load a note in full, ${recallCall} with keywords from its description:\n${sanitizeUntrustedText(manifestText, ["user_memory"])}`
|
|
129
130
|
: "";
|
|
130
131
|
return `${manifestSection}${selectedSection}`;
|
|
131
132
|
}
|
|
@@ -135,7 +135,7 @@ export async function prepareMemory(input) {
|
|
|
135
135
|
const writeIsPersonal = p.writePlane === "personal";
|
|
136
136
|
writeEngine = writeIsPersonal ? personalEngine : projectEngine;
|
|
137
137
|
writeHandle = writeIsPersonal ? personalHandle : projectHandle;
|
|
138
|
-
injectFn = () => mergeInjections(projectEngine.inject(projectHandle), personalEngine.inject(personalHandle));
|
|
138
|
+
injectFn = () => mergeInjections(projectEngine.inject(projectHandle, { writeToolMounted: input.writeToolsMounted }), personalEngine.inject(personalHandle, { writeToolMounted: input.writeToolsMounted }));
|
|
139
139
|
harvestBoth = async () => {
|
|
140
140
|
const writeFirst = writeIsPersonal ? [personalEngine, personalHandle] : [projectEngine, projectHandle];
|
|
141
141
|
const readOther = writeIsPersonal ? [projectEngine, projectHandle] : [personalEngine, personalHandle];
|
|
@@ -159,7 +159,7 @@ export async function prepareMemory(input) {
|
|
|
159
159
|
const handle = await personalEngine.materialize(memorySpec.scopes, memorySpec.writeScope);
|
|
160
160
|
writeEngine = personalEngine;
|
|
161
161
|
writeHandle = handle;
|
|
162
|
-
injectFn = () => personalEngine.inject(handle);
|
|
162
|
+
injectFn = () => personalEngine.inject(handle, { writeToolMounted: input.writeToolsMounted });
|
|
163
163
|
harvestBoth = () => personalEngine.harvest(handle);
|
|
164
164
|
}
|
|
165
165
|
else {
|
|
@@ -171,7 +171,7 @@ export async function prepareMemory(input) {
|
|
|
171
171
|
const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope);
|
|
172
172
|
writeEngine = engine;
|
|
173
173
|
writeHandle = handle;
|
|
174
|
-
injectFn = () => engine.inject(handle);
|
|
174
|
+
injectFn = () => engine.inject(handle, { writeToolMounted: input.writeToolsMounted });
|
|
175
175
|
harvestBoth = () => engine.harvest(handle);
|
|
176
176
|
}
|
|
177
177
|
memoryWriteGateRef.current = (w) => writeEngine.gateWrite(writeHandle, w.key, w.content);
|
|
@@ -16,6 +16,7 @@ import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
|
|
|
16
16
|
import { type OutputRef, type BlockedRef, type SkillListingEntry } from "./synthetic-tools.js";
|
|
17
17
|
import type { MemoryEngine } from "../memory-engine/engine.js";
|
|
18
18
|
import { type ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
|
|
19
|
+
import type { ToolDisclosureManifest } from "../trace.js";
|
|
19
20
|
import type { TaskNotificationPayload } from "../task-notification.js";
|
|
20
21
|
import { type CwdRef } from "../../tools/fs/index.js";
|
|
21
22
|
import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
@@ -111,6 +112,7 @@ export interface Prepared {
|
|
|
111
112
|
elements: import("../../prompt-assembly/turn-snapshot.js").CacheIdentityElements;
|
|
112
113
|
};
|
|
113
114
|
lowering?: import("../../prompt-assembly/turn-snapshot.js").LoweringRecord;
|
|
115
|
+
toolDisclosure?: ToolDisclosureManifest;
|
|
114
116
|
};
|
|
115
117
|
epochDeclaredSections: import("../../prompt-assembly/epoch.js").EpochDeclaredSections;
|
|
116
118
|
turnSnapshot?: import("../../prompt-assembly/turn-snapshot.js").TurnPromptSnapshot;
|
|
@@ -125,6 +127,7 @@ export interface Prepared {
|
|
|
125
127
|
activeTools: Set<string>;
|
|
126
128
|
deferredToolNames?: ReadonlySet<string>;
|
|
127
129
|
toolMaterializeStatic: boolean;
|
|
130
|
+
staticFaceFor?: (name: string) => boolean;
|
|
128
131
|
memoryEngineSession?: {
|
|
129
132
|
engine: MemoryEngine;
|
|
130
133
|
handle: MemorySessionHandle;
|
|
@@ -38,7 +38,7 @@ import { pathToUri } from "../lsp-protocol.js";
|
|
|
38
38
|
import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
|
|
39
39
|
import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
|
|
40
40
|
import { compileOutputSchema } from "./strict-output-schema.js";
|
|
41
|
-
import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, } from "./tool-disclosure.js";
|
|
41
|
+
import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, staticSchemaRenderable, } from "./tool-disclosure.js";
|
|
42
42
|
import { composeMemoryBlock } from "../memory.js";
|
|
43
43
|
import { preflightLockedConfig } from "../locked-config.js";
|
|
44
44
|
import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
|
|
@@ -629,6 +629,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
629
629
|
if (extraBodyCollisions.length > 0) {
|
|
630
630
|
deps.onError?.(new Error(`model.extraBody contains reserved key(s) [${extraBodyCollisions.join(", ")}] that core owns and sets itself — they are ignored. Remove them from extraBody.`), { phase: "config", sessionId });
|
|
631
631
|
}
|
|
632
|
+
if (model.extraBody !== undefined && Object.prototype.hasOwnProperty.call(model.extraBody, "max_completion_tokens")) {
|
|
633
|
+
const e = new Error(`model.extraBody supplies "max_completion_tokens" — this spelling is engine-owned (the overflow retry ` +
|
|
634
|
+
`adjusts the per-request output cap, and a constant extraBody value would override that adjustment on ` +
|
|
635
|
+
`every retry). Set the cap via limits.maxOutputTokens (per request) or model.maxTokens instead.`);
|
|
636
|
+
e.code = "config.extra_body_output_cap";
|
|
637
|
+
throw e;
|
|
638
|
+
}
|
|
632
639
|
const nestedStats = { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0, anyUnpriced: false };
|
|
633
640
|
if (resume) {
|
|
634
641
|
nestedStats.tokens = resume.seed.nestedStats.tokens;
|
|
@@ -1592,6 +1599,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1592
1599
|
return memoryWriteGateRef.current?.(w);
|
|
1593
1600
|
},
|
|
1594
1601
|
mountBackgroundTaskTools: false,
|
|
1602
|
+
monitorToolActive: backgroundTaskToolsActive && !(toolFaceSnapshot.exclude?.includes("Monitor") ?? false),
|
|
1595
1603
|
});
|
|
1596
1604
|
{
|
|
1597
1605
|
const bandNames = new Set(band.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
@@ -1844,6 +1852,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1844
1852
|
sessionId,
|
|
1845
1853
|
taskRootPath,
|
|
1846
1854
|
memoryWriteGateRef,
|
|
1855
|
+
writeToolsMounted: handsEnabled && spec.handsReadOnly !== true && !(toolFaceSnapshot.exclude?.includes("Write") ?? false),
|
|
1847
1856
|
admissionCtx: {
|
|
1848
1857
|
orgMemoryDenied: complianceDenies.has("org_memory_mount"),
|
|
1849
1858
|
complianceDegraded,
|
|
@@ -2282,6 +2291,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2282
2291
|
.map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
|
|
2283
2292
|
let toolsDeltaRef;
|
|
2284
2293
|
let toolMaterializeStatic = false;
|
|
2294
|
+
const staticFaceForRef = {};
|
|
2285
2295
|
if (deferred.size > 0 || failedMcpServers.length > 0) {
|
|
2286
2296
|
toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
|
|
2287
2297
|
}
|
|
@@ -2293,6 +2303,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2293
2303
|
throw e;
|
|
2294
2304
|
}
|
|
2295
2305
|
const registry = buildDeferredRegistry(deferred, tools);
|
|
2306
|
+
const envStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
|
|
2307
|
+
if (envStrategy !== undefined && envStrategy !== "swap" && envStrategy !== "static") {
|
|
2308
|
+
const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(envStrategy)}).`);
|
|
2309
|
+
e.code = "config.tool_materialize_invalid";
|
|
2310
|
+
throw e;
|
|
2311
|
+
}
|
|
2312
|
+
const requestedStrategy = spec.toolMaterializeStrategy ?? envStrategy ?? "swap";
|
|
2313
|
+
const laneDegrade = requestedStrategy === "static" && spec.deferSelfResolve === false;
|
|
2314
|
+
const materializeStatic = requestedStrategy === "static" && !laneDegrade;
|
|
2315
|
+
toolMaterializeStatic = materializeStatic;
|
|
2316
|
+
promptManifest.toolDisclosure = {
|
|
2317
|
+
deferredTools: deferred.size,
|
|
2318
|
+
strategy: materializeStatic ? "static" : "swap",
|
|
2319
|
+
source: laneDegrade ? "degraded_no_direct_lane" : spec.toolMaterializeStrategy !== undefined ? "spec" : envStrategy !== undefined ? "env" : "default",
|
|
2320
|
+
};
|
|
2321
|
+
const everExempt = new Set();
|
|
2322
|
+
const staticFaceFor = (name) => {
|
|
2323
|
+
if (!materializeStatic)
|
|
2324
|
+
return false;
|
|
2325
|
+
if (everExempt.has(name))
|
|
2326
|
+
return false;
|
|
2327
|
+
if (staticSchemaRenderable(tools.find((t) => t.name === name)?.parameters))
|
|
2328
|
+
return true;
|
|
2329
|
+
everExempt.add(name);
|
|
2330
|
+
return false;
|
|
2331
|
+
};
|
|
2332
|
+
staticFaceForRef.current = staticFaceFor;
|
|
2296
2333
|
let activationChain = Promise.resolve();
|
|
2297
2334
|
const serializeActivation = (section) => {
|
|
2298
2335
|
const p = activationChain.then(section);
|
|
@@ -2314,6 +2351,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2314
2351
|
};
|
|
2315
2352
|
},
|
|
2316
2353
|
...(executionMode !== undefined ? { executionMode } : {}),
|
|
2354
|
+
staticFace: () => staticFaceFor(name),
|
|
2317
2355
|
activate: async () => serializeActivation(async () => {
|
|
2318
2356
|
if (activeTools.has(name))
|
|
2319
2357
|
return undefined;
|
|
@@ -2358,16 +2396,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2358
2396
|
};
|
|
2359
2397
|
offloadReachableToolsRef.current = callableToolNames;
|
|
2360
2398
|
let toolSearch;
|
|
2361
|
-
const envStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
|
|
2362
|
-
if (envStrategy !== undefined && envStrategy !== "swap" && envStrategy !== "static") {
|
|
2363
|
-
const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(envStrategy)}).`);
|
|
2364
|
-
e.code = "config.tool_materialize_invalid";
|
|
2365
|
-
throw e;
|
|
2366
|
-
}
|
|
2367
|
-
const materializeStatic = (spec.toolMaterializeStrategy ?? envStrategy ?? "static") === "static" && spec.deferSelfResolve !== false;
|
|
2368
|
-
toolMaterializeStatic = materializeStatic;
|
|
2369
2399
|
const buildToolList = (active) => {
|
|
2370
|
-
const list = tools.map((t) => (deferred.has(t.name) && (
|
|
2400
|
+
const list = tools.map((t) => (deferred.has(t.name) && (staticFaceFor(t.name) || !active.has(t.name)) ? placeholders.get(t.name) : t));
|
|
2371
2401
|
list.push(toolSearch);
|
|
2372
2402
|
return list;
|
|
2373
2403
|
};
|
|
@@ -2408,8 +2438,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2408
2438
|
listingRide: (newly) => listingRideRef.current?.(newly),
|
|
2409
2439
|
mountedNames: callableToolNames,
|
|
2410
2440
|
directCallEnabled: spec.deferSelfResolve !== false,
|
|
2441
|
+
isMounted: (name) => tools.some((t) => t.name === name),
|
|
2411
2442
|
...(materializeStatic
|
|
2412
|
-
? { staticSchemaFor: (name) => tools.find((t) => t.name === name)?.parameters }
|
|
2443
|
+
? { staticSchemaFor: (name) => (staticFaceFor(name) ? tools.find((t) => t.name === name)?.parameters : undefined) }
|
|
2413
2444
|
: {}),
|
|
2414
2445
|
serializeActivation,
|
|
2415
2446
|
});
|
|
@@ -4108,7 +4139,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4108
4139
|
: undefined;
|
|
4109
4140
|
overheadState.promptChars = systemPrompt.length;
|
|
4110
4141
|
const preparedHolder = {};
|
|
4111
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4142
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4112
4143
|
const prepared = buildPrepared();
|
|
4113
4144
|
preparedHolder.current = prepared;
|
|
4114
4145
|
return prepared;
|