@sema-agent/core 5.37.0 → 5.38.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/agents/subagent.js +6 -0
- package/dist/agents/teacher.js +3 -0
- package/dist/agents/team.d.ts +7 -1
- package/dist/agents/team.js +11 -9
- package/dist/agents/verify.js +3 -0
- package/dist/core/auto-mode-prompt-assets.d.ts +5 -3
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/checkpoint-store.d.ts +26 -1
- package/dist/core/hooks.d.ts +129 -2
- package/dist/core/hooks.js +20 -3
- package/dist/core/runner/prepare-config-doors.d.ts +17 -0
- package/dist/core/runner/prepare-config-doors.js +33 -2
- package/dist/core/runner/prepare-task.d.ts +17 -2
- package/dist/core/runner/prepare-task.js +124 -41
- package/dist/core/runner/runtask.js +46 -11
- package/dist/core/tool-model-gate.d.ts +125 -0
- package/dist/core/tool-model-gate.js +303 -0
- package/dist/core/tool-policy.d.ts +1 -1
- package/dist/core/types.d.ts +189 -1
- package/dist/core/types.js +21 -0
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/orchestration/builtin-workflows.d.ts +68 -6
- package/dist/orchestration/builtin-workflows.js +26 -9
- package/dist/orchestration/run-workflow-tool.d.ts +10 -1
- package/dist/orchestration/run-workflow-tool.js +70 -27
- package/dist/orchestration/workflow-script-store.d.ts +8 -3
- package/dist/prompts/coordinator.d.ts +4 -1
- package/dist/prompts/coordinator.js +8 -0
- package/dist/prompts/default.d.ts +14 -4
- package/dist/prompts/default.js +2 -1
- package/dist/scenarios/full-body.d.ts +5 -0
- package/dist/scenarios/full-body.js +8 -4
- package/dist/tools/fs/fs-shared.d.ts +3 -2
- package/dist/tools/fs/fs-shared.js +19 -9
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +12 -1
|
@@ -1034,6 +1034,27 @@ export interface CheckpointState {
|
|
|
1034
1034
|
* with a recorder armed; ABSENT on an unarmed leg AND on an older checkpoint — the resume side reads
|
|
1035
1035
|
* absence-on-an-armed-child fail-closed as `incomplete`. */
|
|
1036
1036
|
delegationProvenance?: import("./memory-engine/delegation-provenance.js").DelegationProvenanceAggregate;
|
|
1037
|
+
/**
|
|
1038
|
+
* #281 件B follow-up — `true` iff the suspended leg ran as a DELEGATED CHILD (the trusted
|
|
1039
|
+
* `RunInternals.isDelegatedChild` fact, read off the leg's own minted
|
|
1040
|
+
* {@link import("./runner/prepare-task.js").Prepared.hookIdentity} so a re-suspend chain carries it
|
|
1041
|
+
* forward). Without it a deps-only resume (`resume(token, outcome, config)` — no trusted internals
|
|
1042
|
+
* re-supplied) re-minted the resumed leg's identity with `isDelegatedChild: false`, so the
|
|
1043
|
+
* delegation-lifecycle observer's emission gate stayed shut and the resumed leg emitted NEITHER
|
|
1044
|
+
* frame — a deployment watching only that seat saw a human-approved delegation frozen at its
|
|
1045
|
+
* park-leg `"suspended"` terminal forever. Live internals still win when re-supplied (the in-engine
|
|
1046
|
+
* resume lanes all do); the checkpoint key is the fallback for the deps-only leg. Only this ONE
|
|
1047
|
+
* axis persists — `insideFork`/`agentName`/`parentToolCallId` stay internals-only, honestly absent
|
|
1048
|
+
* on a deps-only resume rather than guessed; the accepted residue of the absent `insideFork` is
|
|
1049
|
+
* that a FORK child resumed deps-only reads as a plain delegated child on the non-fork consumer
|
|
1050
|
+
* facet (subagent persona/context trim) — documented here rather than fixed with a new
|
|
1051
|
+
* persistence axis. Schema ADDITION (additive, old readers ignore it —
|
|
1052
|
+
* the Q5 posture; no version movement): an older worker resuming a newer row keeps the pre-key
|
|
1053
|
+
* behavior, and an older row without the key resumes frame-less by documented honest absence
|
|
1054
|
+
* (see {@link import("./types.js").DelegationLifecycleEvent}). Absent = the leg was not a
|
|
1055
|
+
* delegated child (never `false` — presence-coded like the other monotonic bits here).
|
|
1056
|
+
*/
|
|
1057
|
+
isDelegatedChild?: true;
|
|
1037
1058
|
}
|
|
1038
1059
|
/**
|
|
1039
1060
|
* {@link CheckpointState} with **every** field made required-PRESENT, while each value keeps its original
|
|
@@ -2099,7 +2120,11 @@ export interface CheckpointStore {
|
|
|
2099
2120
|
* a real pin is the in-memory `TtlSessionStore` (single-instance / tests), where an un-released pin
|
|
2100
2121
|
* keeps an abandoned suspended session in the cache until process exit. v1 leaves the runner without an
|
|
2101
2122
|
* auto-reaper (the service owns the TTL worker); a reaper over the in-memory store should track and
|
|
2102
|
-
* `unpin` the expired sessions itself if that leak matters for its deployment.
|
|
2123
|
+
* `unpin` the expired sessions itself if that leak matters for its deployment. This no-unpin posture
|
|
2124
|
+
* is LOCAL to this live-path `reap`: the managed-retention contract's `expireCheckpoints`
|
|
2125
|
+
* ({@link import("./retention.js").ManagedRetentionCapability}) deliberately closes the reap/unpin
|
|
2126
|
+
* split — a retention-terminal row has its pin released BY that method, so pin release stays
|
|
2127
|
+
* exactly-once per row (each row is terminated by exactly one of the two lanes).
|
|
2103
2128
|
*/
|
|
2104
2129
|
reap(scope: string, cutoff: number): Promise<number>;
|
|
2105
2130
|
/**
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { DocumentContent, ImageContent, TextContent } from "../internal/llm
|
|
|
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";
|
|
5
|
+
import type { WiringLegKind } from "./wiring-manifest.js";
|
|
5
6
|
/**
|
|
6
7
|
* In-process hook seam (design/37) — a provider-agnostic interception layer modeled on CC's hooks,
|
|
7
8
|
* kept process-internal (no shell/HTTP executors, no settings files). The original tool-call trio:
|
|
@@ -67,7 +68,10 @@ export interface Hooks {
|
|
|
67
68
|
*/
|
|
68
69
|
preToolUseObservational?: true;
|
|
69
70
|
postToolUse?(toolName: string, input: unknown, output: HookToolOutput, ctx: HookToolContext): PostToolUseResult | undefined | Promise<PostToolUseResult | undefined>;
|
|
70
|
-
userPromptSubmit?(prompt: string
|
|
71
|
+
userPromptSubmit?(prompt: string,
|
|
72
|
+
/** #281 件A (additive): the run/leg identity envelope — an implementation declaring only
|
|
73
|
+
* `(prompt)` keeps working. Always supplied on the engine's emission. */
|
|
74
|
+
ctx?: UserPromptSubmitContext): UserPromptSubmitResult | undefined | Promise<UserPromptSubmitResult | undefined>;
|
|
71
75
|
/**
|
|
72
76
|
* roadmap #5 (CC 198 Stop hook, :473831-): runs when the agent WOULD OTHERWISE END its run (no
|
|
73
77
|
* more tool calls, steering and follow-up queues dry). Return `{ block: reason }` to PUSH BACK —
|
|
@@ -106,7 +110,11 @@ export interface Hooks {
|
|
|
106
110
|
*/
|
|
107
111
|
postToolBatch?(batch: PostToolBatchCall[], meta?: {
|
|
108
112
|
injectedThisTurn: "final_verification" | "finalize";
|
|
109
|
-
}
|
|
113
|
+
},
|
|
114
|
+
/** #281 件A (additive): the run/leg identity envelope, on its OWN parameter — `meta`'s presence
|
|
115
|
+
* is an existing signal ("the engine injected at this boundary") that must not become
|
|
116
|
+
* always-true just because identity rides along. Always supplied on the engine's emission. */
|
|
117
|
+
ctx?: PostToolBatchContext): PostToolBatchResult | undefined | Promise<PostToolBatchResult | undefined>;
|
|
110
118
|
/**
|
|
111
119
|
* design/134 (CC PreCompact parity): runs before each compaction, AFTER the trigger gate and a
|
|
112
120
|
* valid cut point are confirmed (so every preCompact corresponds to a compaction that would
|
|
@@ -177,6 +185,10 @@ export interface Hooks {
|
|
|
177
185
|
export type PermissionDeniedSource = "policy" | "hook" | "safety" | "shellGate" | "planMode" | "classifier" | "org";
|
|
178
186
|
/** The payload a {@link Hooks.permissionDenied} callback observes (CC-exact fields + `source`). */
|
|
179
187
|
export interface PermissionDeniedPayload {
|
|
188
|
+
/** #281 件A — the run/leg identity envelope. Present on every ENGINE emission (the gate's single
|
|
189
|
+
* deny exit, the hook-crash intercept, the plan-mode and compliance denies); absent only when a
|
|
190
|
+
* host drives {@link runToolGate} without `ToolGateInput.identity`. */
|
|
191
|
+
identity?: HookInvocationIdentity;
|
|
180
192
|
toolName: string;
|
|
181
193
|
/** The FINAL (post-hook-rewrite / post-policy-rewrite) args the chain adjudicated — what would have
|
|
182
194
|
* executed; not necessarily the model's original args. */
|
|
@@ -198,6 +210,9 @@ export interface PermissionDeniedPayload {
|
|
|
198
210
|
export declare function cloneObserverInput(input: unknown): unknown;
|
|
199
211
|
/** Context for {@link Hooks.stopFailure} — aligned with the TaskResult error face (observe-only). */
|
|
200
212
|
export interface StopFailureContext {
|
|
213
|
+
/** #281 件A — the run/leg identity envelope (always present on the engine's emission; the seat is
|
|
214
|
+
* optional only because the envelope's whole contract is). See {@link HookInvocationIdentity}. */
|
|
215
|
+
identity?: HookInvocationIdentity;
|
|
201
216
|
/** Human-readable error message (the assembled `errorMessage`; any `[code]` prefix already stripped). */
|
|
202
217
|
error: string;
|
|
203
218
|
/** Machine-readable kind — the assembled `errorCode` lifted from the brain's `[code]` prefix
|
|
@@ -242,6 +257,10 @@ export interface PostToolBatchResult {
|
|
|
242
257
|
}
|
|
243
258
|
/** Context for {@link Hooks.preCompact} (design/134). */
|
|
244
259
|
export interface PreCompactContext {
|
|
260
|
+
/** #281 件A — the run/leg identity envelope. Present when the Runner drives the compaction (its
|
|
261
|
+
* wrapper injects it); honestly absent when a deployment drives the compaction module directly
|
|
262
|
+
* (presence law arm 3 on {@link HookInvocationIdentity}). */
|
|
263
|
+
identity?: HookInvocationIdentity;
|
|
245
264
|
/** "auto" = threshold-triggered; "manual" = /compact; "forced" = promptTooLong recovery or
|
|
246
265
|
* trim-pressure propagation (block is ignored on forced — the compaction is not optional). */
|
|
247
266
|
trigger: "auto" | "manual" | "forced";
|
|
@@ -260,6 +279,8 @@ export interface PreCompactResult {
|
|
|
260
279
|
}
|
|
261
280
|
/** Context for {@link Hooks.postCompact} (design/134, observe-only). */
|
|
262
281
|
export interface PostCompactContext {
|
|
282
|
+
/** #281 件A — same presence law as {@link PreCompactContext.identity}. */
|
|
283
|
+
identity?: HookInvocationIdentity;
|
|
263
284
|
trigger: "auto" | "manual" | "forced";
|
|
264
285
|
/** The conversation summary the compaction produced (CC `compact_summary` parity). */
|
|
265
286
|
summary: string;
|
|
@@ -268,6 +289,11 @@ export interface PostCompactContext {
|
|
|
268
289
|
}
|
|
269
290
|
/** Context for the {@link Hooks.stop} hook (CC `stop_hook_active` parity). */
|
|
270
291
|
export interface StopHookContext {
|
|
292
|
+
/** #281 件A — the run/leg identity envelope. The load-bearing member for THIS seat: a process-level
|
|
293
|
+
* `deps.hooks.stop` fires in root, delegated-child and resume legs alike, and previously could not
|
|
294
|
+
* tell which run it was being asked to end (matrix row 10/13 residual). Always present on the
|
|
295
|
+
* engine's stop-gate emission. */
|
|
296
|
+
identity?: HookInvocationIdentity;
|
|
271
297
|
/** True when this run is already continuing because a previous stop() blocked — check it and
|
|
272
298
|
* return success (undefined) once your condition can't be improved, or you will loop to the cap. */
|
|
273
299
|
stopHookActive: boolean;
|
|
@@ -394,10 +420,94 @@ export interface HookEnvCapabilities {
|
|
|
394
420
|
* {@link createCwdReader} carries the one live read under the same rules.
|
|
395
421
|
*/
|
|
396
422
|
export declare function createHookEnvCapabilities(env: ExecutionEnv): HookEnvCapabilities;
|
|
423
|
+
/**
|
|
424
|
+
* #281 件A — the UNIFIED IDENTITY ENVELOPE every hook invocation can carry (`ctx.identity`): which
|
|
425
|
+
* run, which leg, and where in the delegation tree the hook is firing. The CC analog is
|
|
426
|
+
* `createBaseHookInput`'s per-event base fields; the members here are sema's OWN identity facts
|
|
427
|
+
* (session/leg/delegation axes), not a transcription of CC's field names.
|
|
428
|
+
*
|
|
429
|
+
* WHY: `deps.hooks` is PROCESS-level — one `stop`/`preToolUse`/`permissionDenied` seat serves the
|
|
430
|
+
* root run, every delegated child and every resume leg at once, and before this envelope none of
|
|
431
|
+
* those invocations could say which leg they belonged to. Additive: every seat is
|
|
432
|
+
* `identity?: HookInvocationIdentity`, so a consumer that never reads it sees nothing new.
|
|
433
|
+
*
|
|
434
|
+
* PRESENCE LAW (which invocations carry it — stated here once, referenced per seat):
|
|
435
|
+
* · PRESENT on every ENGINE-driven invocation of every hook seat: the tool gate's PreToolUse
|
|
436
|
+
* screenings (phase 1 + the approval-edit re-screen), postToolUse / postToolUseFailure,
|
|
437
|
+
* postToolBatch, userPromptSubmit, stop, stopFailure, preCompact / postCompact (the Runner's
|
|
438
|
+
* wrapper injects it), and every engine `permissionDenied` emission (gate deny exit, hook-crash
|
|
439
|
+
* intercept, plan-mode deny, compliance deny). The engine mints ONE frozen envelope per prepared
|
|
440
|
+
* leg ({@link mintHookInvocationIdentity} — single mint home) and every station reuses it.
|
|
441
|
+
* · HONESTLY ABSENT (no fabrication) at exactly four places, each structurally unable to know:
|
|
442
|
+
* 1. a HOST driving the exported {@link runToolGate} directly without supplying
|
|
443
|
+
* `ToolGateInput.identity` — the engine cannot know a leg it is not running;
|
|
444
|
+
* 2. an inherited PreToolUse screening face riding a DESCENDANT's constraint fold
|
|
445
|
+
* ({@link createPreToolUseConstraintPolicy}) — the fold consumes it as a `ToolPolicy`, whose
|
|
446
|
+
* request shape carries no leg identity, and stamping the INSTALL-site leg would name the
|
|
447
|
+
* wrong run;
|
|
448
|
+
* 3. a deployment driving the compaction module directly with its own `preCompact`/`postCompact`
|
|
449
|
+
* options — those contexts are built below the runner, which is where the envelope lives;
|
|
450
|
+
* 4. the PRE-CAS durable-resume observation of a declared-observational PreToolUse face
|
|
451
|
+
* (resumeStream's edited-args leg) — it runs BEFORE the resume leg prepares, so the leg's
|
|
452
|
+
* envelope does not exist yet, and minting a partial one off the checkpoint would guess at
|
|
453
|
+
* the delegation axes (the row persists `isDelegatedChild` alone; `insideFork`/`agentName`/
|
|
454
|
+
* `parentToolCallId` are internals-only, and a wrong guess is worse than an honest
|
|
455
|
+
* absence). The SAME face is re-consulted with full identity by the resumed leg's own gate.
|
|
456
|
+
*
|
|
457
|
+
* CONSUMER NOTE — the minted object is FROZEN and carries a NULL PROTOTYPE (same rule and same
|
|
458
|
+
* trade as {@link HookEnvCapabilities}: absence must be a fact about this object, not about what
|
|
459
|
+
* `Object.prototype` happens to hold). Read members directly (`identity.agentName`), test presence
|
|
460
|
+
* with `in` or `!== undefined`; do NOT call inherited methods on it — `identity.hasOwnProperty(...)`
|
|
461
|
+
* or implicit string coercion would throw, exactly as on the env capability face.
|
|
462
|
+
*/
|
|
463
|
+
export interface HookInvocationIdentity {
|
|
464
|
+
/** The run's session id (uuid domain) — the same key `TaskResult.sessionId` reports. */
|
|
465
|
+
readonly sessionId: string;
|
|
466
|
+
/** The run's unified task id (`spec.taskId ?? sessionId` — the value TaskEvent's `sourceTaskId`
|
|
467
|
+
* and `task_progress` frames key on). Equal to {@link sessionId} when no task id was declared. */
|
|
468
|
+
readonly taskId: string;
|
|
469
|
+
/** Which prepared leg this invocation belongs to — the wiring manifest's own `leg.kind`
|
|
470
|
+
* derivation, verbatim ({@link WiringLegKind}: `"root" | "child" | "resume"`). NOTE the fork
|
|
471
|
+
* lane's legs read `"child"` here like any other delegation; {@link insideFork} is the
|
|
472
|
+
* fork-lane discriminator. */
|
|
473
|
+
readonly legKind: WiringLegKind;
|
|
474
|
+
/** RB-204 delegation fact, verbatim: `true` iff this leg runs as a delegated child (every core
|
|
475
|
+
* spawn lane — sync/steer/background/fork/workflow — sets it; a top-level run reads `false`). */
|
|
476
|
+
readonly isDelegatedChild: boolean;
|
|
477
|
+
/** Present (`true`) iff this leg IS a forked child (`Agent(subagent_type:"fork")` — the
|
|
478
|
+
* design/110 trusted internals fact). Absent everywhere else. */
|
|
479
|
+
readonly insideFork?: true;
|
|
480
|
+
/** The delegated child's display name, when one was threaded at spawn (taskName / agent-type).
|
|
481
|
+
* UNTRUSTED display text (model-chosen) — already single-line-bounded at mint (same
|
|
482
|
+
* `inlineUntrusted` treatment as the `task_progress` name lane); never an identity key. */
|
|
483
|
+
readonly agentName?: string;
|
|
484
|
+
/** The spawning Agent tool call's own id, when this leg runs under one — the delegation-chain
|
|
485
|
+
* position anchor ({@link import("./types.js").TaskEvent}'s `parentToolCallId` twin). Absent on
|
|
486
|
+
* a top-level run and on a directly-started workflow child (no launching tool call — no id is
|
|
487
|
+
* fabricated). */
|
|
488
|
+
readonly parentToolCallId?: string;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* #281 件A — the ONE construction home of {@link HookInvocationIdentity} (domain-lexicon: one mint
|
|
492
|
+
* site; the runner calls it once per prepared leg and every hook station reuses the same object).
|
|
493
|
+
* Frozen AND null-prototype: the envelope is handed to arbitrarily many deployment callbacks, and a
|
|
494
|
+
* hook mutating `ctx.identity` must not rewrite what a later hook (or the delegation-lifecycle
|
|
495
|
+
* observer) reads. The null prototype is the {@link createHookEnvCapabilities} rule applied to the
|
|
496
|
+
* other absence-signaling face this module mints — "absent member" must be a fact about THIS object,
|
|
497
|
+
* and a frozen object with an ordinary prototype still answers `insideFork`/`agentName` reads (and
|
|
498
|
+
* `in` probes) from `Object.prototype`, so a prototype write elsewhere in the process could hand
|
|
499
|
+
* every leg a delegation axis the mint never stamped (codex r1). `agentName` is sanitized here — it
|
|
500
|
+
* is the only member whose value a model influences.
|
|
501
|
+
*/
|
|
502
|
+
export declare function mintHookInvocationIdentity(facts: HookInvocationIdentity): HookInvocationIdentity;
|
|
397
503
|
/** Identifying context passed to tool hooks. */
|
|
398
504
|
export interface HookToolContext {
|
|
399
505
|
toolCallId: string;
|
|
400
506
|
toolName: string;
|
|
507
|
+
/** #281 件A — the run/leg identity envelope; see {@link HookInvocationIdentity} for the presence
|
|
508
|
+
* law (present on engine-driven gates; absent on a host-driven gate without
|
|
509
|
+
* `ToolGateInput.identity` and on the delegation-fold twin). */
|
|
510
|
+
identity?: HookInvocationIdentity;
|
|
401
511
|
/**
|
|
402
512
|
* The read-only path-resolution face over the env the hands run against (see
|
|
403
513
|
* {@link HookEnvCapabilities} for the why, and for why it is capabilities rather than the env object).
|
|
@@ -450,6 +560,18 @@ export interface PostToolUseResult {
|
|
|
450
560
|
/** Appended to the result as a `<system-reminder>` the model can read. */
|
|
451
561
|
additionalContext?: string;
|
|
452
562
|
}
|
|
563
|
+
/** #281 件A — the context of a {@link Hooks.userPromptSubmit} invocation (second parameter,
|
|
564
|
+
* additive). Carries only the identity envelope today; an object so later additions stay additive. */
|
|
565
|
+
export interface UserPromptSubmitContext {
|
|
566
|
+
/** The run/leg identity envelope — see {@link HookInvocationIdentity} for the presence law. */
|
|
567
|
+
identity?: HookInvocationIdentity;
|
|
568
|
+
}
|
|
569
|
+
/** #281 件A — the context of a {@link Hooks.postToolBatch} invocation (third parameter, additive —
|
|
570
|
+
* deliberately NOT folded into `meta`, whose presence already means "the engine injected here"). */
|
|
571
|
+
export interface PostToolBatchContext {
|
|
572
|
+
/** The run/leg identity envelope — see {@link HookInvocationIdentity} for the presence law. */
|
|
573
|
+
identity?: HookInvocationIdentity;
|
|
574
|
+
}
|
|
453
575
|
/** A UserPromptSubmit hook result: block the submission, or inject context ahead of the prompt. */
|
|
454
576
|
export interface UserPromptSubmitResult {
|
|
455
577
|
/** Block submission entirely; the task fails with this model-readable reason. */
|
|
@@ -672,6 +794,11 @@ export interface ToolGateInput {
|
|
|
672
794
|
toolName: string;
|
|
673
795
|
input: Record<string, unknown>;
|
|
674
796
|
};
|
|
797
|
+
/** #281 件A — the run/leg identity envelope, put verbatim on every {@link HookToolContext} this
|
|
798
|
+
* gate call builds and on every {@link PermissionDeniedPayload} it emits. The Runner supplies its
|
|
799
|
+
* per-leg mint; a host driving the gate directly may omit it, and the contexts then carry no
|
|
800
|
+
* identity (honest absence — this layer never fabricates a leg). */
|
|
801
|
+
identity?: HookInvocationIdentity;
|
|
675
802
|
preToolUse?: Hooks["preToolUse"];
|
|
676
803
|
/** The read-only env capability face put on every {@link HookToolContext} this gate call
|
|
677
804
|
* builds ({@link HookEnvCapabilities}). Built ONCE per task by the runner (after the env is minted) and
|
package/dist/core/hooks.js
CHANGED
|
@@ -78,6 +78,18 @@ export function createHookEnvCapabilities(env) {
|
|
|
78
78
|
}
|
|
79
79
|
return Object.freeze(face);
|
|
80
80
|
}
|
|
81
|
+
const IDENTITY_AGENT_NAME_MAX = 80;
|
|
82
|
+
export function mintHookInvocationIdentity(facts) {
|
|
83
|
+
return Object.freeze(Object.assign(Object.create(null), {
|
|
84
|
+
sessionId: facts.sessionId,
|
|
85
|
+
taskId: facts.taskId,
|
|
86
|
+
legKind: facts.legKind,
|
|
87
|
+
isDelegatedChild: facts.isDelegatedChild,
|
|
88
|
+
...(facts.insideFork === true ? { insideFork: true } : {}),
|
|
89
|
+
...(facts.agentName !== undefined ? { agentName: inlineUntrusted(facts.agentName.slice(0, 320), IDENTITY_AGENT_NAME_MAX) } : {}),
|
|
90
|
+
...(facts.parentToolCallId !== undefined ? { parentToolCallId: facts.parentToolCallId } : {}),
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
81
93
|
export function formatHookFeedback(text) {
|
|
82
94
|
return `<system-reminder>\n${text}\n</system-reminder>`;
|
|
83
95
|
}
|
|
@@ -270,7 +282,12 @@ export function persistedRuleMandateOf(marks) {
|
|
|
270
282
|
export async function runToolGate(input) {
|
|
271
283
|
const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
|
|
272
284
|
const { toolCallId, toolName } = event;
|
|
273
|
-
const hookCtx = () => ({
|
|
285
|
+
const hookCtx = () => ({
|
|
286
|
+
toolCallId,
|
|
287
|
+
toolName,
|
|
288
|
+
...(input.hookEnv !== undefined ? { env: input.hookEnv } : {}),
|
|
289
|
+
...(input.identity !== undefined ? { identity: input.identity } : {}),
|
|
290
|
+
});
|
|
274
291
|
let currentInput = event.input;
|
|
275
292
|
const preToolContext = [];
|
|
276
293
|
let hookAsk;
|
|
@@ -285,7 +302,7 @@ export async function runToolGate(input) {
|
|
|
285
302
|
const reason = preToolUseCrashReason(`this call to "${toolName}"`, err);
|
|
286
303
|
traceHookCrash(input, err, notifier);
|
|
287
304
|
if (input.permissionDenied) {
|
|
288
|
-
await notifier.notifyAsync(() => input.permissionDenied?.({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, source: "hook" }), "toolGate.permissionDenied");
|
|
305
|
+
await notifier.notifyAsync(() => input.permissionDenied?.({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, source: "hook", ...(input.identity !== undefined ? { identity: input.identity } : {}) }), "toolGate.permissionDenied");
|
|
289
306
|
}
|
|
290
307
|
return { block: true, reason: formatHookFeedback(reason), preToolContext };
|
|
291
308
|
}
|
|
@@ -741,7 +758,7 @@ export async function runToolGate(input) {
|
|
|
741
758
|
currentInput = decision.updatedInput;
|
|
742
759
|
}
|
|
743
760
|
if (input.permissionDenied) {
|
|
744
|
-
await notifier.notifyAsync(() => input.permissionDenied?.({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason: denyReason, source: denySource }), "toolGate.permissionDenied");
|
|
761
|
+
await notifier.notifyAsync(() => input.permissionDenied?.({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason: denyReason, source: denySource, ...(input.identity !== undefined ? { identity: input.identity } : {}) }), "toolGate.permissionDenied");
|
|
745
762
|
}
|
|
746
763
|
const denySettledBy = decision.settledBy;
|
|
747
764
|
const denyApprover = denySettledBy !== undefined ? resolvedApprover : undefined;
|
|
@@ -107,6 +107,10 @@ export interface PrepareConfigDoorsResult {
|
|
|
107
107
|
exclude: readonly string[] | undefined;
|
|
108
108
|
defer: readonly string[] | undefined;
|
|
109
109
|
alwaysLoad: readonly string[] | undefined;
|
|
110
|
+
/** design/277 — the model-gate restore selector ({@link TaskSpec.restoreGatedTools}), fourth
|
|
111
|
+
* seat of the same frozen task-start snapshot: the gate decision and the delegation carrier
|
|
112
|
+
* read THIS, never the live spec. */
|
|
113
|
+
restoreGated: readonly string[] | true | undefined;
|
|
110
114
|
};
|
|
111
115
|
/** owned — the profile half of the RB-50 single decision point (model-independent by contract). */
|
|
112
116
|
promptProfile: "simple" | "classic";
|
|
@@ -127,6 +131,19 @@ export interface PrepareConfigDoorsResult {
|
|
|
127
131
|
compModel: Model | undefined;
|
|
128
132
|
/** owned — the mitigations half of the RB-50 decision point (model-family fact). */
|
|
129
133
|
fableMitigations: boolean;
|
|
134
|
+
/** owned, frozen — design/277 model-gate DISCLOSURE data (the application itself already
|
|
135
|
+
* happened on {@link spec}: its `tools` is the survivor rebind when anything was removed).
|
|
136
|
+
* prepareTask emits the notices from this after the doors return (the onNotice station);
|
|
137
|
+
* the doors stay side-effect-free on the announcement axis. */
|
|
138
|
+
modelGate: {
|
|
139
|
+
/** gate-class → removed wire names (sorted, unique); empty map = nothing removed. */
|
|
140
|
+
removedByClass: ReadonlyMap<string, readonly string[]>;
|
|
141
|
+
/** stamped classes with no merged-table row (inert tags, fail-open — announce material). */
|
|
142
|
+
unknownClasses: readonly string[];
|
|
143
|
+
/** a not-in-force `SEMA_TOOL_MODEL_GATE` value outside the closed set (discard-announce
|
|
144
|
+
* material; the in-force arm never lands here — it throws at the door). */
|
|
145
|
+
discardedEnvRaw: string | undefined;
|
|
146
|
+
};
|
|
130
147
|
/** owned — validated deployment governance windows (undefined = ungoverned). */
|
|
131
148
|
usageWindows: readonly UsageWindow[] | undefined;
|
|
132
149
|
/** owned, out-param cell — created EMPTY here; the brain-call wiring later installs into
|
|
@@ -4,6 +4,7 @@ import { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
|
4
4
|
import { preflightLockedConfig } from "../locked-config.js";
|
|
5
5
|
import { assertRetentionCapability } from "../retention.js";
|
|
6
6
|
import { resolveModel, resolveTaskModel, roleModelIfSet } from "../roles.js";
|
|
7
|
+
import { applyToolModelGate, assertRestoreGatedToolsValue, modelIdTail } from "../tool-model-gate.js";
|
|
7
8
|
import { resolveUsageWindows } from "../usage-window-store.js";
|
|
8
9
|
import { deriveAskEffective, resolveAskSeamForm, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
9
10
|
const TASK_LIMIT_KEY_DICT = {
|
|
@@ -73,7 +74,7 @@ export function resolveTaskLimits(limits) {
|
|
|
73
74
|
return limits;
|
|
74
75
|
}
|
|
75
76
|
export function isFableFamilyModelId(id) {
|
|
76
|
-
const tail = id
|
|
77
|
+
const tail = modelIdTail(id);
|
|
77
78
|
return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
|
|
78
79
|
}
|
|
79
80
|
export function resolveModelPromptTraits(model, spec, internals) {
|
|
@@ -85,10 +86,16 @@ export function resolveModelPromptTraits(model, spec, internals) {
|
|
|
85
86
|
export function prepareConfigDoors(input) {
|
|
86
87
|
const { deps, sessions, resume, internals } = input;
|
|
87
88
|
let spec = input.spec;
|
|
89
|
+
assertRestoreGatedToolsValue(spec.restoreGatedTools);
|
|
88
90
|
const toolFaceSnapshot = {
|
|
89
91
|
exclude: spec.excludeTools ? Object.freeze([...spec.excludeTools]) : undefined,
|
|
90
92
|
defer: spec.deferTools ? Object.freeze([...spec.deferTools]) : undefined,
|
|
91
93
|
alwaysLoad: spec.alwaysLoadTools ? Object.freeze([...spec.alwaysLoadTools]) : undefined,
|
|
94
|
+
restoreGated: spec.restoreGatedTools === true
|
|
95
|
+
? true
|
|
96
|
+
: spec.restoreGatedTools !== undefined
|
|
97
|
+
? Object.freeze([...spec.restoreGatedTools])
|
|
98
|
+
: undefined,
|
|
92
99
|
};
|
|
93
100
|
const promptProfile = resolveModelPromptTraits({ id: "" }, spec, internals).promptProfile;
|
|
94
101
|
if (spec.resumeAt !== undefined) {
|
|
@@ -223,11 +230,34 @@ export function prepareConfigDoors(input) {
|
|
|
223
230
|
throw e;
|
|
224
231
|
}
|
|
225
232
|
const perTask = spec.agents;
|
|
226
|
-
spec = {
|
|
233
|
+
spec = {
|
|
234
|
+
...spec,
|
|
235
|
+
tools: pool.map((t) => {
|
|
236
|
+
if (typeof t.withAgents !== "function")
|
|
237
|
+
return t;
|
|
238
|
+
const rebuilt = t.withAgents(perTask);
|
|
239
|
+
return t.modelGate !== undefined && rebuilt.modelGate === undefined ? { ...rebuilt, modelGate: t.modelGate } : rebuilt;
|
|
240
|
+
}),
|
|
241
|
+
};
|
|
227
242
|
}
|
|
228
243
|
const resolvedRole = resolveTaskModel(spec, deps);
|
|
229
244
|
const model = resolvedRole.model;
|
|
230
245
|
const fableMitigations = resolveModelPromptTraits(model, spec, internals).fableMitigations;
|
|
246
|
+
const gateDecision = applyToolModelGate({
|
|
247
|
+
tools: spec.tools,
|
|
248
|
+
modelId: model.id,
|
|
249
|
+
depsSeat: deps.toolModelGate,
|
|
250
|
+
restoreGated: toolFaceSnapshot.restoreGated,
|
|
251
|
+
envRaw: process.env.SEMA_TOOL_MODEL_GATE,
|
|
252
|
+
});
|
|
253
|
+
if (gateDecision.survivors !== undefined) {
|
|
254
|
+
spec = { ...spec, tools: gateDecision.survivors };
|
|
255
|
+
}
|
|
256
|
+
const modelGate = Object.freeze({
|
|
257
|
+
removedByClass: gateDecision.removedByClass,
|
|
258
|
+
unknownClasses: gateDecision.unknownClasses,
|
|
259
|
+
discardedEnvRaw: gateDecision.discardedEnvRaw,
|
|
260
|
+
});
|
|
231
261
|
const thinking = spec.thinking ?? resolvedRole.thinking ?? model.defaultThinking;
|
|
232
262
|
const compModel = spec.compactionModel
|
|
233
263
|
? resolveModel(spec.compactionModel, deps.models)
|
|
@@ -243,6 +273,7 @@ export function prepareConfigDoors(input) {
|
|
|
243
273
|
thinking,
|
|
244
274
|
compModel,
|
|
245
275
|
fableMitigations,
|
|
276
|
+
modelGate,
|
|
246
277
|
usageWindows,
|
|
247
278
|
brainCallGuardrailRef,
|
|
248
279
|
brainCallGuardrailMs,
|
|
@@ -11,7 +11,7 @@ import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
|
11
11
|
import type { OnAsk, ToolCallRequest, ToolPolicy } from "../tool-policy.js";
|
|
12
12
|
import { type ActiveSkillFrame } from "./active-skill-scope.js";
|
|
13
13
|
import type { SessionPermissionRules } from "../session-policy-store.js";
|
|
14
|
-
import { type Hooks, type OrgGateVerdict } from "../hooks.js";
|
|
14
|
+
import { type Hooks, type HookInvocationIdentity, type OrgGateVerdict } from "../hooks.js";
|
|
15
15
|
import type { RecoveredOrphan } from "../session-reconcile.js";
|
|
16
16
|
import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js";
|
|
17
17
|
import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
|
|
@@ -29,8 +29,14 @@ import { type WiringManifest } from "../wiring-manifest.js";
|
|
|
29
29
|
import type { ActiveWorktreeSession, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
|
|
30
30
|
import type { NestedUsageAccum, RunnerDeps, TaskEvent, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
31
31
|
import type { RepairBundle } from "../../agents/repair-loop.js";
|
|
32
|
-
/** Test seam (mirrors `
|
|
32
|
+
/** Test seam (mirrors `__resetToolModelGateAnnouncements`): never called by production code.
|
|
33
|
+
* Re-arms BOTH arms (a WeakMap has no clear — it is re-minted). */
|
|
33
34
|
export declare function __resetMaterializeEnvAnnouncements(): void;
|
|
35
|
+
/** Test seam (mirrors `__resetMaterializeEnvAnnouncements`): never called by production code.
|
|
36
|
+
* Re-arms BOTH arms (a WeakMap has no clear — it is re-minted). Deliberately UNLIKE the read-face
|
|
37
|
+
* seam below (console latch only): tests here legitimately reuse ONE sink across prepares to pin
|
|
38
|
+
* the per-sink dedup itself, so the seam must be able to re-arm a still-referenced sink. */
|
|
39
|
+
export declare function __resetToolModelGateAnnouncements(): void;
|
|
34
40
|
/** Test seam (mirrors `__resetMalformedNoticeSeatAnnouncement`): never called by production code.
|
|
35
41
|
* Deliberately asymmetric — it resets only the console latch: the WeakSet arm needs no seam
|
|
36
42
|
* because a test resets it by minting a fresh sink function (identity IS the ledger key), while
|
|
@@ -279,6 +285,15 @@ export interface Prepared {
|
|
|
279
285
|
* model/tool interaction. Host/operator plane — never enters model context.
|
|
280
286
|
*/
|
|
281
287
|
wiringManifest: WiringManifest;
|
|
288
|
+
/**
|
|
289
|
+
* #281 件A — this leg's frozen identity envelope, minted ONCE in prepare beside the wiring
|
|
290
|
+
* manifest (same leg derivation, one mint — {@link mintHookInvocationIdentity}). Every hook
|
|
291
|
+
* station runtask drives (stop/stopFailure/userPromptSubmit/postToolBatch, the compaction
|
|
292
|
+
* wrapper) and the 件B delegation-lifecycle observer read THIS object; prepare's own stations
|
|
293
|
+
* (the tool gate, the post-tool contexts) close over the same const. Always present — a prepared
|
|
294
|
+
* leg always knows its identity.
|
|
295
|
+
*/
|
|
296
|
+
hookIdentity: HookInvocationIdentity;
|
|
282
297
|
promptManifest: {
|
|
283
298
|
constitution: "core" | "replaced" | "provider-assembled";
|
|
284
299
|
blocks: Array<{
|