@sema-agent/core 7.11.1 → 7.11.2

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +20 -8
  2. package/dist/core/runner/compaction-knobs.d.ts +45 -0
  3. package/dist/core/runner/compaction-knobs.js +3 -0
  4. package/dist/core/runner/contracts.d.ts +8 -1
  5. package/dist/core/runner/run-attachment-seats.d.ts +20 -0
  6. package/dist/core/runner/run-attachment-seats.js +187 -0
  7. package/dist/core/runner/run-brain-sinks.d.ts +29 -0
  8. package/dist/core/runner/run-brain-sinks.js +61 -0
  9. package/dist/core/runner/run-clock-and-content.d.ts +52 -0
  10. package/dist/core/runner/run-clock-and-content.js +26 -0
  11. package/dist/core/runner/run-compaction-machinery.d.ts +35 -0
  12. package/dist/core/runner/run-compaction-machinery.js +98 -0
  13. package/dist/core/runner/run-git-lane.d.ts +64 -0
  14. package/dist/core/runner/run-git-lane.js +102 -0
  15. package/dist/core/runner/run-identity-wiring.d.ts +96 -0
  16. package/dist/core/runner/run-identity-wiring.js +84 -0
  17. package/dist/core/runner/run-reasoning-seat.d.ts +27 -0
  18. package/dist/core/runner/run-reasoning-seat.js +48 -0
  19. package/dist/core/runner/run-recovery-lanes.d.ts +54 -0
  20. package/dist/core/runner/run-recovery-lanes.js +180 -0
  21. package/dist/core/runner/run-stop-and-final-verify.d.ts +32 -0
  22. package/dist/core/runner/run-stop-and-final-verify.js +159 -0
  23. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +38 -0
  24. package/dist/core/runner/run-telemetry-and-budget-seats.js +159 -0
  25. package/dist/core/runner/run-tool-mount-facts.d.ts +26 -0
  26. package/dist/core/runner/run-tool-mount-facts.js +58 -0
  27. package/dist/core/runner/run-turn-boundary.d.ts +0 -35
  28. package/dist/core/runner/run-turn-boundary.js +1 -3
  29. package/dist/core/runner/runtask.js +94 -1116
  30. package/dist/tools/fs/fs-bash.js +3 -2
  31. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## 7.11.1 — 2026-09-09
3
+ ## 7.11.2 — 2026-09-10
4
4
 
5
- ### Fixes — a read the DEPLOYMENT'S READ BOUNDARY demoted is a mandated ask; the read-only shell arm cannot clear it (B-057; security axis; @server 7.69.0 six cells @test @cli)
6
- - **The regression (7.11.0 #619).** `bashReversibilityProbe` minted `mandated` only for an operand OUTSIDE the roots; a deny-listed operand (`grep needle secrets/app.txt` under `readDenyPatterns:["**/secrets/**"]`) and a recursive walk under a wired deny judge (`grep -r needle sub`, `du`) came back as a bare `reversible:false`an "ordinary" classify-tier ask and the new read-only arm (a reader of command text, blind to paths) retired it and the command ran, with `decisionReason:"read_only"`. Fail-open; measured red first.
7
- - **The rule, ONE arm (`boundaryGate`):** a demotion the read boundary raised is structural — outside the roots, on the deny judge (new structured `CompoundReadonlyVerdict.readDenied`, stamped where the deny arm demotes), or a recursive walk under the deny judge and neither a stored allow rule nor the read-only arm may retire it (`probe_mandate`, the same word #502 minted). Rules do not grow: the mandate's trigger widens from "outside the roots" to "the boundary spoke". No wire change; `ReversibilityVerdict.mandated` unchanged in shape.
8
- - The same rule covers a recursion the classifier cannot bound even with NO deny pattern: `find . -name x && pwd` under `shellGate:"classify"` asks once again (the 7.11.0 reading that let the read-only arm clear it was the same fail-open, pinned in `test/backlog482-compound-readonly-e2e.test.ts`).
9
- - Observable: with `readFace:"roots"`, `shellGate:"classify"` and a deny pattern, those three commands ask ONCE (`origin:"shell_gate_tighten"`), no `permission.read_only_allowed` trace, and run only after the person answers; with no pattern (or one that matches nothing) the same readers are reversible at the fold and ask nothing. Pins: `test/backlog-b057-read-boundary-mandate.test.ts`.
5
+ ### Fixes — B-057 second half: the `run_in_background:true` arm of the shell probe now mints the read-boundary mandate too (test [6825]; security axis; @server @cli @test)
6
+ - 7.11.1 closed B-057 on the foreground path only: `bashReversibilityProbe` returned early for a backgrounded call with the out-of-root half of the mandate alone, so a backgrounded `cat`/`grep` of a deny-listed or recursively-walked operand carried no `mandated` and a STORED allow rule cleared it zero asks, the file's content read and spooled. The flag now decides only `reversible:false`; where the command reads is judged by the same `boundaryGate` as the foreground path (one verdict source, no new arm). No wire change.
7
+ - Observable: `run_in_background:true` + `readDenyPatterns:["**/secrets/**"]` + a stored EXACT allow rule for the command (`Bash(grep needle secrets/app.txt)` a `Bash(grep:*)` prefix rule does not admit the backgrounded shape by the rule lane's own reading) `grep needle secrets/app.txt` / `grep -r needle sub` ask ONCE (`origin:"shell_gate_tighten"`), no `permission.read_only_allowed` trace; the same rule with no deny pattern on the foreground shape zero asks. Pins: `test/backlog-b057-read-boundary-mandate.test.ts`.
10
8
 
11
- ### Fixesa `spec.tools` entry the ToolSpec arm cannot rebuild is refused by name with a stable code (#666 ③; test [6794] G4.b; @server @test)
12
- - The caller mount rebuilds a raw ToolSpec by spreading it, so an entry whose `name` / `execute` live on a prototype (a class instance, `Object.create(spec)`, `Object.create(product)`), a function carrying the brand, or a Proxy lying about its own keys mounted with `name: undefined` and failed on an unrelated roster read (`undefined.startsWith`). ONE predicate can the spec arm construct this entry (own enumerable string `name` + own function `execute`)? now refuses the whole family with terminal code **`config.tool_mount_denied`** (registered, terminal) and a remedy sentence shared with the rebind seal (plain-object ToolSpec / the product itself / the supported wrapper form via `stampDefineToolBrand`). The 7.11.0-era brand-inheritance arm (`inheritsDefineToolBrand`) is retired it closed only the branded subset. Pins: the four shapes through a real Runner + the own-property control.
9
+ ### Internaldesign/393 S5: `runLocked`'s seat lanes R1–R12 leave `runtask.ts` as eleven `run-*.ts` lanes, and its fourteen parameters become one Input (byte-invariant; zero behavior surface)
10
+ - **What moved.** `Runner.runLocked`'s R1–R12 the identity wiring (post-prepare bindings, `ident`, the manifest frame, the harness sinks, the bridges, the idle redelivery, the loop latch, `onReady`), the telemetry and budget seats (pricing / degrade / limits / budget writers, the run record), the attachment seats (the counters / attach groups and the resume re-derivations — the one async lane), the tool-mount facts (mount gates, the write-family resolver, the three task-start trace frames), the reasoning seat, the clock and content seats (the walltime window, the hard timer, `pushContent`, `emitCommitted`), the brain sinks, the compaction machinery (the accounted brain, the breaker, the window-safety builder), the stop gate + final-verification seat, the recovery lanes (the shared forced pass, the loop recovery chain, the guard chain's arm-B seat) and the git lane moved whole to `run-identity-wiring.ts`, `run-telemetry-and-budget-seats.ts`, `run-attachment-seats.ts`, `run-tool-mount-facts.ts`, `run-reasoning-seat.ts`, `run-clock-and-content.ts`, `run-brain-sinks.ts`, `run-compaction-machinery.ts`, `run-stop-and-final-verify.ts`, `run-recovery-lanes.ts`, `run-git-lane.ts` (layer 3, run lanes), each a factory behind ONE Input (`Run…Input` / `Run…Result`, 88 seats in all) that hands back the values the leg reads under the same names. The driver keeps R0 (the notification lane + prepare), the two assemblies (the harness handlers — R8 — and the turn-boundary call, each the S2 lane's call site; the design's 18-seat R11 is cut there, leaving the recovery lane at 10 seats) and the legs; it mints the run state (`createRunState`, a pure zero-value, ~50 lines earlier than before) and three views over its own `let`s (the notification lane's bindings, the held agent_end account, the reasoning resolution) so a lane writes the driver's variable, never a copy. `runLocked(spec, queue, …, entryTracer)` — fourteen positional parameters — is now `runLocked(input: RunLockedInput)` (driver-private; the seat types are the contracts.ts seats the stream lanes already spell). The three compaction knobs (`MAX_CONSECUTIVE_COMPACTION_FAILURES`, `COMPACTION_REGROWTH_FACTOR`, `COMPACTION_FREED_EPSILON`) sank from the turn-boundary lane to `compaction-knobs.ts` (layer 1): the recovery lane reads them too, and a lane may not name a sibling. `STOP_HOOK_BLOCK_CAP` moved with the stop gate.
11
+ - **Byte-invariant.** dist `runtask.js` differs from the previous release by import lines, the call-site object literal, the `RunLockedInput` destructure, the three views and the eleven lane calls (−1 117 / +≈85 lines, of which the run IIFE's `setResult` callback is re-indented under its key, not changed); `run-turn-boundary.js` by three constant declarations out and one import in; the eleven lane files and `compaction-knobs.js` are the only additions; no other runtime file changed; the export surface is unchanged. Every lane whose top-level await count is 0 (all but one) is a synchronous factory; the attachment-seats lane is `async` and is awaited where its session reads always sat — inside it every read sits on the tick it did, and the driver's continuation after it is ONE microtask later than the one-function body's (the 390 v2.0 ruling ① class the design named for R3; nothing between the lane's return and the driver's next read can write what that read reads). `runtask.ts` 6 594 → 4 587.
12
+ - **Doors that moved with it.** module-size ratchet (runtask and run-turn-boundary banked; eleven lane entries), `gate:phase-api` floor 771 → 859 and the run- row's residents pin, `gate:layering` `seatLanesNote` + `compaction-knobs` on layer 1, the run-lane residents pin, the rb466 / fail-open / retired-key per-file lists (+11 each), the reminder-literal rows (`fvMarked` / the bare final-verification probe → `run-attachment-seats.ts`), the nuia baseline regenerated, INTEGRATION-CORE's two driver-assembly anchors re-read (the rest stay #667's), the RunState writer note in contracts.ts. `@server @cli @test`: nothing to pick up — no wire key, no closed set, no export moved.
13
13
 
14
14
  ### Internal — design/393 S4: the TaskStream façade leaves `runtask.ts` as five `stream-*.ts` lanes (byte-invariant; zero behavior surface)
15
15
  - **What moved.** `Runner.runTaskStream`'s T3–T7 — the settle backstop (the `run.catch` handler: checkpoint reopen compensation, terminal resume-failure unpin/destroy, the `failed` terminal mint, the backstop `task.end`, the owed delegation terminal, the drained `done` push), the suspended-run reap (`reapSuspended`), and the eight verb closures (`steer`; `notify` / `optOutMemoryCapture` / `compact` / `detach` / `interrupt`; `halt` / `destroy`) — moved whole to `stream-settle-backstop.ts`, `stream-reap.ts`, `stream-steer-verb.ts`, `stream-lifecycle-verbs.ts`, `stream-halt-verbs.ts` (layer 3, run lanes), each a factory behind ONE Input (`Stream…Input` / `Stream…Result`, 43 seats in all) that hands back the closure(s) the stream object carries under the same names. The driver keeps the seats (T1), the run IIFE (T2) and the eleven-name stream object; it mints the lanes' live view (`TaskStreamLiveSeat` — getters over `resultValue` / `handle` / `reapHandle`, one setter for the backstop's mint) and threads the Runner's registries and live deps seat as borrowed seats. Six seat types (`LiveHandle`, `TaskIdRef`, `ManualCompactRef`, `NotifyRef`, `CaptureOptOutRef`, `TaskStreamLiveSeat`) went down to `contracts.ts` from the driver's inline annotations. `steerChain` and `destroyOnce` moved with the only verb that read each.
@@ -26,6 +26,18 @@
26
26
  - `@cli` 1.0.106: pick up this version with 7.11.0 — a thinking-locked classifier model is now asked for `low` inside a 2304-token cap instead of being sent an off it ignores (where the catalog maps `low` onto the wire, it thinks at that tier; where it does not, at its default — see the residual); a model that exhausts the cap still fails once per ask; nothing to render. `@test`: known cannot-off (a catalog entry `reasoning:true` on the openai wire with no `thinkingLevelMap.off`, or anthropic adaptive) ⇒ the FIRST classifier request carries `reasoning:"low"` and `max_tokens` 2304, one request; an unknown entry (no `reasoning`) answering empty `finish_reason:"length"` then `<block>no</block>` ⇒ two requests, the second `low` at 2304; a can-off entry (deepseek format / declared off spelling) ⇒ `off` at 256, one request. `@server`: zero surface.
27
27
  - Pins: `test/backlog661-classifier-cap-retry.test.ts` (the seat predicate — cannot-off adaptive and stock-openai first request `low` + 2304; can-off `off` + 256 ×3; the re-ask's second request `low`; cannot-off empty ⇒ one request); `test/backlog618-classifier-request-shape.test.ts` re-pinned (the no-cap seat is gone: cannot-off ⇒ `low` + 2304).
28
28
 
29
+ ## 7.11.1 — 2026-09-09
30
+
31
+ ### Fixes — a read the DEPLOYMENT'S READ BOUNDARY demoted is a mandated ask; the read-only shell arm cannot clear it (B-057; security axis; @server 7.69.0 six cells @test @cli)
32
+ - **The regression (7.11.0 #619).** `bashReversibilityProbe` minted `mandated` only for an operand OUTSIDE the roots; a deny-listed operand (`grep needle secrets/app.txt` under `readDenyPatterns:["**/secrets/**"]`) and a recursive walk under a wired deny judge (`grep -r needle sub`, `du`) came back as a bare `reversible:false` — an "ordinary" classify-tier ask — and the new read-only arm (a reader of command text, blind to paths) retired it and the command ran, with `decisionReason:"read_only"`. Fail-open; measured red first.
33
+ - **The rule, ONE arm (`boundaryGate`):** a demotion the read boundary raised is structural — outside the roots, on the deny judge (new structured `CompoundReadonlyVerdict.readDenied`, stamped where the deny arm demotes), or a recursive walk under the deny judge — and neither a stored allow rule nor the read-only arm may retire it (`probe_mandate`, the same word #502 minted). Rules do not grow: the mandate's trigger widens from "outside the roots" to "the boundary spoke". No wire change; `ReversibilityVerdict.mandated` unchanged in shape.
34
+ - The same rule covers a recursion the classifier cannot bound even with NO deny pattern: `find . -name x && pwd` under `shellGate:"classify"` asks once again (the 7.11.0 reading that let the read-only arm clear it was the same fail-open, pinned in `test/backlog482-compound-readonly-e2e.test.ts`).
35
+ - Observable: with `readFace:"roots"`, `shellGate:"classify"` and a deny pattern, those three commands ask ONCE (`origin:"shell_gate_tighten"`), no `permission.read_only_allowed` trace, and run only after the person answers; with no pattern (or one that matches nothing) the same readers are reversible at the fold and ask nothing. Pins: `test/backlog-b057-read-boundary-mandate.test.ts`.
36
+
37
+ ### Fixes — a `spec.tools` entry the ToolSpec arm cannot rebuild is refused by name with a stable code (#666 ③; test [6794] G4.b; @server @test)
38
+ - The caller mount rebuilds a raw ToolSpec by spreading it, so an entry whose `name` / `execute` live on a prototype (a class instance, `Object.create(spec)`, `Object.create(product)`), a function carrying the brand, or a Proxy lying about its own keys mounted with `name: undefined` and failed on an unrelated roster read (`undefined.startsWith`). ONE predicate — can the spec arm construct this entry (own enumerable string `name` + own function `execute`)? — now refuses the whole family with terminal code **`config.tool_mount_denied`** (registered, terminal) and a remedy sentence shared with the rebind seal (plain-object ToolSpec / the product itself / the supported wrapper form via `stampDefineToolBrand`). The 7.11.0-era brand-inheritance arm (`inheritsDefineToolBrand`) is retired — it closed only the branded subset. Pins: the four shapes through a real Runner + the own-property control.
39
+
40
+
29
41
  ## 7.11.0 — 2026-09-09
30
42
 
31
43
  ### Fixes — the auto-mode classifier reads its verdict the way CC 2.1.250 does, and re-asks once when a capped reply came back empty (#661 ① ⑤; B-056; @cli 1.0.106 @test @server)
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The run loop's three compaction knobs (design/393 S5): the consecutive-failure breaker trip, the anti-thrash
3
+ * regrowth factor and the effective-compaction epsilon. They were declared at the turn-boundary lane and the driver
4
+ * reached DOWN for them; the recovery lane (the forced-compaction pass the prompt-too-long and guard-chain arms
5
+ * share) reads the same three, and a lane may not name a sibling lane — so the three values sank to the machinery
6
+ * stratum, where the two lanes and the driver all reach down for them. Values verbatim, comments included.
7
+ *
8
+ * No `run-` family prefix, for the reason initial-run-state.ts gives: a module the lanes stand on is not a driven
9
+ * lane, and the prefix would put it under the phase-api gate and the run-lanes glob.
10
+ */
11
+ /**
12
+ * §17.4 (design/64 GAP-25): stop attempting within-task compaction after this many CONSECUTIVE
13
+ * failures in one task — a never-recovering summary gateway must not burn an API call at every turn
14
+ * boundary (CC: MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3). Reset on any success; an open breaker also
15
+ * skips the end-of-task attempt in `finish()` (teardown unaffected). Per-task scope.
16
+ *
17
+ * RB-190 — this used to say "a flaky/never-recovering gateway", and the word `flaky` was not true. Because
18
+ * any success zeroes the counter, a gateway that alternates never reaches three CONSECUTIVE failures and
19
+ * the breaker never opens; it keeps paying for a call at every failed boundary, indefinitely.
20
+ *
21
+ * The behaviour is right and stays: a gateway succeeding half the time is compacting half the time, and
22
+ * opening the breaker on it would disable compaction outright and let the context overflow — trading a
23
+ * wasted call for a failed task. What was wrong is the comment claiming a guarantee the code does not
24
+ * make. An alternating gateway is deliberately NOT caught here; if that ever needs catching it wants a
25
+ * different signal (a failure RATE over a window), not a consecutive counter.
26
+ */
27
+ export declare const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
28
+ /**
29
+ * §25.2 anti-thrash (LONGRUN-1b, search [84]): after a compaction, the context must regrow past
30
+ * (tokensBefore − freed) × this factor — the post-compaction size in trigger units — before another
31
+ * summary call fires. An EFFECTIVE compaction (freed large) leaves the floor far below the threshold,
32
+ * so behavior is unchanged; an INEFFECTIVE one (small window, chunky turns, large summary ⇒ freed
33
+ * small, still at/over the threshold) raises the floor so attempts space out instead of firing at
34
+ * every boundary (measured: 26/39 boundaries, +135% wall). The floor binds only when post-compaction
35
+ * size > threshold/1.5 ≈ compaction freed <33% headroom. Degrades gracefully: the retained summary
36
+ * chain keeps early facts; only the un-summarized tail falls back to trim.
37
+ */
38
+ export declare const COMPACTION_REGROWTH_FACTOR = 1.5;
39
+ /**
40
+ * design/84 Seam C reuse guard (b): a compaction that frees fewer than this many (structural) tokens is
41
+ * treated as NO-EFFECTIVE-COMPACTION and does NOT raise the anti-thrash floor. Guards against a reused
42
+ * provider summary (or a pathological LLM summary) that reclaims no headroom yet suppresses the next real
43
+ * summary by inflating the floor to trigger×factor.
44
+ */
45
+ export declare const COMPACTION_FREED_EPSILON = 256;
@@ -0,0 +1,3 @@
1
+ export const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
2
+ export const COMPACTION_REGROWTH_FACTOR = 1.5;
3
+ export const COMPACTION_FREED_EPSILON = 256;
@@ -2041,7 +2041,14 @@ export interface ParkAsk {
2041
2041
  * Grouped by lifecycle semantics per the B15 pre-recon partition. Function members start as inert
2042
2042
  * placeholders and are assigned at their ORIGINAL declaration sites (the original code could not
2043
2043
  * call them earlier either — TDZ proved it). Not on the npm face; this extraction is what enabled
2044
- * createTurnBoundary / createHarnessHandlers (三期, both in runtask.ts).
2044
+ * createTurnBoundary / createHarnessHandlers (三期; both are run lanes of their own since design/393 S2).
2045
+ *
2046
+ * WRITERS, by group (design/393 S5 — the driver mints the zero value, initial-run-state.ts, and hands it to every
2047
+ * lane as a borrowed-mutable seat): `telemetry` / `degrade` / `limits` / `budget` initial values and function
2048
+ * members — the telemetry-and-budget-seats lane; `counters` / `attach` initial values — the attachment-seats lane
2049
+ * (the clock lane zeroes `counters.walltimeSyncBackstopFired`; the tool-mount facts only read); the per-turn writes — the harness-handlers lane
2050
+ * (`turn`, `counters`, `limits`, `budget`), the turn-boundary lane (`counters`, `attach`, `budget`) and the
2051
+ * recovery lane (`counters.compactionFloor`, `attach.attachState`); the settle-time reads stay the driver's.
2045
2052
  */
2046
2053
  export interface RunState {
2047
2054
  telemetry: {
@@ -0,0 +1,20 @@
1
+ import type { TaskSpec } from "../types.js";
2
+ import type { Prepared, ResumeRun, RunnerDepsSeat, RunState } from "./contracts.js";
3
+ export interface RunAttachmentSeatsInput {
4
+ /** borrowed-readonly — the task spec: the attachments config and the `finalVerification` opt-in. */
5
+ spec: TaskSpec;
6
+ /** borrowed-mutable — the leg's prepared seat: `announcedListingsRef` is seeded; the session, the listing faces,
7
+ * the date-change / instruction-source / size-guideline faces, the tool effects and the reminder mark are read. */
8
+ prepared: Prepared;
9
+ /** borrowed-readonly — the resume plan when this leg resumes a checkpoint (its `announcedListings` mirror is the first seed rung). */
10
+ resume: ResumeRun | undefined;
11
+ /** borrowed-mutable — the run's mutable state: this lane is the WRITER of the `counters` and `attach` groups' initial
12
+ * values (later writers: the harness handlers, the boundary, the recovery pass). */
13
+ rs: RunState;
14
+ /** borrowed-readonly — the Runner's deployment deps, read once for `probeInstructionSources`. */
15
+ runner: RunnerDepsSeat;
16
+ }
17
+ /** Nothing comes back: the lane's products are the `counters` / `attach` groups it wrote on the borrowed run state. */
18
+ export interface RunAttachmentSeatsResult {
19
+ }
20
+ export declare function runAttachmentSeats(input: RunAttachmentSeatsInput): Promise<RunAttachmentSeatsResult>;
@@ -0,0 +1,187 @@
1
+ import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
2
+ import { stripGitStatusUnits } from "./git-status-frame.js";
3
+ import { SKILLS_LISTING_PROBE_HEADER } from "./synthetic-tools.js";
4
+ import { AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, agentListingDeltaHeader, agentListingInitialHeader, createAttachmentState, replayAnnouncedListing, replayAnnouncedModels } from "./turn-attachments.js";
5
+ export async function runAttachmentSeats(input) {
6
+ const { spec, prepared, resume, rs, runner } = input;
7
+ rs.counters.wroteThisRun = false;
8
+ rs.counters.finalVerifyInjections = 0;
9
+ rs.counters.groundingSignalPreR9 = false;
10
+ rs.counters.groundingSignalPostR9 = false;
11
+ rs.attach.attachmentsCfg = spec.attachments;
12
+ rs.attach.agentListingOn = rs.attach.attachmentsCfg?.agentListing !== false && eventDefaultOn("agent_listing");
13
+ rs.attach.skillsListingOn = rs.attach.attachmentsCfg?.skillsListing !== false && eventDefaultOn("skills_listing");
14
+ const listingsLive = (rs.attach.agentListingOn && prepared.agentListing !== undefined) || (rs.attach.skillsListingOn && prepared.skillsListing !== undefined);
15
+ const backgroundTasksLive = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
16
+ rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive ? createAttachmentState() : undefined;
17
+ rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.legDate } : undefined;
18
+ rs.attach.instrProbe = runner.deps.probeInstructionSources;
19
+ rs.attach.instrState =
20
+ rs.attach.instrProbe !== undefined && prepared.instructionSources !== undefined && prepared.instructionSources.length > 0
21
+ ? { lastAnnouncedHash: new Map(prepared.instructionSources.map((s) => [s.path, s.contentHash])) }
22
+ : undefined;
23
+ rs.attach.sizeGuidelineState =
24
+ prepared.workflowSizeGuideline !== undefined
25
+ ? { announcedGuideline: prepared.workflowSizeGuideline.legGuideline, current: prepared.workflowSizeGuideline.current }
26
+ : undefined;
27
+ rs.counters.cadenceTurns = 0;
28
+ rs.turn.lastTurnHadToolCalls = false;
29
+ if (rs.attach.attachState !== undefined && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
30
+ try {
31
+ const branch = await prepared.session.getBranch();
32
+ for (let i = branch.length - 1; i >= 0; i--) {
33
+ const e = branch[i];
34
+ if (e.type === "compaction") {
35
+ rs.attach.attachState.postCompactPending = true;
36
+ break;
37
+ }
38
+ if (e.type === "message" && e.message.role === "assistant")
39
+ break;
40
+ }
41
+ }
42
+ catch {
43
+ }
44
+ }
45
+ if (rs.attach.attachState !== undefined &&
46
+ ((rs.attach.agentListingOn && prepared.agentListing?.seedAnnounced === true) ||
47
+ (rs.attach.skillsListingOn && prepared.skillsListing?.seedAnnounced === true))) {
48
+ const cpListings = resume?.cp.state.announcedListings;
49
+ let entryListings;
50
+ try {
51
+ entryListings = await prepared.session.getAnnouncedListing();
52
+ }
53
+ catch {
54
+ entryListings = undefined;
55
+ }
56
+ const textOf = (m) => {
57
+ const c = m.content;
58
+ if (typeof c === "string")
59
+ return c;
60
+ if (Array.isArray(c)) {
61
+ return c
62
+ .map((b) => (b !== null && typeof b === "object" && b.type === "text" ? String(b.text ?? "") : ""))
63
+ .join("\n");
64
+ }
65
+ return "";
66
+ };
67
+ let branchTexts = null;
68
+ const branchLoad = async () => {
69
+ if (branchTexts === null) {
70
+ const branch = await prepared.session.getBranch();
71
+ branchTexts = branch.flatMap((e) => {
72
+ if (e.type !== "message")
73
+ return [];
74
+ const m = e.message;
75
+ if (m.role !== "user")
76
+ return [];
77
+ const full = textOf(e.message);
78
+ if (m.engineMinted === true)
79
+ return [stripGitStatusUnits(full, prepared.reminderMark)];
80
+ if (Array.isArray(m.engineSegments) && m.engineSegments.length > 0) {
81
+ return m.engineSegments.map((s) => stripGitStatusUnits(full.slice(Math.max(0, s.start), Math.max(0, s.end)), prepared.reminderMark));
82
+ }
83
+ if (typeof m.enginePrefixChars === "number" && m.enginePrefixChars > 0)
84
+ return [stripGitStatusUnits(full.slice(0, m.enginePrefixChars), prepared.reminderMark)];
85
+ return [];
86
+ });
87
+ }
88
+ return branchTexts;
89
+ };
90
+ const branchReplay = async (headers) => {
91
+ try {
92
+ return replayAnnouncedListing(await branchLoad(), headers);
93
+ }
94
+ catch {
95
+ return undefined;
96
+ }
97
+ };
98
+ if (rs.attach.agentListingOn && prepared.agentListing?.seedAnnounced === true) {
99
+ const listing = prepared.agentListing;
100
+ if (cpListings?.agents === undefined && entryListings?.agents !== undefined) {
101
+ const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
102
+ rs.attach.attachState.announcedAgentTypes = new Map(entryListings.agents.map((n) => [n, descOf.get(n) ?? ""]));
103
+ prepared.announcedListingsRef.agents = [...entryListings.agents];
104
+ if (entryListings.models !== undefined) {
105
+ rs.attach.attachState.announcedModels = [...entryListings.models];
106
+ prepared.announcedListingsRef.models = [...entryListings.models];
107
+ }
108
+ }
109
+ else if (cpListings?.agents !== undefined) {
110
+ const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
111
+ rs.attach.attachState.announcedAgentTypes = new Map(cpListings.agents.map((n) => [n, descOf.get(n) ?? ""]));
112
+ prepared.announcedListingsRef.agents = [...cpListings.agents];
113
+ if (cpListings.models !== undefined) {
114
+ rs.attach.attachState.announcedModels = [...cpListings.models];
115
+ prepared.announcedListingsRef.models = [...cpListings.models];
116
+ }
117
+ }
118
+ else {
119
+ const replayed = await branchReplay({
120
+ initial: agentListingInitialHeader(listing.toolName),
121
+ delta: agentListingDeltaHeader(listing.toolName),
122
+ removed: AGENT_LISTING_REMOVED_HEADER,
123
+ });
124
+ if (replayed !== undefined) {
125
+ const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
126
+ rs.attach.attachState.announcedAgentTypes = new Map([...replayed].map((n) => [n, descOf.get(n) ?? ""]));
127
+ prepared.announcedListingsRef.agents = [...replayed];
128
+ const replayedModels = replayAnnouncedModels(await branchLoad());
129
+ if (replayedModels !== undefined) {
130
+ rs.attach.attachState.announcedModels = [...replayedModels];
131
+ prepared.announcedListingsRef.models = [...replayedModels];
132
+ }
133
+ }
134
+ }
135
+ }
136
+ if (rs.attach.skillsListingOn && prepared.skillsListing?.seedAnnounced === true) {
137
+ const listing = prepared.skillsListing;
138
+ if (cpListings?.skills === undefined && entryListings?.skills !== undefined) {
139
+ const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
140
+ rs.attach.attachState.announcedSkills = new Map(entryListings.skills.map((n) => [n, descOf.get(n) ?? ""]));
141
+ prepared.announcedListingsRef.skills = [...entryListings.skills];
142
+ }
143
+ else if (cpListings?.skills !== undefined) {
144
+ const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
145
+ rs.attach.attachState.announcedSkills = new Map(cpListings.skills.map((n) => [n, descOf.get(n) ?? ""]));
146
+ prepared.announcedListingsRef.skills = [...cpListings.skills];
147
+ }
148
+ else {
149
+ const replayed = await branchReplay({
150
+ initial: SKILLS_LISTING_PROBE_HEADER,
151
+ delta: SKILLS_LISTING_DELTA_HEADER,
152
+ removed: SKILLS_LISTING_REMOVED_HEADER,
153
+ });
154
+ if (replayed !== undefined) {
155
+ const descOf = new Map(listing.entries.map((e) => [e.name, e.description]));
156
+ rs.attach.attachState.announcedSkills = new Map([...replayed].map((n) => [n, descOf.get(n) ?? ""]));
157
+ prepared.announcedListingsRef.skills = [...replayed];
158
+ }
159
+ }
160
+ }
161
+ }
162
+ if (spec.finalVerification === true && resume !== undefined) {
163
+ try {
164
+ const branch = await prepared.session.getBranch();
165
+ for (const e of branch) {
166
+ if (e.type !== "message")
167
+ continue;
168
+ const m = e.message;
169
+ if (m.role === "user") {
170
+ const text = typeof m.content === "string"
171
+ ? m.content
172
+ : m.content.map((b) => (b !== null && typeof b === "object" && b.type === "text" ? String(b.text ?? "") : "")).join("\n");
173
+ const fvMarked = m.engineMinted === true && text.includes(`<system-reminder mark="${prepared.reminderMark}">[final verification]`);
174
+ if ((fvMarked || text.includes("<system-reminder>[final verification]")) && rs.counters.finalVerifyInjections < 2)
175
+ rs.counters.finalVerifyInjections += 1;
176
+ }
177
+ else if (m.role === "toolResult" && !rs.counters.wroteThisRun && (prepared.toolEffects.get(m.toolName) ?? "write") !== "read") {
178
+ rs.counters.wroteThisRun = true;
179
+ }
180
+ }
181
+ }
182
+ catch {
183
+ }
184
+ }
185
+ rs.attach.attachmentsInjected = 0;
186
+ return {};
187
+ }
@@ -0,0 +1,29 @@
1
+ import type { ReasoningWireFacts } from "../../brain/reasoning.js";
2
+ import type { PushQueue } from "../push-queue.js";
3
+ import type { TaskEvent } from "../types.js";
4
+ import type { RunInternals, RunState } from "./contracts.js";
5
+ export interface RunBrainSinksInput {
6
+ /** borrowed-mutable — the run's event queue: the `status` frames. */
7
+ queue: PushQueue<TaskEvent>;
8
+ /** borrowed-readonly — the trusted run-scoped channel: `agentName` (the display name), `onStatusEvent` (the second sink). */
9
+ internals: RunInternals | undefined;
10
+ /** borrowed-readonly — the run's mutable state, read for the telemetry frames' tracer and task id. */
11
+ rs: RunState;
12
+ /** borrowed-readonly — the run's event-identity mint (stamped on every status frame). */
13
+ ident: () => {
14
+ eventId: string;
15
+ parentToolCallId?: string;
16
+ sourceTaskId?: string;
17
+ };
18
+ /** borrowed-readonly — the spawning tool call's id when this task runs AS A SUB-AGENT; absent on a top-level run. */
19
+ parentToolCallId: string | undefined;
20
+ /** borrowed-readonly — the reasoning lane's wire-facts consumer, the third sink the composition installs. */
21
+ observeReasoningWireFacts: (facts: ReasoningWireFacts) => void;
22
+ }
23
+ export interface RunBrainSinksResult {
24
+ /** design/99 BC-2: the subagent's human display name for its `task_progress` ticks, or undefined. */
25
+ subagentName: string | undefined;
26
+ /** Compose the three brain ALS sinks around a brain-driving call. */
27
+ withBrainSinks: <T>(fn: () => Promise<T>) => Promise<T>;
28
+ }
29
+ export declare function runBrainSinks(input: RunBrainSinksInput): RunBrainSinksResult;
@@ -0,0 +1,61 @@
1
+ import { runWithBrainTelemetry, runWithReasoningWireFacts, runWithStatusSink } from "../../brain/status-sink.js";
2
+ import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
3
+ import { emitTrace } from "../trace.js";
4
+ import { inlineUntrusted } from "../untrusted-text.js";
5
+ export function runBrainSinks(input) {
6
+ const { queue, internals, rs, ident, parentToolCallId, observeReasoningWireFacts } = input;
7
+ const subagentName = parentToolCallId !== undefined && internals?.agentName !== undefined ? inlineUntrusted(internals.agentName.slice(0, 320), 80) : undefined;
8
+ const statusSinkNotifier = createSafeNotifier({
9
+ onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals status sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
10
+ });
11
+ const statusEmit = (s) => {
12
+ const frame = {
13
+ type: "status",
14
+ phase: s.phase,
15
+ ...(s.detail !== undefined ? { detail: s.detail } : {}),
16
+ ...(s.retryInSec !== undefined ? { retryInSec: s.retryInSec } : {}),
17
+ ...(s.retryInMs !== undefined ? { retryInMs: s.retryInMs } : {}),
18
+ ...(s.retryAtMs !== undefined ? { retryAtMs: s.retryAtMs } : {}),
19
+ ...(s.elapsedMs !== undefined ? { elapsedMs: s.elapsedMs } : {}),
20
+ ...(s.timeoutMs !== undefined ? { timeoutMs: s.timeoutMs } : {}),
21
+ ...(s.attempt !== undefined ? { attempt: s.attempt } : {}),
22
+ ...(s.maxRetries !== undefined ? { maxRetries: s.maxRetries } : {}),
23
+ ...(s.errClass !== undefined ? { errClass: s.errClass } : {}),
24
+ ...(s.errorStatus !== undefined ? { errorStatus: s.errorStatus } : {}),
25
+ ...ident(),
26
+ };
27
+ Object.freeze(frame);
28
+ const accepted = queue.push(frame);
29
+ if (accepted && internals?.onStatusEvent !== undefined) {
30
+ statusSinkNotifier.notify(() => observeThenableRejection(internals.onStatusEvent?.(frame), statusSinkNotifier, "runtask.onStatusEvent"), "runtask.onStatusEvent");
31
+ }
32
+ };
33
+ const telemetryEmit = (t) => {
34
+ emitTrace(rs.telemetry.tracer, () => t.kind === "failover"
35
+ ? {
36
+ kind: "brain.failover",
37
+ version: 1,
38
+ taskId: rs.telemetry.taskId,
39
+ servedIndex: t.servedIndex,
40
+ total: t.total,
41
+ ...(t.errorCode !== undefined ? { errorCode: t.errorCode } : {}),
42
+ ts: Date.now(),
43
+ }
44
+ : t.kind === "breaker"
45
+ ? { kind: "breaker.transition", version: 1, taskId: rs.telemetry.taskId, key: t.key, phase: t.phase, failures: t.failures, ts: Date.now() }
46
+ : t.kind === "retry"
47
+ ? {
48
+ kind: "brain.retry",
49
+ version: 1,
50
+ taskId: rs.telemetry.taskId,
51
+ attempt: t.attempt,
52
+ phase: t.phase,
53
+ ...(t.errClass !== undefined ? { errClass: t.errClass } : {}),
54
+ ...(t.nextDelayMs !== undefined ? { nextDelayMs: t.nextDelayMs } : {}),
55
+ ts: Date.now(),
56
+ }
57
+ : { kind: "vision.placeholder", version: 1, taskId: rs.telemetry.taskId, count: t.count, ts: Date.now() });
58
+ };
59
+ const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, () => runWithReasoningWireFacts(observeReasoningWireFacts, fn)));
60
+ return { subagentName, withBrainSinks };
61
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * design/393 S5 — the run body's CLOCK and CONTENT seats (R6), verbatim from `Runner.runLocked`: the per-slice
3
+ * walltime window and its MONOTONIC deadline anchored at task start, the hard-abort timer armed against that same
4
+ * deadline (a suspendable task gets the backstop grace), the content-event push that also forwards a sub-agent's
5
+ * events to the parent's display sink, the run's own committed-tail ref and the `message_committed` mint that
6
+ * retires a settled tool call from the started set.
7
+ */
8
+ import type { PushQueue } from "../push-queue.js";
9
+ import type { TaskEvent, TaskSpec } from "../types.js";
10
+ import { startTimeout } from "./clock-and-limits.js";
11
+ import type { Prepared, RunInternals, RunState } from "./contracts.js";
12
+ export interface RunClockAndContentInput {
13
+ /** borrowed-readonly — the task spec: `limits.maxWalltimeMs`. */
14
+ spec: TaskSpec;
15
+ /** borrowed-readonly — the leg's prepared seat: the harness and abort controller the timer is armed against, the
16
+ * resource-suspend eligibility. */
17
+ prepared: Prepared;
18
+ /** borrowed-mutable — the run's event queue: content events and `message_committed` frames. */
19
+ queue: PushQueue<TaskEvent>;
20
+ /** borrowed-readonly — the trusted run-scoped channel: `onForwardEvent` (the parent's display sink). */
21
+ internals: RunInternals | undefined;
22
+ /** borrowed-mutable — the run's mutable state: `counters.walltimeSyncBackstopFired` is zeroed here; the monotonic
23
+ * task-start anchor is read. */
24
+ rs: RunState;
25
+ /** borrowed-readonly — the run's event-identity mint (stamped on every committed frame). */
26
+ ident: () => {
27
+ eventId: string;
28
+ parentToolCallId?: string;
29
+ sourceTaskId?: string;
30
+ };
31
+ /** borrowed-readonly — the spawning tool call's id when this task runs AS A SUB-AGENT; forwarding is gated on it. */
32
+ parentToolCallId: string | undefined;
33
+ /** borrowed-mutable — the tool-mount lane's started-call set: a committed toolResult retires its id here. */
34
+ startedToolCallIds: Set<string>;
35
+ }
36
+ export interface RunClockAndContentResult {
37
+ /** design/164: the PER-SLICE active clock, or undefined (0 is a valid, exhausted window). */
38
+ effectiveTimeoutMs: number | undefined;
39
+ /** RB-20: the monotonic deadline every walltime enforcement decision reads, or undefined. */
40
+ walltimeMonotonicDeadline: number | undefined;
41
+ /** The hard walltime backstop: its fired flag / lateness, and the clear the teardown calls. */
42
+ timeout: ReturnType<typeof startTimeout>;
43
+ /** Push a content event to the run's queue and, for a sub-agent, to the parent's display sink. */
44
+ pushContent: (e: TaskEvent) => void;
45
+ /** #483: the id of the LAST entry this run committed — the settle-time seal's provenance anchor. */
46
+ ownCommittedTailRef: {
47
+ current: string | undefined;
48
+ };
49
+ /** The `message_committed` mint every persisted entry of this run flows through. */
50
+ emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void;
51
+ }
52
+ export declare function runClockAndContent(input: RunClockAndContentInput): RunClockAndContentResult;
@@ -0,0 +1,26 @@
1
+ import { startTimeout } from "./clock-and-limits.js";
2
+ export function runClockAndContent(input) {
3
+ const { spec, prepared, queue, internals, rs, ident, parentToolCallId, startedToolCallIds } = input;
4
+ const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
5
+ const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
6
+ rs.counters.walltimeSyncBackstopFired = false;
7
+ const timeout = startTimeout(prepared.harness, prepared.abortController, walltimeMonotonicDeadline !== undefined ? walltimeMonotonicDeadline - performance.now() : undefined, prepared.suspendForResource !== undefined);
8
+ const pushContent = (e) => {
9
+ queue.push(e);
10
+ if (parentToolCallId !== undefined && internals?.onForwardEvent) {
11
+ try {
12
+ internals.onForwardEvent(e);
13
+ }
14
+ catch {
15
+ }
16
+ }
17
+ };
18
+ const ownCommittedTailRef = { current: undefined };
19
+ const emitCommitted = (entryId, role, toolCallId) => {
20
+ ownCommittedTailRef.current = entryId;
21
+ if (role === "toolResult" && toolCallId !== undefined)
22
+ startedToolCallIds.delete(toolCallId);
23
+ queue.push({ type: "message_committed", entryId, role, ...(toolCallId !== undefined ? { toolCallId } : {}), ...ident() });
24
+ };
25
+ return { effectiveTimeoutMs, walltimeMonotonicDeadline, timeout, pushContent, ownCommittedTailRef, emitCommitted };
26
+ }
@@ -0,0 +1,35 @@
1
+ import type { Model } from "../../internal/llm.js";
2
+ import { type MaybeCompactOptions } from "../auto-compaction.js";
3
+ import { type ModelPricing } from "../pricing.js";
4
+ import type { Brain, TaskSpec } from "../types.js";
5
+ import type { Stats } from "./assemble-result.js";
6
+ import type { Prepared, RunnerDepsSeat, RunState } from "./contracts.js";
7
+ export interface RunCompactionMachineryInput {
8
+ /** borrowed-readonly — the task spec: the compaction settings. */
9
+ spec: TaskSpec;
10
+ /** borrowed-readonly — the leg's prepared seat: the model, the prompt overhead, the usage governance and the session id. */
11
+ prepared: Prepared;
12
+ /** borrowed-mutable — the run's mutable state: `telemetry.unpricedSpend` is set when a summarizer model is unpriced;
13
+ * the budget ceiling and the telemetry are read. */
14
+ rs: RunState;
15
+ /** borrowed-mutable — the run's usage counters: every accounted summary call adds to them (compaction spend
16
+ * tracked separately too). */
17
+ stats: Stats;
18
+ /** borrowed-readonly — the telemetry lane's price-table door (#462), consulted for every summarizer table adopted. */
19
+ noteUnevaluablePriceTable: (p: ModelPricing) => void;
20
+ /** borrowed-readonly — the Runner's deployment deps, read LIVE (`brain`, `pricing`, `onError`). */
21
+ runner: RunnerDepsSeat;
22
+ }
23
+ export interface RunCompactionMachineryResult {
24
+ /** The accounted compaction brain — `complete` always provided, usage recorded inline. */
25
+ compactionBrain: Brain;
26
+ /** design/64 §25 (A): whether ROUTINE turn-boundary compaction runs for this task. */
27
+ withinTaskCompaction: boolean;
28
+ /** §17.4: the consecutive-failure circuit breaker shared by every compaction lane of the run. */
29
+ compactionBreaker: {
30
+ failures: number;
31
+ };
32
+ /** design/145 门A/§3: the window-safety wiring (per call — a mid-task degrade moves the fallback target). */
33
+ windowSafetyOptions: (mainModel: Model) => Pick<MaybeCompactOptions, "fallbackBudget" | "onWindowSafety">;
34
+ }
35
+ export declare function runCompactionMachinery(input: RunCompactionMachineryInput): RunCompactionMachineryResult;