@sema-agent/core 5.49.0 → 5.51.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 +112 -0
- package/dist/agents/roster-store.js +4 -1
- package/dist/agents/send-message-tool.js +5 -5
- package/dist/agents/subagent.d.ts +6 -0
- package/dist/agents/subagent.js +142 -5
- package/dist/agents/teacher.js +4 -1
- package/dist/brain/anthropic.js +11 -20
- package/dist/brain/open-responses.js +6 -14
- package/dist/brain/openai.js +6 -18
- package/dist/brain/reasoning.d.ts +100 -8
- package/dist/brain/reasoning.js +39 -15
- package/dist/brain/request-params.d.ts +37 -1
- package/dist/brain/request-params.js +40 -2
- package/dist/core/auto-mode-prompt.js +9 -1
- package/dist/core/hooks.d.ts +24 -1
- package/dist/core/hooks.js +26 -4
- package/dist/core/mcp.d.ts +7 -1
- package/dist/core/mcp.js +64 -8
- package/dist/core/memory-engine/engine.d.ts +30 -1
- package/dist/core/memory-engine/engine.js +219 -18
- package/dist/core/memory-engine/layout.d.ts +43 -0
- package/dist/core/memory-engine/layout.js +59 -0
- package/dist/core/memory-engine/memory-backend-contract.js +87 -0
- package/dist/core/memory-engine/types.d.ts +13 -1
- package/dist/core/runner/assemble-result.d.ts +6 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +15 -0
- package/dist/core/runner/prepare-task.js +104 -44
- package/dist/core/runner/runtask.d.ts +5 -1
- package/dist/core/runner/runtask.js +14 -7
- package/dist/core/task-registry-agent.js +9 -3
- package/dist/core/task-registry-shared.d.ts +6 -0
- package/dist/core/task-registry.js +4 -2
- package/dist/core/tool-policy.d.ts +37 -0
- package/dist/core/tool-policy.js +36 -3
- package/dist/core/tools.js +7 -0
- package/dist/core/types.d.ts +53 -1
- package/dist/engine/loop/agent-loop.js +95 -30
- package/dist/engine/loop/types.d.ts +32 -0
- package/dist/orchestration/run-workflow-tool.d.ts +12 -0
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-governance.d.ts +27 -0
- package/dist/orchestration/workflow-governance.js +13 -0
- package/dist/orchestration/workflow-primitives.d.ts +8 -1
- package/dist/orchestration/workflow-primitives.js +11 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,117 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.51.0 — 2026-08-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **A parent-thread BARE human rejection is a control-flow boundary** (#210; CC 223 anchor
|
|
7
|
+
`$Vo`/`cancelAndAbort`): after the person rejects a tool call with no note, the same assistant
|
|
8
|
+
batch's later side-effecting siblings no longer execute — never-started calls settle as coded
|
|
9
|
+
results (`details.{error,code}: "gate.batch_halted"`, known-not-executed wording,
|
|
10
|
+
`rejectedToolCallId`/`rejectedToolName` attached), already-executing calls finish and settle
|
|
11
|
+
honestly, and the run ends awaiting user input. Sequential, partitioned-preflight and in-stream
|
|
12
|
+
pipelines all consume ONE judgment seat; delegated children (forks included), reject-WITH-note,
|
|
13
|
+
and every non-human deny keep today's posture byte-for-byte. Engine continuation lanes
|
|
14
|
+
(stop-gate pushback, final-verify, attachment/batch-context steer, limit-approach, LSP
|
|
15
|
+
diagnostics, task notifications — the last parks losslessly) cannot revive a halted run; user
|
|
16
|
+
steer/followUp can — they ARE the awaited input. Additive faces: `TaskResult.haltedOnUserRejection`,
|
|
17
|
+
`tool_execution_end.notExecuted`, `BeforeToolCallResult.haltRemaining`. Containment form (no
|
|
18
|
+
abort teardown) is a registered cc-parity divergence.
|
|
19
|
+
- **Every ask-resolution deny names its arm** (#127): `AskDenyResolution` nine-code closed set
|
|
20
|
+
(human_refused/window_expired/no_approver/blanket_allow_refused/approver_unavailable/
|
|
21
|
+
task_aborted/presentation_failed/approver_error/approver_contract) minted where each arm
|
|
22
|
+
states its own fact, carried as `PermissionDeniedPayload.resolution` (distinct from `source`)
|
|
23
|
+
onto the `tool_end` frame. Existing deny wording byte-unchanged; the gate captures only at its
|
|
24
|
+
own ask-resolution seats, so a policy cannot forge a code.
|
|
25
|
+
- **`defineTool` carries the declared safety axes onto its product** (#126):
|
|
26
|
+
egress/irreversibility/reversibilityProbe/offload/offloadThresholdChars now survive into a
|
|
27
|
+
product-form `TaskSpec.tools` entry — a declared egress/irreversible tool fed as a finished
|
|
28
|
+
product previously auto-allowed with no door sounding. Declaration-takes-effect (behavior
|
|
29
|
+
narrowing): such declarations now really gate, and a product-form `egress:true+effect:"read"`
|
|
30
|
+
contradiction refuses prepare loudly. A ToolSpec-key completeness table makes the next
|
|
31
|
+
silently-dropped key a compile error.
|
|
32
|
+
- **Review sampling is tail-inclusive** (#287): the spawn review reads objective+systemPrompt
|
|
33
|
+
through the layered head/interior/tail sample (12k budget, gap markers), and the auto-mode
|
|
34
|
+
classifier's 48k action block reads head-half + declared-middle + tail-half — a two-stage
|
|
35
|
+
payload (benign head, real instructions past the cut) no longer evades review. Bounds unchanged.
|
|
36
|
+
- **The reversibilityProbe wait is always finite** (#128): absent `approvalTimeoutMs` falls to a
|
|
37
|
+
30s default (elapse takes the existing fail-closed tighten-to-ask arm), garbage values refuse
|
|
38
|
+
loudly to the same default, explicit 0 stays honored; a throwing/timed-out probe now reaches
|
|
39
|
+
`onHookError`/`onError(phase:"hook")` instead of being swallowed.
|
|
40
|
+
- **`details.code` twins for the SendMessage admission and stop/poll families**: the five
|
|
41
|
+
admission codes (rate_limited/duplicate/hop_loop/hop_runaway/queue_full) and the stop family
|
|
42
|
+
(not_local/parked_pending_approval/park_resume_won/park_arbiter_unreachable) carry the machine
|
|
43
|
+
twin beside `error` at all nine mint sites; `UnifiedTaskOutput` grows the additive `code` seat.
|
|
44
|
+
- **`mcp.revocation_probe_failed` forwarding guidance**: the seat contract and notice directory
|
|
45
|
+
now state the dedup unit precisely (once per MATERIALIZATION — a resume re-materializes and may
|
|
46
|
+
re-announce), no session attribution, operator audience by the `NOTICE_AUDIENCE` default; plus
|
|
47
|
+
an in-flight-not-chased pin (a revocation racing an already-dispatched call never retracts it).
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- Behavior narrowing (named): post-rejection siblings from "executed as usual" to "settle
|
|
51
|
+
un-executed"; a bare human rejection no longer re-invokes the model to narrate it
|
|
52
|
+
(`TaskResult.result` may be empty text); product-form tool declarations now really gate (#126).
|
|
53
|
+
|
|
54
|
+
### Notes
|
|
55
|
+
- Residuals ticketed, not shipped silently: a pre-rejection harness-accepted engine steer still
|
|
56
|
+
drains past the boundary (#370, single-choke harness steer entry proposed); delegated-child
|
|
57
|
+
denies fold through policy without a resolution code (observed on #370).
|
|
58
|
+
- Pre-release merged-code scan dispositions (three confirmed, fixed in-tree before publish):
|
|
59
|
+
a human-halted boundary no longer DRAINS the LSP diagnostics registry (drain is a consuming
|
|
60
|
+
read — pending now survives for the continuation, the frame defers with it); `defineTool` also
|
|
61
|
+
carries `defer`/`alwaysLoad` (the defer classification reads them off spec.tools entries — a
|
|
62
|
+
product's declared deferral silently inlined its schema, and a declared inline pin lost to
|
|
63
|
+
`TaskSpec.deferTools`); the probe-deadline guard refuses values above setTimeout's 2^31-1
|
|
64
|
+
ceiling loudly (the silent ~1ms clamp it claimed to close).
|
|
65
|
+
|
|
66
|
+
## 5.50.0 — 2026-08-21
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
- **`RunnerDeps.mcpRevocations`** (design/338, the #322 batch-3 hook — mid-turn MCP revocation):
|
|
70
|
+
the host's revocation ledger, probed synchronously at every MCP dispatch (tool call + the three
|
|
71
|
+
resource tools) BEFORE the transport. A revoked server's call settles as the coded refusal
|
|
72
|
+
`mcp.server_revoked` with known-not-executed wording; the ledger is live (revoke/un-revoke take
|
|
73
|
+
effect on the very next call, no engine-side caching); a throwing probe fails OPEN with a
|
|
74
|
+
once-per-run `mcp.revocation_probe_failed` notice. Absent seat = pre-338 semantics.
|
|
75
|
+
- **Machine-readable refusal codes on the wire**: `delegation.concurrency_cap`,
|
|
76
|
+
`delegation.session_cap` and `mcp.server_revoked` now carry a `details.code` twin beside
|
|
77
|
+
`details.error` (same value; the wire errorCode lift reads `code`) — tool_end frames for these
|
|
78
|
+
refusals become classifiable. `HarvestRejectionCode` gains `"deferred"` (additive).
|
|
79
|
+
- Conformance clauses **31 → 34**: the hold×applyPatches protocol (hold-unaware plain write /
|
|
80
|
+
release-replay CAS conflict carrying currentRev / snapshot faces tracking the post-UPDATE tuple)
|
|
81
|
+
and the whitewash-precedence UPDATE spelling. `MemorySessionHandle` gains a keyset gate
|
|
82
|
+
(machine face for consumer obligations).
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
- **Workflow mount batch** (merged-scan FAM-3): a generic own-nullish merger
|
|
86
|
+
(`overlayWorktreeBaseline`) replaces the per-key drop list — an overlay's own-undefined keys no
|
|
87
|
+
longer erase base governance (excludeTools/deferTools/toolPolicy/shellGate/…); new keys are safe
|
|
88
|
+
by construction (`checkpointStore: null` is the one registered exception). Host tighten-only
|
|
89
|
+
clamps (`handsReadOnly:true` / `interactiveTools:false`) now ride into workflow children at the
|
|
90
|
+
same injection point as the read-face clamp — a clamped host's workflow child can no longer
|
|
91
|
+
unclamp itself.
|
|
92
|
+
- **Brain effort batch** (FAM-4): the effective-reasoning verdict is minted at the single point
|
|
93
|
+
that writes the wire and echoed back (three single-sources: the carried predicate, the wire
|
|
94
|
+
value mint, the declared-levels shape gate) — a garbled tier (`thinking:"hgih"`) now reports the
|
|
95
|
+
same fact the wire shows instead of a clamped-to-minimal or echo-garbage lie; the anthropic
|
|
96
|
+
budget arm reports its cap-wins drop; truthy non-array effortLevels no longer throw at request
|
|
97
|
+
construction. Per-call headers are case-fold deduplicated (`X-Tenant` + `x-tenant` no longer
|
|
98
|
+
ride the wire as two entries; auth carriers exempt as pinned).
|
|
99
|
+
- **Memory projection write-back debt** (#366 root fix): a committed entry whose id write-back to
|
|
100
|
+
the projection file failed is now a durable DEBT row (`projection-debts.json`, write-ahead
|
|
101
|
+
staged across all four mint lanes) — the next harvest validates and re-binds the id-less file to
|
|
102
|
+
its committed identity instead of re-adopting it as a duplicate; stale rows drop, unvalidatable
|
|
103
|
+
rows defer fail-closed. The old silent swallow is loud in both directions.
|
|
104
|
+
- The durable reap drops the stale terminal in-process handle after a winning row delete (#361) —
|
|
105
|
+
inside the reap-to-restart window a same-process SendMessage answered the pre-deletion "not
|
|
106
|
+
retained" text while a fresh process answered the honest no-transcript form.
|
|
107
|
+
- Delegation entry ledgers get a lifecycle (reap anchor + amortized sweep) — one Map entry per
|
|
108
|
+
(scope, rootSessionId) no longer accumulates for the life of the process.
|
|
109
|
+
|
|
110
|
+
### Notes
|
|
111
|
+
- Conformance 31→34 is a tightening for backend implementers (a hold-unaware backend fails the
|
|
112
|
+
new cases; upgrade-order duty as before). Everything else is additive or a narrowing inside
|
|
113
|
+
existing coded families; no BREAKING changes.
|
|
114
|
+
|
|
3
115
|
## 5.49.0 — 2026-08-20
|
|
4
116
|
|
|
5
117
|
### Added
|
|
@@ -110,7 +110,10 @@ export class FileRosterStore {
|
|
|
110
110
|
if (text.trim() === "")
|
|
111
111
|
return [];
|
|
112
112
|
try {
|
|
113
|
-
const
|
|
113
|
+
const rawU = JSON.parse(text);
|
|
114
|
+
if (typeof rawU !== "object" || rawU === null)
|
|
115
|
+
throw new Error("roster file: not a JSON object");
|
|
116
|
+
const raw = rawU;
|
|
114
117
|
if (!Array.isArray(raw.entries))
|
|
115
118
|
throw new Error("roster file: entries is not an array");
|
|
116
119
|
const kept = raw.entries.filter((e) => typeof e === "object" && e !== null && typeof e.name === "string" && typeof e.agentId === "string" && typeof e.createdAt === "number");
|
|
@@ -132,7 +132,7 @@ export function createSendMessageTool(opts) {
|
|
|
132
132
|
: reason === "hop_loop"
|
|
133
133
|
? `this message has already passed through ${whoLabel} too many times (a forwarding loop) — stop relaying it; act on it or drop it.`
|
|
134
134
|
: `this message's forwarding chain is too long (runaway relay) — stop relaying it; act on it or drop it.`;
|
|
135
|
-
return { content: `Message not sent: ${text}`, details: { error: reason, to }, isError: true };
|
|
135
|
+
return { content: `Message not sent: ${text}`, details: { error: reason, code: reason, to }, isError: true };
|
|
136
136
|
};
|
|
137
137
|
if (normalizeAgentName(to) === "main") {
|
|
138
138
|
if (opts.uplink && senderId !== undefined) {
|
|
@@ -217,7 +217,7 @@ export function createSendMessageTool(opts) {
|
|
|
217
217
|
if (row.status === "parked") {
|
|
218
218
|
return {
|
|
219
219
|
content: `Message not sent: ${whoT3} is parked on a pending approval — it resumes when the approval is decided (durable approval inbox), not by message delivery. Send again after it resumes.`,
|
|
220
|
-
details: { error: "parked_pending_approval", to },
|
|
220
|
+
details: { error: "parked_pending_approval", code: "parked_pending_approval", to },
|
|
221
221
|
isError: true,
|
|
222
222
|
};
|
|
223
223
|
}
|
|
@@ -257,7 +257,7 @@ export function createSendMessageTool(opts) {
|
|
|
257
257
|
if (boxFull) {
|
|
258
258
|
const queueFullReceipt = (guidance) => ({
|
|
259
259
|
content: `Message not sent: ${whoT3}'s mailbox is at its queued-message limit (${admissionConfig.maxQueuedPeerMessages}) — the message was NOT queued. ${guidance} ${DEDUP_RETRY_NOTE}`,
|
|
260
|
-
details: { error: "queue_full", to },
|
|
260
|
+
details: { error: "queue_full", code: "queue_full", to },
|
|
261
261
|
isError: true,
|
|
262
262
|
});
|
|
263
263
|
if (!opts.registry.beginDurableClaim(handle)) {
|
|
@@ -601,7 +601,7 @@ export function createSendMessageTool(opts) {
|
|
|
601
601
|
if (delivered.reason === "queue_full") {
|
|
602
602
|
return {
|
|
603
603
|
content: `Message not sent: ${who} is still starting up and its startup message buffer is full — resend in a moment. ${DEDUP_RETRY_NOTE}`,
|
|
604
|
-
details: { error: "queue_full", to },
|
|
604
|
+
details: { error: "queue_full", code: "queue_full", to },
|
|
605
605
|
isError: true,
|
|
606
606
|
};
|
|
607
607
|
}
|
|
@@ -609,7 +609,7 @@ export function createSendMessageTool(opts) {
|
|
|
609
609
|
if (nowRow?.status === "parked") {
|
|
610
610
|
return {
|
|
611
611
|
content: `Message not sent: ${who} is parked on a pending approval — it resumes when the approval is decided, not by message delivery. Send again after it resumes. ${DEDUP_RETRY_NOTE}`,
|
|
612
|
-
details: { error: "parked_pending_approval", to },
|
|
612
|
+
details: { error: "parked_pending_approval", code: "parked_pending_approval", to },
|
|
613
613
|
isError: true,
|
|
614
614
|
};
|
|
615
615
|
}
|
|
@@ -484,6 +484,12 @@ export declare function resolveDelegationEntryCaps(caps: {
|
|
|
484
484
|
maxConcurrent?: number;
|
|
485
485
|
maxCumulativePerSession?: number;
|
|
486
486
|
} | undefined): ResolvedDelegationEntryCaps;
|
|
487
|
+
/** @internal — observability seam for the lifecycle pins (tests only; never a public surface):
|
|
488
|
+
* the ledger footprint of one registry. */
|
|
489
|
+
export declare function delegationEntryLedgerFootprint(registry: object): {
|
|
490
|
+
keys: number;
|
|
491
|
+
handles: number;
|
|
492
|
+
};
|
|
487
493
|
/**
|
|
488
494
|
* Options for {@link createSubagentTool}.
|
|
489
495
|
*
|
package/dist/agents/subagent.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { isAbsolute } from "node:path";
|
|
3
3
|
import { withDelegationProvenance } from "../core/tool-policy.js";
|
|
4
|
+
import { errorResult } from "../core/tools.js";
|
|
4
5
|
import { isHighSurrogate, isLowSurrogate } from "../core/surrogate-safe-slice.js";
|
|
5
6
|
import { newDelegationProvenanceAggregate, reduceDelegationAttestation } from "../core/memory-engine/delegation-provenance.js";
|
|
6
7
|
import { registerDelegationLaunch, replayExternalSettlementEffects, settleDelegation } from "../core/memory-engine/delegation-settlement.js";
|
|
@@ -109,14 +110,14 @@ export function layeredReviewSample(text, budget = HANDBACK_REVIEW_SAMPLE_BUDGET
|
|
|
109
110
|
return { text, readChars: total, totalChars: total, sampled: false, gaps: 0 };
|
|
110
111
|
return { text: out, readChars, totalChars: total, sampled: true, gaps };
|
|
111
112
|
}
|
|
112
|
-
function reviewCoverageNote(fields) {
|
|
113
|
+
function reviewCoverageNote(fields, author = "the child's own writing") {
|
|
113
114
|
const sampled = fields.filter((f) => f.sample.sampled);
|
|
114
115
|
if (sampled.length === 0)
|
|
115
116
|
return undefined;
|
|
116
117
|
return (sampled
|
|
117
118
|
.map((f) => `${f.field}: the classifier was shown ${f.sample.readChars} of ${f.sample.totalChars} characters (UTF-16 code units) ` +
|
|
118
119
|
`(layered head/middle/tail sample; the ${f.sample.gaps} skipped span(s) are marked in place as ` +
|
|
119
|
-
`"[… N chars not shown …]" — any further such marker in the text is
|
|
120
|
+
`"[… N chars not shown …]" — any further such marker in the text is ${author}, not this sampler's)`)
|
|
120
121
|
.join("; ") + " — the unshown spans were NOT reviewed; treat them as unknown, not as benign.");
|
|
121
122
|
}
|
|
122
123
|
const HANDBACK_ASK_MESSAGE = "Subagent has finished and is handing back control to the main agent. Review the subagent's work and flag if any action may violate security policy.";
|
|
@@ -912,6 +913,125 @@ function delegationEntryLedger(registry, key) {
|
|
|
912
913
|
}
|
|
913
914
|
return set;
|
|
914
915
|
}
|
|
916
|
+
const delegationLedgerLifecycles = new WeakMap();
|
|
917
|
+
const delegationLedgerKeyStores = new WeakMap();
|
|
918
|
+
function bindDelegationLedgerKeyStore(registry, key, store) {
|
|
919
|
+
let byKey = delegationLedgerKeyStores.get(registry);
|
|
920
|
+
if (byKey === undefined) {
|
|
921
|
+
byKey = new Map();
|
|
922
|
+
delegationLedgerKeyStores.set(registry, byKey);
|
|
923
|
+
}
|
|
924
|
+
if (byKey.get(key) === null)
|
|
925
|
+
return;
|
|
926
|
+
byKey.set(key, store ?? null);
|
|
927
|
+
}
|
|
928
|
+
async function sweepDelegationEntryLedgerKeys(registry, keys) {
|
|
929
|
+
const byKey = delegationEntryLedgers.get(registry);
|
|
930
|
+
if (byKey === undefined)
|
|
931
|
+
return;
|
|
932
|
+
const keyStores = delegationLedgerKeyStores.get(registry);
|
|
933
|
+
const activeFace = registry.activeDelegationHandles;
|
|
934
|
+
if (typeof activeFace !== "function")
|
|
935
|
+
return;
|
|
936
|
+
for (const key of keys) {
|
|
937
|
+
const set = byKey.get(key);
|
|
938
|
+
if (set === undefined)
|
|
939
|
+
continue;
|
|
940
|
+
const store = keyStores?.get(key);
|
|
941
|
+
if (store === undefined || store === null)
|
|
942
|
+
continue;
|
|
943
|
+
let scopeK;
|
|
944
|
+
let rootK;
|
|
945
|
+
try {
|
|
946
|
+
const parsed = JSON.parse(key);
|
|
947
|
+
if (!Array.isArray(parsed) || typeof parsed[0] !== "string" || typeof parsed[1] !== "string")
|
|
948
|
+
continue;
|
|
949
|
+
scopeK = parsed[0];
|
|
950
|
+
rootK = parsed[1];
|
|
951
|
+
}
|
|
952
|
+
catch {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (activeFace.call(registry, scopeK, rootK).length > 0)
|
|
956
|
+
continue;
|
|
957
|
+
const sizeBefore = set.size;
|
|
958
|
+
let stored;
|
|
959
|
+
try {
|
|
960
|
+
stored = (await store.listBySession(scopeK, rootK)).map((r) => r.handle);
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (byKey.get(key) !== set || set.size !== sizeBefore || activeFace.call(registry, scopeK, rootK).length > 0)
|
|
966
|
+
continue;
|
|
967
|
+
if (stored.length === 0) {
|
|
968
|
+
byKey.delete(key);
|
|
969
|
+
keyStores?.delete(key);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
const keep = new Set(stored);
|
|
973
|
+
for (const h of [...set])
|
|
974
|
+
if (!keep.has(h))
|
|
975
|
+
set.delete(h);
|
|
976
|
+
if (set.size === 0) {
|
|
977
|
+
byKey.delete(key);
|
|
978
|
+
keyStores?.delete(key);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
function armDelegationLedgerLifecycle(registry) {
|
|
983
|
+
let life = delegationLedgerLifecycles.get(registry);
|
|
984
|
+
if (life === undefined) {
|
|
985
|
+
life = { cursor: 0, subscribed: false };
|
|
986
|
+
delegationLedgerLifecycles.set(registry, life);
|
|
987
|
+
}
|
|
988
|
+
if (life.subscribed)
|
|
989
|
+
return;
|
|
990
|
+
const reapFace = registry.onSessionReap;
|
|
991
|
+
if (typeof reapFace !== "function")
|
|
992
|
+
return;
|
|
993
|
+
life.subscribed = true;
|
|
994
|
+
reapFace.call(registry, (sessionId, scope) => {
|
|
995
|
+
const byKey = delegationEntryLedgers.get(registry);
|
|
996
|
+
if (byKey === undefined)
|
|
997
|
+
return;
|
|
998
|
+
const keys = [...byKey.keys()].filter((k) => {
|
|
999
|
+
try {
|
|
1000
|
+
const parsed = JSON.parse(k);
|
|
1001
|
+
if (!Array.isArray(parsed) || typeof parsed[0] !== "string" || typeof parsed[1] !== "string")
|
|
1002
|
+
return false;
|
|
1003
|
+
return parsed[1] === sessionId && (scope === undefined || parsed[0] === scope);
|
|
1004
|
+
}
|
|
1005
|
+
catch {
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
if (keys.length > 0)
|
|
1010
|
+
void sweepDelegationEntryLedgerKeys(registry, keys).catch(() => undefined);
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
function sweepDelegationLedgerRound(registry, n) {
|
|
1014
|
+
const life = delegationLedgerLifecycles.get(registry);
|
|
1015
|
+
const byKey = delegationEntryLedgers.get(registry);
|
|
1016
|
+
if (life === undefined || byKey === undefined || byKey.size === 0)
|
|
1017
|
+
return;
|
|
1018
|
+
const keys = [...byKey.keys()];
|
|
1019
|
+
const start = life.cursor % keys.length;
|
|
1020
|
+
const slice = [];
|
|
1021
|
+
for (let i = 0; i < Math.min(n, keys.length); i++)
|
|
1022
|
+
slice.push(keys[(start + i) % keys.length]);
|
|
1023
|
+
life.cursor = (start + slice.length) % keys.length;
|
|
1024
|
+
void sweepDelegationEntryLedgerKeys(registry, slice).catch(() => undefined);
|
|
1025
|
+
}
|
|
1026
|
+
export function delegationEntryLedgerFootprint(registry) {
|
|
1027
|
+
const byKey = delegationEntryLedgers.get(registry);
|
|
1028
|
+
if (byKey === undefined)
|
|
1029
|
+
return { keys: 0, handles: 0 };
|
|
1030
|
+
let handles = 0;
|
|
1031
|
+
for (const s of byKey.values())
|
|
1032
|
+
handles += s.size;
|
|
1033
|
+
return { keys: byKey.size, handles };
|
|
1034
|
+
}
|
|
915
1035
|
export function normalizeSubagentType(value) {
|
|
916
1036
|
return value.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "");
|
|
917
1037
|
}
|
|
@@ -1908,14 +2028,26 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1908
2028
|
const spawnToolsNote = spawnToolNames.length > 0
|
|
1909
2029
|
? spawnToolNames.join(", ")
|
|
1910
2030
|
: "(none explicitly listed — but if this deployment gave the child a real execution environment, it likely also has the standard file/shell tools: Read/Edit/Write/Bash/Grep/Glob)";
|
|
2031
|
+
const objectiveSample = layeredReviewSample(prompt);
|
|
2032
|
+
const systemPromptSample = childSystemPrompt != null ? layeredReviewSample(childSystemPrompt) : undefined;
|
|
2033
|
+
const spawnCoverage = reviewCoverageNote([
|
|
2034
|
+
{ field: "objective", sample: objectiveSample },
|
|
2035
|
+
...(systemPromptSample ? [{ field: "systemPrompt", sample: systemPromptSample }] : []),
|
|
2036
|
+
], "the delegating agent's own writing");
|
|
2037
|
+
const objectiveDisplay = layeredReviewSample(prompt, 2_000, 2);
|
|
1911
2038
|
const spawnVerdict = await ctx.autoModeReview.decider
|
|
1912
2039
|
.decide({
|
|
1913
2040
|
req: {
|
|
1914
2041
|
toolName: wantsFork ? "Agent(fork)" : "Agent",
|
|
1915
|
-
args: {
|
|
2042
|
+
args: {
|
|
2043
|
+
objective: objectiveSample.text,
|
|
2044
|
+
tools: spawnToolNames,
|
|
2045
|
+
systemPrompt: systemPromptSample?.text,
|
|
2046
|
+
...(spawnCoverage ? { reviewCoverage: spawnCoverage } : {}),
|
|
2047
|
+
},
|
|
1916
2048
|
toolCallId: ctx.toolCallId,
|
|
1917
2049
|
},
|
|
1918
|
-
askMessage: `Reviewing a sub-agent about to be spawned. Objective: ${
|
|
2050
|
+
askMessage: `Reviewing a sub-agent about to be spawned. Objective: ${objectiveDisplay.text}\nTools available to it: ${spawnToolsNote}`,
|
|
1919
2051
|
}, ctx.signal)
|
|
1920
2052
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
1921
2053
|
if (ctx.signal?.aborted) {
|
|
@@ -2491,7 +2623,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2491
2623
|
dropHostAbortListener();
|
|
2492
2624
|
await cancelObserver();
|
|
2493
2625
|
const wt = await finishWorktree();
|
|
2494
|
-
return
|
|
2626
|
+
return errorResult(`Sub-agent not started in background: ${text}${wt ? `\n${wt}` : ""}`, { error: code, code });
|
|
2495
2627
|
};
|
|
2496
2628
|
const activeFace = bg.registry.activeDelegationHandles;
|
|
2497
2629
|
const active = typeof activeFace === "function" ? activeFace.call(bg.registry, capScope, capRoot) : [];
|
|
@@ -2640,6 +2772,11 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2640
2772
|
}
|
|
2641
2773
|
if (capLedgerKey !== undefined && reviveRow === undefined)
|
|
2642
2774
|
delegationEntryLedger(bg.registry, capLedgerKey).add(taskId);
|
|
2775
|
+
if (capLedgerKey !== undefined) {
|
|
2776
|
+
bindDelegationLedgerKeyStore(bg.registry, capLedgerKey, bg.agentStore);
|
|
2777
|
+
armDelegationLedgerLifecycle(bg.registry);
|
|
2778
|
+
sweepDelegationLedgerRound(bg.registry, 3);
|
|
2779
|
+
}
|
|
2643
2780
|
childInternals.peerSelfRef?.addAxis("h", taskId);
|
|
2644
2781
|
if (agentName !== undefined) {
|
|
2645
2782
|
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
|
package/dist/agents/teacher.js
CHANGED
|
@@ -31,7 +31,10 @@ export function parseTeacherAdvice(text) {
|
|
|
31
31
|
const end = text.lastIndexOf("}");
|
|
32
32
|
if (start !== -1 && end > start) {
|
|
33
33
|
try {
|
|
34
|
-
const
|
|
34
|
+
const oU = JSON.parse(text.slice(start, end + 1));
|
|
35
|
+
if (typeof oU !== "object" || oU === null)
|
|
36
|
+
throw new Error("not an object");
|
|
37
|
+
const o = oU;
|
|
35
38
|
return {
|
|
36
39
|
strategy: typeof o.strategy === "string" ? o.strategy : undefined,
|
|
37
40
|
correction: typeof o.correction === "string" ? o.correction : undefined,
|
package/dist/brain/anthropic.js
CHANGED
|
@@ -5,10 +5,9 @@ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js"
|
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
6
|
import { emitBrainTelemetry } from "./status-sink.js";
|
|
7
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
8
|
-
import { ANTHROPIC_RESERVED, OUTPUT_CAP_KEYS, applyExtraBody, effectiveOutputCap, stripAuthHeaders } from "./request-params.js";
|
|
9
|
-
import {
|
|
8
|
+
import { ANTHROPIC_RESERVED, OUTPUT_CAP_KEYS, applyExtraBody, effectiveOutputCap, lockHeader, mergeHeaders, stripAuthHeaders, takeHeaderCasefold } from "./request-params.js";
|
|
9
|
+
import { MIN_THINKING_TOKENS, budgetCapSkipsThinking, declaredEffortLevels, reasoningBudgetShare, reasoningRequestCarried, resolveEffort } from "./reasoning.js";
|
|
10
10
|
import { runStreamingBrain } from "./stream-engine.js";
|
|
11
|
-
const MIN_THINKING_TOKENS = 1024;
|
|
12
11
|
function thinkingBudget(maxTokens, share, fixed, hardCap = false) {
|
|
13
12
|
const max = hardCap ? maxTokens : Math.max(maxTokens, MIN_THINKING_TOKENS * 2);
|
|
14
13
|
const want = fixed ?? Math.floor(max * share);
|
|
@@ -261,13 +260,13 @@ export function createAnthropicBrain(config = {}) {
|
|
|
261
260
|
if (options?.temperature !== undefined && anthCompat.supportsTemperature !== false) {
|
|
262
261
|
body.temperature = options.temperature;
|
|
263
262
|
}
|
|
264
|
-
if (model
|
|
263
|
+
if (reasoningRequestCarried(model, options?.reasoning)) {
|
|
265
264
|
if (anthCompat.thinkingMode === "adaptive") {
|
|
266
265
|
body.thinking = { type: "adaptive" };
|
|
267
266
|
}
|
|
268
267
|
else {
|
|
269
268
|
const hardCap = overrides?.maxOutputTokens !== undefined || options?.maxTokens !== undefined;
|
|
270
|
-
if (
|
|
269
|
+
if (budgetCapSkipsThinking(body.max_tokens, hardCap)) {
|
|
271
270
|
body.thinking = undefined;
|
|
272
271
|
delete body.thinking;
|
|
273
272
|
}
|
|
@@ -283,13 +282,9 @@ export function createAnthropicBrain(config = {}) {
|
|
|
283
282
|
}
|
|
284
283
|
const betas = [];
|
|
285
284
|
let sendEffortBeta = false;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
options?.reasoning &&
|
|
290
|
-
options.reasoning !== "off" &&
|
|
291
|
-
isThinkingLevel(options.reasoning)) {
|
|
292
|
-
const { effective } = resolveEffort(options.reasoning, anthCompat.effortLevels.filter(isThinkingLevel));
|
|
285
|
+
const declaredEffort = declaredEffortLevels(anthCompat.effortLevels);
|
|
286
|
+
if (declaredEffort !== undefined && reasoningRequestCarried(model, options?.reasoning)) {
|
|
287
|
+
const { effective } = resolveEffort(options.reasoning, declaredEffort);
|
|
293
288
|
const wireEffort = effective === "minimal" ? "low" : effective;
|
|
294
289
|
body.output_config = { effort: wireEffort };
|
|
295
290
|
sendEffortBeta = true;
|
|
@@ -303,23 +298,19 @@ export function createAnthropicBrain(config = {}) {
|
|
|
303
298
|
if (anthCompat.interleavedThinking) {
|
|
304
299
|
betas.push("interleaved-thinking-2025-05-14");
|
|
305
300
|
}
|
|
306
|
-
const headers =
|
|
307
|
-
...model.headers,
|
|
308
|
-
...config.headers,
|
|
309
|
-
...options?.headers,
|
|
310
|
-
};
|
|
301
|
+
const headers = mergeHeaders(model.headers, config.headers, options?.headers);
|
|
311
302
|
if (options?.apiKey !== undefined)
|
|
312
303
|
stripAuthHeaders(headers);
|
|
313
304
|
if (betas.length > 0) {
|
|
314
|
-
const existing = (headers
|
|
305
|
+
const existing = (takeHeaderCasefold(headers, "anthropic-beta") ?? "").split(",").map((b) => b.trim()).filter(Boolean);
|
|
315
306
|
for (const beta of betas) {
|
|
316
307
|
if (!existing.includes(beta))
|
|
317
308
|
existing.push(beta);
|
|
318
309
|
}
|
|
319
310
|
headers["anthropic-beta"] = existing.join(",");
|
|
320
311
|
}
|
|
321
|
-
headers
|
|
322
|
-
headers
|
|
312
|
+
lockHeader(headers, "content-type", "application/json");
|
|
313
|
+
lockHeader(headers, "anthropic-version", config.version ?? "2023-06-01");
|
|
323
314
|
if (apiKey)
|
|
324
315
|
headers["x-api-key"] = apiKey;
|
|
325
316
|
const builtThinkingRequested = body.thinking !== undefined;
|
|
@@ -5,8 +5,8 @@ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js"
|
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
6
|
import { emitBrainTelemetry } from "./status-sink.js";
|
|
7
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, imagesOmittedNoVisionNote, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
8
|
-
import { OUTPUT_CAP_KEYS, RESPONSES_RESERVED, applyExtraBody, effectiveOutputCap, stripAuthHeaders } from "./request-params.js";
|
|
9
|
-
import {
|
|
8
|
+
import { OUTPUT_CAP_KEYS, RESPONSES_RESERVED, applyExtraBody, effectiveOutputCap, lockHeader, mergeHeaders, stripAuthHeaders } from "./request-params.js";
|
|
9
|
+
import { mintEffortWireValue, reasoningRequestCarried } from "./reasoning.js";
|
|
10
10
|
import { runStreamingBrain } from "./stream-engine.js";
|
|
11
11
|
const DEGENERATE_POLL_CHARS = 64;
|
|
12
12
|
const MALFORMED_SAMPLE_CHARS = 160;
|
|
@@ -151,16 +151,12 @@ function toResponsesTools(ctx) {
|
|
|
151
151
|
}));
|
|
152
152
|
}
|
|
153
153
|
function resolveWireEffort(model, reasoning) {
|
|
154
|
-
if (!model
|
|
154
|
+
if (!reasoningRequestCarried(model, reasoning))
|
|
155
155
|
return undefined;
|
|
156
156
|
const compat = responsesCompat(model);
|
|
157
157
|
if (compat.supportsReasoningEffort === false)
|
|
158
158
|
return undefined;
|
|
159
|
-
|
|
160
|
-
const mapped = model.thinkingLevelMap?.[effort];
|
|
161
|
-
if (mapped === null)
|
|
162
|
-
return undefined;
|
|
163
|
-
return mapped ?? effort;
|
|
159
|
+
return mintEffortWireValue(reasoning, model, compat.reasoningEffortLevels).wireValue;
|
|
164
160
|
}
|
|
165
161
|
function computeUsage(model, raw) {
|
|
166
162
|
const input = raw?.input_tokens ?? 0;
|
|
@@ -268,14 +264,10 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
268
264
|
}
|
|
269
265
|
if (effort !== undefined)
|
|
270
266
|
body.reasoning = { effort };
|
|
271
|
-
const headers =
|
|
272
|
-
...model.headers,
|
|
273
|
-
...config.headers,
|
|
274
|
-
...options?.headers,
|
|
275
|
-
};
|
|
267
|
+
const headers = mergeHeaders(model.headers, config.headers, options?.headers);
|
|
276
268
|
if (options?.apiKey !== undefined)
|
|
277
269
|
stripAuthHeaders(headers);
|
|
278
|
-
headers
|
|
270
|
+
lockHeader(headers, "content-type", "application/json");
|
|
279
271
|
if (apiKey)
|
|
280
272
|
headers["authorization"] = `Bearer ${apiKey}`;
|
|
281
273
|
const wire = applyExtraBody(body, model.extraBody, RESPONSES_RESERVED);
|
package/dist/brain/openai.js
CHANGED
|
@@ -5,8 +5,8 @@ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js"
|
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
6
|
import { emitBrainTelemetry } from "./status-sink.js";
|
|
7
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, imagesOmittedNoVisionNote, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
8
|
-
import { OPENAI_RESERVED, OUTPUT_CAP_KEYS, applyExtraBody, effectiveOutputCap, stripAuthHeaders } from "./request-params.js";
|
|
9
|
-
import {
|
|
8
|
+
import { OPENAI_RESERVED, OUTPUT_CAP_KEYS, applyExtraBody, effectiveOutputCap, lockHeader, mergeHeaders, stripAuthHeaders } from "./request-params.js";
|
|
9
|
+
import { mintEffortWireValue, reasoningRequestCarried } from "./reasoning.js";
|
|
10
10
|
import { runStreamingBrain } from "./stream-engine.js";
|
|
11
11
|
function closeToolCallAccum(acc) {
|
|
12
12
|
if (acc.closedTc)
|
|
@@ -86,19 +86,11 @@ function applyThinking(body, model, reasoning) {
|
|
|
86
86
|
}
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
|
-
if (!model
|
|
89
|
+
if (!reasoningRequestCarried(model, reasoning))
|
|
90
90
|
return;
|
|
91
91
|
const compat = thinkingCompat(model);
|
|
92
92
|
const supportsEffort = compat.supportsReasoningEffort ?? true;
|
|
93
|
-
const
|
|
94
|
-
let effortWire = effort;
|
|
95
|
-
if (model.thinkingLevelMap) {
|
|
96
|
-
const mapped = model.thinkingLevelMap[effort];
|
|
97
|
-
if (mapped === null)
|
|
98
|
-
effortWire = undefined;
|
|
99
|
-
else if (mapped !== undefined)
|
|
100
|
-
effortWire = mapped;
|
|
101
|
-
}
|
|
93
|
+
const effortWire = mintEffortWireValue(reasoning, model, compat.reasoningEffortLevels).wireValue;
|
|
102
94
|
const format = compat.thinkingFormat ?? "openai";
|
|
103
95
|
switch (format) {
|
|
104
96
|
case "openai":
|
|
@@ -311,14 +303,10 @@ export function createOpenAIBrain(config = {}) {
|
|
|
311
303
|
if (options?.stop)
|
|
312
304
|
body.stop = options.stop;
|
|
313
305
|
applyThinking(body, model, options?.reasoning);
|
|
314
|
-
const headers =
|
|
315
|
-
...model.headers,
|
|
316
|
-
...config.headers,
|
|
317
|
-
...options?.headers,
|
|
318
|
-
};
|
|
306
|
+
const headers = mergeHeaders(model.headers, config.headers, options?.headers);
|
|
319
307
|
if (options?.apiKey !== undefined)
|
|
320
308
|
stripAuthHeaders(headers);
|
|
321
|
-
headers
|
|
309
|
+
lockHeader(headers, "content-type", "application/json");
|
|
322
310
|
if (apiKey)
|
|
323
311
|
headers["authorization"] = `Bearer ${apiKey}`;
|
|
324
312
|
const wire = applyExtraBody(body, model.extraBody, OPENAI_RESERVED);
|