@sema-agent/core 5.61.0 → 5.62.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 +67 -0
- package/dist/brain/open-responses.js +8 -3
- package/dist/brain/openai.js +4 -4
- package/dist/brain/stream-engine.d.ts +13 -2
- package/dist/brain/stream-engine.js +3 -3
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/checkpoint-store.d.ts +36 -4
- package/dist/core/checkpoint-store.js +1 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +83 -4
- package/dist/core/hooks.js +3 -3
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/pricing.d.ts +24 -0
- package/dist/core/pricing.js +18 -0
- package/dist/core/runner/prepare-config-doors.d.ts +34 -0
- package/dist/core/runner/prepare-config-doors.js +55 -0
- package/dist/core/runner/prepare-task.d.ts +46 -7
- package/dist/core/runner/prepare-task.js +77 -42
- package/dist/core/runner/runtask.d.ts +7 -0
- package/dist/core/runner/runtask.js +198 -13
- package/dist/core/runner/turn-attachments.d.ts +137 -5
- package/dist/core/runner/turn-attachments.js +25 -2
- package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
- package/dist/core/tool-errors.d.ts +2 -1
- package/dist/core/tool-policy.d.ts +27 -0
- package/dist/core/types.d.ts +138 -12
- package/dist/core/untrusted-text.d.ts +5 -4
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/usage-window-store.d.ts +109 -8
- package/dist/core/usage-window-store.js +79 -12
- package/dist/orchestration/run-workflow-tool.d.ts +2 -2
- package/dist/orchestration/workflow.d.ts +2 -2
- package/dist/prompt-assembly/event-registry.js +2 -0
- package/dist/server/http.d.ts +1 -1
- package/dist/stores/file/usage-window-store.d.ts +1 -1
- package/dist/stores/file/usage-window-store.js +27 -6
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/scheduler-tools.js +9 -1
- package/package.json +1 -1
|
@@ -705,10 +705,7 @@ export type ResumeOutcome = {
|
|
|
705
705
|
* resolve CAS: a second wake of the same token loses the CAS (`checkpoint.already_resolved`). */
|
|
706
706
|
| {
|
|
707
707
|
gate: "wake";
|
|
708
|
-
message?:
|
|
709
|
-
text: string;
|
|
710
|
-
trusted: boolean;
|
|
711
|
-
};
|
|
708
|
+
message?: PendingSteerInput;
|
|
712
709
|
} | {
|
|
713
710
|
gate: "task_done";
|
|
714
711
|
result: TaskResult;
|
|
@@ -737,6 +734,17 @@ export type PendingAction = {
|
|
|
737
734
|
* **UNTRUSTED, ADVISORY**: inboxes must escape contextually and surface `args` alongside.
|
|
738
735
|
* NEVER read by the resume path (args + boundInputHash stay the sole execution contract). */
|
|
739
736
|
preview?: unknown;
|
|
737
|
+
/** #457 ② (additive; no checkpoint-version bump — the `hasBidiControls` precedent below: an
|
|
738
|
+
* optional field an older reader ignores and the resume path never reads) — the PARK twin of
|
|
739
|
+
* `AskRequest.previewWithheld`: present ⇔ this tool declares an approval preview and the mint
|
|
740
|
+
* could not present a usable one, `"oversize"` (produced but past the 16KiB display bound — the
|
|
741
|
+
* stub still rides {@link preview}) or `"unavailable"` (threw / no JSON serialization). ABSENT
|
|
742
|
+
* covers "no preview declared", "the projection declined" and "it worked" — read presence, never
|
|
743
|
+
* absence. A DISCLOSURE for the approval inbox's card, never a verdict: {@link args} and
|
|
744
|
+
* {@link boundInputHash} are carried either way and the resume contract is untouched. Judged over
|
|
745
|
+
* THIS row's fidelity-projected args snapshot, so it can honestly differ from the synchronous
|
|
746
|
+
* face on a shape-changing backend — exactly as {@link hasBidiControls} can. */
|
|
747
|
+
previewWithheld?: "oversize" | "unavailable";
|
|
740
748
|
/** design/179 §4, re-formed by design/375 §3.1/§3.3 (BREAKING — replaces the retired
|
|
741
749
|
* `ruleSuggestions` seat; a pre-375 inbox reading a new row simply finds no offers and renders
|
|
742
750
|
* no "don't ask again" option, fail toward asking) — the persistable allow-rule OFFERS this
|
|
@@ -1689,6 +1697,18 @@ export interface CheckpointSummary {
|
|
|
1689
1697
|
* contextually escape), ECHO-ONLY (no gate/CAS/resume reads it), and surfaces SHOULD show the
|
|
1690
1698
|
* bound raw args (`toolInput`/`pendingAction.args`) alongside — the preview never replaces them. */
|
|
1691
1699
|
preview?: unknown;
|
|
1700
|
+
/** #457 ② — the row's `PendingAction.tool_approval.previewWithheld`, projected BESIDE {@link preview}
|
|
1701
|
+
* for the same reason that one is: without it the ONE-CALL inbox cannot tell "this tool declares no
|
|
1702
|
+
* preview" from "the projection failed", which is the exact collapse the determinant exists to
|
|
1703
|
+
* remove — and re-opening the row with an N+1 `get` just to answer it defeats `listByScope`.
|
|
1704
|
+
* `"oversize"` (produced but past the 16KiB mint bound — the truncation stub still rides
|
|
1705
|
+
* {@link preview}) or `"unavailable"` (threw / no JSON serialization). The {@link toolCallId}
|
|
1706
|
+
* ABSENCE-SHAPE contract applies: OMITTED otherwise, never `null`/`false`/present-but-`undefined`.
|
|
1707
|
+
* A pure echo of the row's own persisted value — unlike {@link hasBidiControls}, there is no
|
|
1708
|
+
* projection-time backfill, because whether a tool DECLARED a preview is not knowable from the
|
|
1709
|
+
* projection's own materials (a pre-#457 row therefore reads absent, which is honest: unreported).
|
|
1710
|
+
* ECHO-ONLY display metadata; nothing gates on it. */
|
|
1711
|
+
previewWithheld?: "oversize" | "unavailable";
|
|
1692
1712
|
/**
|
|
1693
1713
|
* #438: PRESENCE ONLY — this pending `tool_approval` row's payload carries at least one DIRECTIONAL
|
|
1694
1714
|
* format control (the {@link import("./tool-policy.js").AskRequest.hasBidiControls} character class;
|
|
@@ -1799,6 +1819,12 @@ export declare class CheckpointError extends Error {
|
|
|
1799
1819
|
* second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
|
|
1800
1820
|
* Nothing is written on this path. */
|
|
1801
1821
|
| "steering.duplicate_input_id"
|
|
1822
|
+
/** design/373 §4.3 (D2): the deployment's `userPromptSubmit` hook refused a wake resume's
|
|
1823
|
+
* `message` at the entrance screen (block verdict, or a fail-closed timeout/crash — the
|
|
1824
|
+
* message discriminates). Pre-CAS: the checkpoint stays `pending` and byte-identical, so the
|
|
1825
|
+
* SAME token is redeemable once the screen clears — a refusal burns nothing. The live steer
|
|
1826
|
+
* verb refuses under the same code word (its own Error face); one vocabulary, two entrances. */
|
|
1827
|
+
| "steering.blocked_by_hook"
|
|
1802
1828
|
/** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
|
|
1803
1829
|
* checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
|
|
1804
1830
|
* resource-limit continue). Wake is a pure un-park verb and must never circumvent an approval — resume
|
|
@@ -1948,6 +1974,12 @@ export declare class CheckpointError extends Error {
|
|
|
1948
1974
|
* second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
|
|
1949
1975
|
* Nothing is written on this path. */
|
|
1950
1976
|
| "steering.duplicate_input_id"
|
|
1977
|
+
/** design/373 §4.3 (D2): the deployment's `userPromptSubmit` hook refused a wake resume's
|
|
1978
|
+
* `message` at the entrance screen (block verdict, or a fail-closed timeout/crash — the
|
|
1979
|
+
* message discriminates). Pre-CAS: the checkpoint stays `pending` and byte-identical, so the
|
|
1980
|
+
* SAME token is redeemable once the screen clears — a refusal burns nothing. The live steer
|
|
1981
|
+
* verb refuses under the same code word (its own Error face); one vocabulary, two entrances. */
|
|
1982
|
+
| "steering.blocked_by_hook"
|
|
1951
1983
|
/** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
|
|
1952
1984
|
* checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
|
|
1953
1985
|
* resource-limit continue). Wake is a pure un-park verb and must never circumvent an approval — resume
|
|
@@ -260,6 +260,7 @@ export function summarizeCheckpoint(cp) {
|
|
|
260
260
|
? { restoreMode: cp.state.workspaceHandle.restoreMode === "park_only" ? "park_only" : "snapshot" }
|
|
261
261
|
: {}),
|
|
262
262
|
...(tool?.preview !== undefined ? { preview: tool.preview } : {}),
|
|
263
|
+
...(tool?.previewWithheld === "oversize" || tool?.previewWithheld === "unavailable" ? { previewWithheld: tool.previewWithheld } : {}),
|
|
263
264
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
264
265
|
};
|
|
265
266
|
}
|
|
@@ -93,7 +93,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
93
93
|
* src/ for notice mint shapes and names any code that is minted but unregistered, or registered but
|
|
94
94
|
* no longer minted.
|
|
95
95
|
*/
|
|
96
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "delegation.transcript_integrity", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "route.fallback_to_primary", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "task.turn_interrupted", "tool_result.offload_put_failed"];
|
|
96
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "delegation.transcript_integrity", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "route.fallback_to_primary", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "tool_result.offload_put_failed"];
|
|
97
97
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
98
98
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
99
99
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -113,6 +113,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
113
113
|
"route.base_url_changed_key_unchanged",
|
|
114
114
|
"task.user_steer_undrained",
|
|
115
115
|
"task.user_followup_undrained",
|
|
116
|
+
"steering.parked_input_blocked",
|
|
116
117
|
"task.turn_interrupted",
|
|
117
118
|
"tool_result.offload_put_failed",
|
|
118
119
|
];
|
|
@@ -126,6 +127,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
126
127
|
"task.user_steer_undrained": "user",
|
|
127
128
|
"task.user_followup_undrained": "user",
|
|
128
129
|
"task.turn_interrupted": "user",
|
|
130
|
+
"steering.parked_input_blocked": "user",
|
|
129
131
|
"config.autocompact_window_clamped": "operator",
|
|
130
132
|
"config.env_timeout_discarded": "operator",
|
|
131
133
|
"config.materialize_env_discarded": "operator",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DocumentContent, ImageContent, TextContent } from "../internal/llm.js";
|
|
1
|
+
import type { ActorAssertion, DocumentContent, ImageContent, TextContent } from "../internal/llm.js";
|
|
2
2
|
import type { ExecutionEnv, FileError, Result, SessionTreeEntry } from "../internal/harness-types.js";
|
|
3
3
|
import type { DecisionReason, PermissionResult, ResolvedAsk, ToolCallRequest, ToolPolicy } from "./tool-policy.js";
|
|
4
4
|
import { type AskClass } from "./ask-class.js";
|
|
@@ -85,9 +85,36 @@ export interface Hooks {
|
|
|
85
85
|
*/
|
|
86
86
|
preToolUseObservational?: true;
|
|
87
87
|
postToolUse?(toolName: string, input: unknown, output: HookToolOutput, ctx: HookToolContext): PostToolUseResult | undefined | Promise<PostToolUseResult | undefined>;
|
|
88
|
+
/**
|
|
89
|
+
* design/373 §4.3 (D2) ⚠️ WIDENED INVOCATION SET: this seat now screens EVERY user-lane entrance,
|
|
90
|
+
* not just the objective — a live `TaskStream.steer`, a wake resume's message (pre-CAS, at the
|
|
91
|
+
* verb), and a parked steer's resume redelivery (any draining resume kind) all pass through it, discriminated by
|
|
92
|
+
* {@link UserPromptSubmitContext.source}. A hook that blocks unconditionally therefore starts
|
|
93
|
+
* refusing steers too — branch on `ctx.source` (+ `ctx.actor`) to scope a verdict. The screen's
|
|
94
|
+
* domain is caller/operator PROSE only: engine-authored frames (task notifications, diagnostics,
|
|
95
|
+
* recall) never pass here — external `notify()` text takes the sanitized DATA-frame lane instead
|
|
96
|
+
* (ruled: notification frames are not prompts; see the notify contract). The same input can
|
|
97
|
+
* legally arrive twice under two sources (live + parked redelivery) — idempotency is the hook's,
|
|
98
|
+
* keyed on `ctx.inputId`. Block semantics per entrance: objective ⇒ the task ends `blocked`;
|
|
99
|
+
* live steer ⇒ typed `steering.blocked_by_hook` to the steer caller; resume message ⇒ typed
|
|
100
|
+
* pre-CAS refusal (the checkpoint stays pending); parked redelivery ⇒ the frame is withheld
|
|
101
|
+
* with a `steering.parked_input_blocked` notice + a `delivery:"blocked"` human_input account,
|
|
102
|
+
* and the resume itself proceeds (a blocked instruction must never wedge a wake).
|
|
103
|
+
*
|
|
104
|
+
* ⚠️ RE-ENTRANT within one leg: the steer verb goes live the moment the run's handle publishes —
|
|
105
|
+
* BEFORE the objective seat runs — so a steer screen can overlap the objective screen (and a
|
|
106
|
+
* resume-message screen, which fires pre-CAS at the verb) in flight. A stateful implementation
|
|
107
|
+
* (per-call temp files, a single in-flight latch, a shared accumulator) must tolerate concurrent
|
|
108
|
+
* entry; only steer-vs-steer is serialized. And that serialization has a LATENCY face: queued
|
|
109
|
+
* steers screen strictly FIFO, so each waits behind its predecessor's ENTIRE hook bound (the
|
|
110
|
+
* resolved `timeoutMs`, default 600s) — a slow or wedged screen delays every steer behind it by
|
|
111
|
+
* up to the full bound, which is a reason to declare a short `timeoutMs` on deployments that
|
|
112
|
+
* screen steers.
|
|
113
|
+
*/
|
|
88
114
|
userPromptSubmit?(prompt: string,
|
|
89
115
|
/** #281 件A (additive): the run/leg identity envelope — an implementation declaring only
|
|
90
|
-
* `(prompt)` keeps working. Always supplied on the engine's emission.
|
|
116
|
+
* `(prompt)` keeps working. Always supplied on the engine's emission. design/373: carries the
|
|
117
|
+
* entrance discriminator (`source`/`inputId`/`actor`) — see {@link UserPromptSubmitContext}. */
|
|
91
118
|
ctx?: UserPromptSubmitContext): UserPromptSubmitResult | undefined | Promise<UserPromptSubmitResult | undefined>;
|
|
92
119
|
/**
|
|
93
120
|
* roadmap #5 (CC 198 Stop hook, :473831-): runs when the agent WOULD OTHERWISE END its run (no
|
|
@@ -819,12 +846,55 @@ export interface PostToolUseResult {
|
|
|
819
846
|
additionalContext?: string;
|
|
820
847
|
}
|
|
821
848
|
/** #281 件A — the context of a {@link Hooks.userPromptSubmit} invocation (second parameter,
|
|
822
|
-
* additive).
|
|
849
|
+
* additive). An object so later additions stay additive; design/373 §4.3 (D2) grew the
|
|
850
|
+
* entrance-discrimination trio below. */
|
|
823
851
|
export interface UserPromptSubmitContext {
|
|
824
852
|
/** The run/leg identity envelope — see {@link HookInvocationIdentity} for the presence law. */
|
|
825
853
|
identity?: HookInvocationIdentity;
|
|
826
854
|
/** {@link HookSeatSignal} — this invocation's own abort signal. */
|
|
827
855
|
signal?: AbortSignal;
|
|
856
|
+
/**
|
|
857
|
+
* design/373 §4.3 (D2) — WHICH user-lane entrance this text is entering through. The screen now
|
|
858
|
+
* sits at every user-lane entrance, so a hook that used to see only the objective can (and, if it
|
|
859
|
+
* blocks unconditionally, MUST) branch on this: `"objective"` = the task's opening prompt (the
|
|
860
|
+
* pre-373 only caller); `"steer"` = a live mid-run TaskStream.steer; `"resume_message"` = the
|
|
861
|
+
* message riding a wake resume (screened at the verb, pre-CAS); `"parked_redelivery"` = a parked
|
|
862
|
+
* steer being re-screened on the CURRENT process's hook when a resume redelivers it — ANY resume
|
|
863
|
+
* kind that drains parked steers (policy_ask / dry_run_review / plan_review / resource_limit,
|
|
864
|
+
* wake included), because a park's screening cannot be carried across processes (hooks are
|
|
865
|
+
* process-local config). ⚠️ DOUBLE ARRIVAL IS CONTRACTUAL:
|
|
866
|
+
* the same input can reach a hook twice with different sources (screened live as `"steer"`,
|
|
867
|
+
* parked, re-screened as `"parked_redelivery"`); idempotency is the hook's to hold — a stateful
|
|
868
|
+
* hook keys on {@link inputId}. `"followUp"` is a RESERVED spelling (spec seat only): the Runner
|
|
869
|
+
* face has no user-lane followUp verb today, so no invocation carries it yet — if/when that face
|
|
870
|
+
* lands it arrives screened under this name (never a silent widening).
|
|
871
|
+
* Absent ⇒ an emission from a pre-373 engine (old engines stamped nothing); every current engine
|
|
872
|
+
* seat stamps its name, so treat absence as "version unknown", never as a sixth entrance.
|
|
873
|
+
*/
|
|
874
|
+
source?: "objective" | "steer" | "resume_message" | "followUp" | "parked_redelivery";
|
|
875
|
+
/**
|
|
876
|
+
* design/373 §4.3 (D2) — the screened input's correlation id. Present on EVERY entrance except
|
|
877
|
+
* the objective seat (no id exists on that face — nothing is fabricated there):
|
|
878
|
+
* - `"steer"` — the effective `inputId` (caller-supplied or engine-minted — the SAME id the
|
|
879
|
+
* `human_input` event and any parked copy carry);
|
|
880
|
+
* - `"parked_redelivery"` — the parked entry's STORED id (stable across redeliveries);
|
|
881
|
+
* - `"resume_message"` — always present too: the caller's id verbatim when it supplied one,
|
|
882
|
+
* otherwise MINTED PER CALL by the wake validator. ⚠️ Per call, not per message: the screen
|
|
883
|
+
* sits above every retryable pre-CAS refusal, so a retried wake that supplies no caller id
|
|
884
|
+
* re-screens identical text under a FRESH id. A dedup/quota hook keying its ledger here gets
|
|
885
|
+
* cross-attempt stability only when the CALLER supplies the id — a host that retries wakes
|
|
886
|
+
* should mint its own.
|
|
887
|
+
* This is the dedup key for the double-arrival contract on {@link source}: a quota-charging hook
|
|
888
|
+
* that must not double-charge one input keys its ledger here.
|
|
889
|
+
*/
|
|
890
|
+
inputId?: string;
|
|
891
|
+
/**
|
|
892
|
+
* design/373 §4.3 (D2) — the API-boundary actor snapshot attributed to this input (the same
|
|
893
|
+
* snapshot the `human_input` event reports), when the caller supplied one. Attribution only,
|
|
894
|
+
* never authority — a hook wanting to exempt its own supervisor lane branches on this PLUS
|
|
895
|
+
* {@link source}, and absence means "anonymous caller", exactly as on the event face.
|
|
896
|
+
*/
|
|
897
|
+
actor?: ActorAssertion;
|
|
828
898
|
}
|
|
829
899
|
/** #281 件A — the context of a {@link Hooks.postToolBatch} invocation (third parameter, additive —
|
|
830
900
|
* deliberately NOT folded into `meta`, whose presence already means "the engine injected here"). */
|
|
@@ -1199,7 +1269,16 @@ export interface ToolGateInput {
|
|
|
1199
1269
|
/** design/375 §5.2②: the surviving ask's engine-stamped `segmentCoverage` — threaded so the park
|
|
1200
1270
|
* mint's offer factory reads the same coverage snapshot on the durable route as the synchronous
|
|
1201
1271
|
* mints read off the decision (absent ⇒ all segments uncovered ⇒ over-offer, the safe way). */
|
|
1202
|
-
segmentCoverage?: readonly import("./permission-rule-model.js").SegmentCoverage[]
|
|
1272
|
+
segmentCoverage?: readonly import("./permission-rule-model.js").SegmentCoverage[],
|
|
1273
|
+
/** #457 ④: the surviving ask's `matchedAskRule` — threaded for the SAME errand as
|
|
1274
|
+
* `askDecisionReason`: the park mint's rule-suggestion door must know that an EXPLICIT `ask` rule
|
|
1275
|
+
* matched this call, because the persisted-rule lane refuses to clear such an ask (the
|
|
1276
|
+
* `decision.matchedAskRule === undefined` conjunct on its allow arm), so the parked card must not
|
|
1277
|
+
* offer to mint a rule that would never silence it. APPENDED at the tail deliberately: every
|
|
1278
|
+
* parameter here is positional and `probeReason` is also a `string`, so inserting this beside its
|
|
1279
|
+
* logical neighbour would silently re-bind three existing call sites' arguments with no type
|
|
1280
|
+
* error to catch it. */
|
|
1281
|
+
matchedAskRule?: string) => Promise<ToolGateResult["suspend"] | ParkAttemptFailed | undefined>;
|
|
1203
1282
|
/**
|
|
1204
1283
|
* design/174 — route a policy `ask` on the reserved question tool to this run's CONTENT-ask channel
|
|
1205
1284
|
* before it can become a park or a refusal. Called in the `ask` branch with the FINAL post-hook,
|
package/dist/core/hooks.js
CHANGED
|
@@ -841,7 +841,7 @@ export async function runToolGate(input) {
|
|
|
841
841
|
? { origin: orgAskOrigin !== undefined ? `org_${orgAskOrigin}` : "policy" }
|
|
842
842
|
: undefined;
|
|
843
843
|
if (suspendAsk && decision.action === "ask") {
|
|
844
|
-
const suspended = await suspendAsk(req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined);
|
|
844
|
+
const suspended = await suspendAsk(req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined);
|
|
845
845
|
if (suspended) {
|
|
846
846
|
if ("parkFailed" in suspended)
|
|
847
847
|
parkFailed = suspended.parkFailed;
|
|
@@ -860,7 +860,7 @@ export async function runToolGate(input) {
|
|
|
860
860
|
req.args = outcome.presentedInput;
|
|
861
861
|
}
|
|
862
862
|
if (suspendAsk && outcome.parkDeclined && parkFailed === undefined) {
|
|
863
|
-
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined);
|
|
863
|
+
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined);
|
|
864
864
|
if (suspended) {
|
|
865
865
|
if ("parkFailed" in suspended)
|
|
866
866
|
parkFailed = suspended.parkFailed;
|
|
@@ -892,7 +892,7 @@ export async function runToolGate(input) {
|
|
|
892
892
|
askDenyResolution = resolved.resolution;
|
|
893
893
|
decision = resolved;
|
|
894
894
|
if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && parkFailed === undefined) {
|
|
895
|
-
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined);
|
|
895
|
+
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.matchedAskRule : undefined);
|
|
896
896
|
if (suspended) {
|
|
897
897
|
if ("parkFailed" in suspended)
|
|
898
898
|
parkFailed = suspended.parkFailed;
|
|
@@ -71,6 +71,8 @@ function syntheticCheckpoint(scope) {
|
|
|
71
71
|
args: { probe: true, nested: { depth: 2, list: [1, 2, 3] } },
|
|
72
72
|
boundInputHash: boundInputHashOf({ probe: true, nested: { depth: 2, list: [1, 2, 3] } }),
|
|
73
73
|
hasBidiControls: true,
|
|
74
|
+
preview: { probe: true, note: "park wiring self-check display stub" },
|
|
75
|
+
previewWithheld: "oversize",
|
|
74
76
|
batchToolCallIds: [`${scope}:call`],
|
|
75
77
|
completedCallIds: [],
|
|
76
78
|
},
|
package/dist/core/pricing.d.ts
CHANGED
|
@@ -37,6 +37,30 @@ export interface TokenCounts {
|
|
|
37
37
|
* pricing the cache subset separately instead of trusting the brain's possibly-overstated cost total.
|
|
38
38
|
*/
|
|
39
39
|
export declare function computeCostMicroUsd(counts: TokenCounts, pricing: ModelPricing): number;
|
|
40
|
+
/**
|
|
41
|
+
* RB-368's predicate, single-sourced: does a price table EXIST for this model — an id-keyed
|
|
42
|
+
* `RunnerDeps.pricing` entry, or the model's own `cost` declaration? When neither does,
|
|
43
|
+
* `modelCostToPricing(undefined)` yields an all-zero table and every computed cost is a fabricated 0,
|
|
44
|
+
* indistinguishable from "declared free". Every consumer that must keep those apart (the disclosure faces
|
|
45
|
+
* that go absent rather than lie; the deployment $ governance window that refuses rather than charge a
|
|
46
|
+
* fabricated 0) asks HERE, so the two can never drift into two different answers.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isModelPriced(model: {
|
|
49
|
+
id: string;
|
|
50
|
+
cost?: unknown;
|
|
51
|
+
}, pricing: Record<string, ModelPricing> | undefined): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The first UNEVALUABLE member of a price table (`"inputPer1M"`, …), or `undefined` when every declared
|
|
54
|
+
* price is a finite, non-negative number.
|
|
55
|
+
*
|
|
56
|
+
* {@link isModelPriced} answers "does a table EXIST", which is the RB-368 question. It is deliberately not
|
|
57
|
+
* the same question as "can this table price anything": a `NaN` rate silently computes a cost of ZERO (the
|
|
58
|
+
* `price && tokens` guard treats NaN as absent) and a negative rate computes a negative one, so a table of
|
|
59
|
+
* garbage looks priced and prices everything at nothing. Wherever a number from this table becomes a
|
|
60
|
+
* CEILING rather than a report, the caller must ask this too — a bad value has to be loud, never folded
|
|
61
|
+
* into a silent default.
|
|
62
|
+
*/
|
|
63
|
+
export declare function malformedPricingField(pricing: ModelPricing): string | undefined;
|
|
40
64
|
/** Map a vendor `Model.cost` (already per-1M absolute) to `ModelPricing`. We don't emit 1h cache
|
|
41
65
|
* writes, so `cacheWriteLongPer1M` defaults to the 5-min write price (irrelevant while that count is 0). */
|
|
42
66
|
export declare function modelCostToPricing(cost: {
|
package/dist/core/pricing.js
CHANGED
|
@@ -11,6 +11,24 @@ export function computeCostMicroUsd(counts, pricing) {
|
|
|
11
11
|
per1M(counts.outputTokens, pricing.outputPer1M);
|
|
12
12
|
return Math.round(usd * 1e6);
|
|
13
13
|
}
|
|
14
|
+
export function isModelPriced(model, pricing) {
|
|
15
|
+
return pricing?.[model.id] !== undefined || model.cost !== undefined;
|
|
16
|
+
}
|
|
17
|
+
export function malformedPricingField(pricing) {
|
|
18
|
+
for (const f of ["inputPer1M", "outputPer1M"]) {
|
|
19
|
+
const v = pricing[f];
|
|
20
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0)
|
|
21
|
+
return f;
|
|
22
|
+
}
|
|
23
|
+
for (const f of ["cacheReadPer1M", "cacheWritePer1M", "cacheWriteLongPer1M"]) {
|
|
24
|
+
const v = pricing[f];
|
|
25
|
+
if (v === undefined)
|
|
26
|
+
continue;
|
|
27
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0)
|
|
28
|
+
return f;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
14
32
|
export function modelCostToPricing(cost) {
|
|
15
33
|
return {
|
|
16
34
|
inputPer1M: cost?.input ?? 0,
|
|
@@ -46,6 +46,40 @@ export declare function limitConfigError(code: string, message: string): Error &
|
|
|
46
46
|
* sentinel, and on the budget axes it is an exhausted window (absurd but honest).
|
|
47
47
|
*/
|
|
48
48
|
export declare function resolveTaskLimits(limits: TaskLimits | undefined): TaskLimits | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* RB-318 — validate the MODE-VALUED members of `TaskSpec.attachments` at the same door, and return the
|
|
51
|
+
* config unchanged. Today that is exactly one member, `totalTokensReminderMode`.
|
|
52
|
+
*
|
|
53
|
+
* WHY A DOOR AT ALL (坏值响亮度律): a knob whose bad value silently folds to the default is a knob the
|
|
54
|
+
* deployment believes is armed. `totalTokensReminderMode: "padded_countdown"` (underscore) or
|
|
55
|
+
* `"Countdown"` are the near-misses a hand-written config produces, and both would otherwise read as
|
|
56
|
+
* "absent" and take the default arm — a DIFFERENT readout than the one that was asked for, with nothing
|
|
57
|
+
* anywhere saying so.
|
|
58
|
+
*
|
|
59
|
+
* DELIBERATELY NARROW, twice over:
|
|
60
|
+
* - no unknown-KEY refusal (the `resolveTaskLimits` half that has no counterpart here): a wire caller
|
|
61
|
+
* that sends an attachment flag this engine does not know is asking for a lane that stays off, which
|
|
62
|
+
* is the family's own default posture — not a ceiling silently disarmed. Widening it would also be a
|
|
63
|
+
* behavior change for every existing caller, which this additive lane does not license.
|
|
64
|
+
* - the pre-existing `todoReminderMode` is NOT validated here. It has the same shape and the same
|
|
65
|
+
* weakness, but tightening it would refuse specs that run today; recorded as a follow-up rather than
|
|
66
|
+
* folded into this lane's change.
|
|
67
|
+
*
|
|
68
|
+
* A NON-OBJECT CONTAINER (`attachments: null` off a wire, above all) is neither refused nor crashed on
|
|
69
|
+
* (review finding, verified by reading): every consumer reads this config through `attachmentsCfg?.…`,
|
|
70
|
+
* so a null container already means "no lanes are wired" and the spec RUNS today. An unguarded property
|
|
71
|
+
* read here — the first cut — turned that running spec into an untyped `TypeError` from inside a door
|
|
72
|
+
* that promises a typed `config.attachment_invalid`, and refusing it instead would newly reject a spec
|
|
73
|
+
* that works. There is no container-shape door in this family yet; that is a separate widening, not
|
|
74
|
+
* something this lane gets to invent on the way past.
|
|
75
|
+
*
|
|
76
|
+
* The bypass is narrowed to values that CANNOT supply attachment fields (round-2 review finding): a
|
|
77
|
+
* plain-JS caller can hand this a FUNCTION carrying `totalTokensReminderMode` as a property, and every
|
|
78
|
+
* downstream `attachmentsCfg?.…` read would happily find it — so skipping a function here would fold a
|
|
79
|
+
* typo'd mode to the default through exactly the hole this door exists to close. Functions are
|
|
80
|
+
* property-bearing containers and are validated like objects; only `null` and primitives pass through.
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveAttachmentsConfig(attachments: TaskSpec["attachments"]): TaskSpec["attachments"];
|
|
49
83
|
/**
|
|
50
84
|
* R2 双形轴(追加令 2026-07-18): CC 2.1.212's fable-variant prompt gate (b9e —
|
|
51
85
|
* `fable_5_mitigations` capability / claude-mythos-5), ORTHOGONAL to the simple/classic profile.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { resolveBrainCallGuardrailMs } from "../../brain/timeout.js";
|
|
2
2
|
import { assertReadFaceValue } from "../../tools/fs/index.js";
|
|
3
3
|
import { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
4
|
+
import { isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
|
|
4
5
|
import { preflightLockedConfig } from "../locked-config.js";
|
|
5
6
|
import { assertRetentionCapability } from "../retention.js";
|
|
6
7
|
import { resolveModel, resolveTaskModel, roleModelIfSet } from "../roles.js";
|
|
7
8
|
import { applyToolModelGate, assertRestoreGatedToolsValue, modelIdTail } from "../tool-model-gate.js";
|
|
8
9
|
import { resolveUsageWindows } from "../usage-window-store.js";
|
|
9
10
|
import { deriveAskEffective, resolveAskSeamForm, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
11
|
+
import { TOTAL_TOKENS_REMINDER_MODES } from "./turn-attachments.js";
|
|
10
12
|
const TASK_LIMIT_KEY_DICT = {
|
|
11
13
|
maxTokens: true,
|
|
12
14
|
maxCostUsd: true,
|
|
@@ -31,6 +33,16 @@ export function limitConfigError(code, message) {
|
|
|
31
33
|
e.code = code;
|
|
32
34
|
return e;
|
|
33
35
|
}
|
|
36
|
+
function describeRejectedValue(value) {
|
|
37
|
+
try {
|
|
38
|
+
const quoted = JSON.stringify(value);
|
|
39
|
+
if (quoted !== undefined)
|
|
40
|
+
return quoted;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
}
|
|
44
|
+
return `a ${typeof value} value`;
|
|
45
|
+
}
|
|
34
46
|
export function resolveTaskLimits(limits) {
|
|
35
47
|
if (limits === undefined)
|
|
36
48
|
return undefined;
|
|
@@ -73,6 +85,19 @@ export function resolveTaskLimits(limits) {
|
|
|
73
85
|
}
|
|
74
86
|
return limits;
|
|
75
87
|
}
|
|
88
|
+
export function resolveAttachmentsConfig(attachments) {
|
|
89
|
+
if (attachments === null || (typeof attachments !== "object" && typeof attachments !== "function"))
|
|
90
|
+
return attachments;
|
|
91
|
+
const mode = attachments.totalTokensReminderMode;
|
|
92
|
+
if (mode !== undefined) {
|
|
93
|
+
const legal = TOTAL_TOKENS_REMINDER_MODES;
|
|
94
|
+
if (typeof mode !== "string" || !legal.includes(mode)) {
|
|
95
|
+
throw limitConfigError("config.attachment_invalid", `TaskSpec.attachments.totalTokensReminderMode must be one of ${TOTAL_TOKENS_REMINDER_MODES.map((m) => `"${m}"`).join(", ")} ` +
|
|
96
|
+
`(got ${describeRejectedValue(mode)}) — refused rather than folded to the default, which would publish a different readout than the one the deployment asked for.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return attachments;
|
|
100
|
+
}
|
|
76
101
|
export function isFableFamilyModelId(id) {
|
|
77
102
|
const tail = modelIdTail(id);
|
|
78
103
|
return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
|
|
@@ -290,6 +315,7 @@ export function prepareConfigDoors(input) {
|
|
|
290
315
|
}
|
|
291
316
|
}
|
|
292
317
|
resolveTaskLimits(spec.limits);
|
|
318
|
+
resolveAttachmentsConfig(spec.attachments);
|
|
293
319
|
if (spec.resourceSuspend !== undefined) {
|
|
294
320
|
const rsus = spec.resourceSuspend;
|
|
295
321
|
if (typeof rsus.scope !== "string" || rsus.scope === "") {
|
|
@@ -327,6 +353,35 @@ export function prepareConfigDoors(input) {
|
|
|
327
353
|
}
|
|
328
354
|
const resolvedRole = resolveTaskModel(spec, deps);
|
|
329
355
|
const model = resolvedRole.model;
|
|
356
|
+
if (usageWindows !== undefined && deps.usageWindowStore !== undefined) {
|
|
357
|
+
const costed = usageWindows.find((w) => w.maxCostUsd !== undefined);
|
|
358
|
+
const why = costed === undefined
|
|
359
|
+
? undefined
|
|
360
|
+
: !isModelPriced(model, deps.pricing)
|
|
361
|
+
? `model ${JSON.stringify(model.id)} is unpriced — it has neither a RunnerDeps.pricing entry nor a Model.cost declaration, so this run produces NO cost figure to charge the window with (an unpriced run has no cost, which is not the same as a cost of 0). Price the model, or drop maxCostUsd from the window.`
|
|
362
|
+
: (() => {
|
|
363
|
+
const bad = malformedPricingField(deps.pricing?.[model.id] ?? modelCostToPricing(model.cost));
|
|
364
|
+
return bad === undefined
|
|
365
|
+
? undefined
|
|
366
|
+
: `the price table for model ${JSON.stringify(model.id)} declares an unevaluable ${bad} — every rate must be a finite, non-negative number of USD per 1M tokens. A table like this prices every turn at zero (or below), so the ceiling would never fill: refused rather than enforced in name only.`;
|
|
367
|
+
})();
|
|
368
|
+
if (costed !== undefined && why !== undefined) {
|
|
369
|
+
const e = new Error(`RunnerDeps.usageWindows declares a cost ceiling (maxCostUsd ${String(costed.maxCostUsd)} over ${String(costed.windowMs)}ms, ${costed.anchor}) but ${why}`);
|
|
370
|
+
e.code = "config.usage_window_unpriced";
|
|
371
|
+
throw e;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const dollarCeiling = spec.limits?.maxCostUsd !== undefined
|
|
375
|
+
? "limits.maxCostUsd"
|
|
376
|
+
: spec.resourceSuspend?.totalBudgetUsd !== undefined
|
|
377
|
+
? "resourceSuspend.totalBudgetUsd"
|
|
378
|
+
: undefined;
|
|
379
|
+
if (dollarCeiling !== undefined && isModelPriced(model, deps.pricing)) {
|
|
380
|
+
const bad = malformedPricingField(deps.pricing?.[model.id] ?? modelCostToPricing(model.cost));
|
|
381
|
+
if (bad !== undefined) {
|
|
382
|
+
throw limitConfigError("config.limit_invalid", `TaskSpec.${dollarCeiling} is a money ceiling, but the price table for model ${JSON.stringify(model.id)} declares an unevaluable ${bad} — every rate must be a finite, non-negative number of USD per 1M tokens. A table like this prices every turn at zero (or below), so the ceiling would never trip: refused rather than enforced in name only.`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
330
385
|
const fableMitigations = resolveModelPromptTraits(model, spec, internals).fableMitigations;
|
|
331
386
|
const gateDecision = applyToolModelGate({
|
|
332
387
|
tools: spec.tools,
|
|
@@ -73,14 +73,24 @@ export declare const USAGE_WINDOW_REAP_MARGIN_MS: number;
|
|
|
73
73
|
export interface UsageGovernance {
|
|
74
74
|
/** The ledger key this run is governed under — the principal, or the shared global key. */
|
|
75
75
|
readonly key: string;
|
|
76
|
+
/** Does any governed window carry a MONEY ceiling? Read by the run loop's pricing seats: only a
|
|
77
|
+
* cost-governing deployment has to treat an unevaluable price table as unpriced spend. */
|
|
78
|
+
readonly governsCost: boolean;
|
|
76
79
|
/** Ms the caller must wait before ANY window would admit work again, or `undefined` when none is
|
|
77
80
|
* exhausted as of `now`. Reads the ledger; propagates a store failure (an unreadable ceiling must not
|
|
78
81
|
* read as an open one). */
|
|
79
82
|
check(now: number): Promise<number | undefined>;
|
|
80
|
-
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Charge whatever of the run's cumulative spend has not been charged yet. Takes the run's CUMULATIVE
|
|
85
|
+
* totals rather than deltas so no caller can double-charge by calling twice, and so a caller that skips
|
|
86
|
+
* a boundary loses nothing.
|
|
87
|
+
*
|
|
88
|
+
* `cumulativeCostMicroUsd` is the MONEY half (integer micro-USD, `stats.costMicroUsd`). Pass `undefined`
|
|
89
|
+
* when the run's spend has no cost figure at all (RB-368's unpriced state) — a deployment governing a
|
|
90
|
+
* `maxCostUsd` window then REFUSES here rather than charging the fabricated 0 that would let the ceiling
|
|
91
|
+
* silently stop applying. A token-only deployment ignores the argument entirely.
|
|
92
|
+
*/
|
|
93
|
+
commit(cumulativeTokens: number, cumulativeCostMicroUsd: number | undefined, now: number): Promise<void>;
|
|
84
94
|
}
|
|
85
95
|
/**
|
|
86
96
|
* design/164 件四 — resolve the moment an env's lifetime EXPIRES (epoch ms), or `undefined` when the env
|
|
@@ -133,7 +143,7 @@ export declare function checkpointScopeOf(spec: {
|
|
|
133
143
|
principal?: string;
|
|
134
144
|
}): string;
|
|
135
145
|
export { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
136
|
-
export { isFableFamilyModelId, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
|
|
146
|
+
export { isFableFamilyModelId, resolveAttachmentsConfig, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
|
|
137
147
|
export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
|
|
138
148
|
export interface Prepared {
|
|
139
149
|
harness: AgentHarness;
|
|
@@ -1463,8 +1473,8 @@ export interface RunInternals {
|
|
|
1463
1473
|
* receive a subagent's live `task_progress` ticks (which otherwise stay in the child's ISOLATED stream). Threaded
|
|
1464
1474
|
* recursively down the delegation tree (via `ctx.forwardEvent`), so every nested subagent's ticks bubble to the
|
|
1465
1475
|
* SAME sink. The Runner's ctx wrapper forwards `task_progress` always; when the run's spec sets
|
|
1466
|
-
* `forwardSubagentEvents: true` it ALSO forwards the child's content events (`text_delta` / `
|
|
1467
|
-
* `tool_start` / `tool_end` — the subagent viewing pane, carrying the same UNTRUSTED-RAW/consumer-must-redact
|
|
1476
|
+
* `forwardSubagentEvents: true` it ALSO forwards the child's content events (`text_delta` / `text_end` (#447) /
|
|
1477
|
+
* `reasoning_delta` / `tool_start` / `tool_end` — the subagent viewing pane, carrying the same UNTRUSTED-RAW/consumer-must-redact
|
|
1468
1478
|
* contract as the main stream's tool events). Either way the child stream is NEVER merged into the parent's
|
|
1469
1479
|
* MODEL context (this is purely a render channel). Absent unless the deployment opted in.
|
|
1470
1480
|
*/
|
|
@@ -1629,6 +1639,35 @@ export interface ResolvedWorkspace {
|
|
|
1629
1639
|
* which `cwd` is a host path a consumer may diff / merge / remove. */
|
|
1630
1640
|
remote: boolean;
|
|
1631
1641
|
}
|
|
1642
|
+
/**
|
|
1643
|
+
* RB-330 + 5.38 r2 件2 — the SINGLE effective-delegation derivation for a leg, minted once per prepare
|
|
1644
|
+
* and read by EVERY consumer face; two facets, one source:
|
|
1645
|
+
*
|
|
1646
|
+
* - `isDelegatedChild` — the raw delegation fact, forks included. Trusted internals first; on a
|
|
1647
|
+
* resume leg where the caller supplied NO delegation fact, the checkpoint's persisted axis
|
|
1648
|
+
* ({@link import("../checkpoint-store.js").CheckpointState.isDelegatedChild}) stands in — else a
|
|
1649
|
+
* deps-only `resume(token, outcome, config)` of a parked delegation read `false` at every station:
|
|
1650
|
+
* persona flip (subagent consent/notes pack dropped), ask refusal texts on the parent-thread arm
|
|
1651
|
+
* ("wait for the user" in a transcript no user turn ever lands in), and a lifecycle-observer gate
|
|
1652
|
+
* that never opened. Consumed by `askSourceIdentity` (a fork's refusal posture is the child's) and
|
|
1653
|
+
* the `hookIdentity` mint.
|
|
1654
|
+
* - `isNonForkChild` — the fact minus forks, for the authority/context faces: the `loadProjectMemory`
|
|
1655
|
+
* ctx flag ({@link RunnerDeps.loadProjectMemory} `isSubagent`) and the prompt runtime fact
|
|
1656
|
+
* `isSubagent` (RB-204's `SUBAGENT_CONSENT_NOTICE` gate). A FORK is excluded on both, deliberately:
|
|
1657
|
+
* a fork IS the parent continuing (design/110), so it inherits the parent's authority — and, on the
|
|
1658
|
+
* memory face, the parent's own project context; trimming a fork's CLAUDE.md would hand the
|
|
1659
|
+
* continuation LESS context than the run it continues. (RB-330 history: the memory face once used
|
|
1660
|
+
* `parentTaskId !== undefined` alone, which {@link RunInternals.isDelegatedChild}'s own docstring
|
|
1661
|
+
* forbids — a directly-started workflow's children have no nameable parent.)
|
|
1662
|
+
*
|
|
1663
|
+
* `insideFork` is NOT persisted (the checkpoint's documented honest absence), so on a deps-only resume
|
|
1664
|
+
* a FORK child reads as a plain delegated child on the non-fork facet — the honest residue is written
|
|
1665
|
+
* on the checkpoint field's own list, not papered over with a new persistence axis.
|
|
1666
|
+
*/
|
|
1667
|
+
export declare function effectiveDelegationFacts(internals: Pick<RunInternals, "isDelegatedChild" | "insideFork"> | undefined, seedIsDelegatedChild: boolean | undefined): {
|
|
1668
|
+
isDelegatedChild: boolean;
|
|
1669
|
+
isNonForkChild: boolean;
|
|
1670
|
+
};
|
|
1632
1671
|
/**
|
|
1633
1672
|
* From the resumed/active transcript, the batch position of `currentId` (design/45 §4.ter): the tool-call
|
|
1634
1673
|
* ids of the assistant message that issued it (the batch), and the subset already resolved (executed
|