@sema-agent/core 7.49.5 → 7.50.1

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 (200) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/dist/agents/observer.js +3 -10
  3. package/dist/agents/subagent-steps.d.ts +6 -14
  4. package/dist/agents/subagent-steps.js +3 -17
  5. package/dist/brain/stream-shared.d.ts +1 -1
  6. package/dist/core/allow-entry.d.ts +1 -1
  7. package/dist/core/allow-entry.js +1 -1
  8. package/dist/core/ask-origin.d.ts +15 -11
  9. package/dist/core/ask-origin.js +6 -14
  10. package/dist/core/auto-mode-prompt.js +6 -2
  11. package/dist/core/checkpoint-store.d.ts +6 -4
  12. package/dist/core/checkpoint-store.js +1 -1
  13. package/dist/core/context-edit.d.ts +2 -2
  14. package/dist/core/decision-receipt.d.ts +2 -2
  15. package/dist/core/decision-receipt.js +30 -11
  16. package/dist/core/delegation-frames.d.ts +66 -18
  17. package/dist/core/delegation-frames.js +22 -0
  18. package/dist/core/engine-notice.d.ts +7 -5
  19. package/dist/core/frame-settlement.d.ts +6 -1
  20. package/dist/core/gate-fold.js +34 -19
  21. package/dist/core/gate-lanes.js +15 -13
  22. package/dist/core/gate-outcome.d.ts +2 -2
  23. package/dist/core/governance-codes.js +2 -0
  24. package/dist/core/hooks.d.ts +28 -20
  25. package/dist/core/human-input-projection.js +2 -0
  26. package/dist/core/lsp-diagnostics.d.ts +3 -0
  27. package/dist/core/lsp-diagnostics.js +6 -0
  28. package/dist/core/lsp-session.d.ts +39 -17
  29. package/dist/core/lsp-session.js +118 -58
  30. package/dist/core/lsp.d.ts +83 -3
  31. package/dist/core/lsp.js +146 -16
  32. package/dist/core/mcp.js +2 -1
  33. package/dist/core/media-byte-cap.d.ts +14 -0
  34. package/dist/core/media-byte-cap.js +15 -3
  35. package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
  36. package/dist/core/memory-engine/consolidation-driver.js +54 -6
  37. package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
  38. package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
  39. package/dist/core/memory-engine/consolidation.d.ts +17 -0
  40. package/dist/core/memory-engine/consolidation.js +1 -0
  41. package/dist/core/memory-engine/distiller.d.ts +8 -2
  42. package/dist/core/memory-engine/distiller.js +8 -6
  43. package/dist/core/memory-engine/engine.d.ts +37 -4
  44. package/dist/core/memory-engine/engine.js +127 -38
  45. package/dist/core/memory-engine/file-backend.d.ts +8 -3
  46. package/dist/core/memory-engine/file-backend.js +44 -16
  47. package/dist/core/memory-engine/index.d.ts +2 -2
  48. package/dist/core/memory-engine/index.js +1 -1
  49. package/dist/core/memory-engine/landing-admission.d.ts +65 -0
  50. package/dist/core/memory-engine/landing-admission.js +68 -0
  51. package/dist/core/memory-engine/types.d.ts +23 -0
  52. package/dist/core/mode-policy.d.ts +3 -2
  53. package/dist/core/mode-policy.js +2 -0
  54. package/dist/core/permission-rule-model.d.ts +19 -13
  55. package/dist/core/permission-rule-model.js +7 -4
  56. package/dist/core/permission-rule-provider.d.ts +11 -0
  57. package/dist/core/permission-rules.d.ts +1 -1
  58. package/dist/core/permission-rules.js +5 -2
  59. package/dist/core/persisted-rule-arms.d.ts +15 -7
  60. package/dist/core/persisted-rule-arms.js +13 -6
  61. package/dist/core/reminder-mint.d.ts +5 -0
  62. package/dist/core/reminder-mint.js +1 -0
  63. package/dist/core/retired-keys.d.ts +8 -0
  64. package/dist/core/retired-keys.js +4 -0
  65. package/dist/core/rule-lane-reads.d.ts +4 -4
  66. package/dist/core/rule-lane-reads.js +10 -1
  67. package/dist/core/runner/construction-doors.d.ts +10 -4
  68. package/dist/core/runner/construction-doors.js +25 -2
  69. package/dist/core/runner/contracts.d.ts +20 -3
  70. package/dist/core/runner/env-capability-census.js +1 -1
  71. package/dist/core/runner/gate-exit.js +2 -1
  72. package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
  73. package/dist/core/runner/memory-consolidation-driver.js +5 -2
  74. package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
  75. package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
  76. package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
  77. package/dist/core/runner/permission-rule-lanes.js +13 -43
  78. package/dist/core/runner/person-path-rows.d.ts +244 -0
  79. package/dist/core/runner/person-path-rows.js +387 -0
  80. package/dist/core/runner/policy-throw.d.ts +34 -0
  81. package/dist/core/runner/policy-throw.js +25 -0
  82. package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
  83. package/dist/core/runner/prepare-artifact.d.ts +5 -3
  84. package/dist/core/runner/prepare-artifact.js +2 -8
  85. package/dist/core/runner/prepare-ask-lane.d.ts +2 -1
  86. package/dist/core/runner/prepare-ask-lane.js +14 -16
  87. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  88. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  89. package/dist/core/runner/prepare-context-lane.js +2 -2
  90. package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
  91. package/dist/core/runner/prepare-gate-stations.js +1 -1
  92. package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
  93. package/dist/core/runner/prepare-hands-readface.js +57 -43
  94. package/dist/core/runner/prepare-lsp.d.ts +15 -13
  95. package/dist/core/runner/prepare-lsp.js +11 -11
  96. package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
  97. package/dist/core/runner/prepare-policy-chain.js +11 -10
  98. package/dist/core/runner/prepare-run-refs.js +1 -1
  99. package/dist/core/runner/prepare-task.js +24 -16
  100. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
  101. package/dist/core/runner/prepare-turn-wiring.js +19 -8
  102. package/dist/core/runner/read-root-grant.d.ts +1 -25
  103. package/dist/core/runner/read-root-grant.js +1 -17
  104. package/dist/core/runner/retired-runner-deps.d.ts +2 -2
  105. package/dist/core/runner/run-brain-sinks.d.ts +1 -5
  106. package/dist/core/runner/run-brain-sinks.js +2 -4
  107. package/dist/core/runner/run-harness-handlers.js +5 -4
  108. package/dist/core/runner/run-identity-wiring.d.ts +4 -4
  109. package/dist/core/runner/run-identity-wiring.js +10 -1
  110. package/dist/core/runner/run-leg.d.ts +1 -1
  111. package/dist/core/runner/run-leg.js +22 -9
  112. package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
  113. package/dist/core/runner/run-settle-and-teardown.js +7 -4
  114. package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
  115. package/dist/core/runner/run-terminal-adoption.js +1 -1
  116. package/dist/core/runner/run-turn-boundary.js +2 -2
  117. package/dist/core/runner/runtask.js +8 -8
  118. package/dist/core/runner/session-file-state-replay.d.ts +14 -4
  119. package/dist/core/runner/session-file-state-replay.js +8 -0
  120. package/dist/core/runner/stream-steer-verb.js +3 -0
  121. package/dist/core/runner/tool-end-body.d.ts +5 -4
  122. package/dist/core/runner/tool-end-body.js +4 -4
  123. package/dist/core/runner/tool-output-projection.d.ts +6 -4
  124. package/dist/core/runner/tool-output-projection.js +66 -47
  125. package/dist/core/runner-deps.d.ts +6 -3
  126. package/dist/core/shared-memory/types.d.ts +2 -2
  127. package/dist/core/shell-read-silence.d.ts +23 -0
  128. package/dist/core/shell-read-silence.js +18 -0
  129. package/dist/core/synthetic-read-verdict.d.ts +80 -21
  130. package/dist/core/synthetic-read-verdict.js +89 -18
  131. package/dist/core/task-event.d.ts +113 -25
  132. package/dist/core/task-spec.d.ts +44 -0
  133. package/dist/core/task-tool-shape.d.ts +1 -1
  134. package/dist/core/tool-catalog-entries.js +16 -2
  135. package/dist/core/tool-policy.d.ts +25 -15
  136. package/dist/core/tool-policy.js +9 -13
  137. package/dist/core/trace.d.ts +3 -2
  138. package/dist/core/types.d.ts +3 -3
  139. package/dist/core/types.js +1 -1
  140. package/dist/core/untrusted-text.d.ts +2 -2
  141. package/dist/core/user-attachment.d.ts +164 -0
  142. package/dist/core/user-attachment.js +437 -0
  143. package/dist/engine/compaction/compaction.js +10 -3
  144. package/dist/engine/compaction/utils.js +6 -0
  145. package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
  146. package/dist/engine/execution-env/node-execution-env.js +125 -0
  147. package/dist/engine/harness/agent-harness.d.ts +12 -2
  148. package/dist/engine/harness/agent-harness.js +15 -3
  149. package/dist/engine/harness/types.d.ts +21 -0
  150. package/dist/engine/llm/content-block-render.d.ts +15 -0
  151. package/dist/engine/llm/content-block-render.js +10 -0
  152. package/dist/engine/llm/index.d.ts +1 -0
  153. package/dist/engine/llm/index.js +1 -0
  154. package/dist/engine/llm/types.d.ts +80 -4
  155. package/dist/engine/llm/types.js +43 -1
  156. package/dist/engine/session/import-validate.js +2 -0
  157. package/dist/index.d.ts +6 -4
  158. package/dist/index.js +2 -1
  159. package/dist/internal/harness-types.d.ts +1 -1
  160. package/dist/internal/llm.d.ts +2 -2
  161. package/dist/internal/llm.js +1 -1
  162. package/dist/orchestration/workflow.d.ts +4 -5
  163. package/dist/orchestration/workflow.js +2 -2
  164. package/dist/prompts/default.d.ts +1 -1
  165. package/dist/tools/artifact/artifact-tool.js +1 -1
  166. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  167. package/dist/tools/fs/dir-entries.d.ts +4 -10
  168. package/dist/tools/fs/dir-entries.js +4 -11
  169. package/dist/tools/fs/file-read-check.js +3 -3
  170. package/dist/tools/fs/fs-pdf.js +2 -4
  171. package/dist/tools/fs/fs-read.d.ts +47 -4
  172. package/dist/tools/fs/fs-read.js +63 -19
  173. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  174. package/dist/tools/fs/fs-search-tools.js +7 -2
  175. package/dist/tools/fs/fs-write.d.ts +1 -1
  176. package/dist/tools/fs/fs-write.js +7 -6
  177. package/dist/tools/fs/index.d.ts +7 -1
  178. package/dist/tools/fs/index.js +56 -34
  179. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  180. package/dist/tools/fs/read-deny-home.js +32 -22
  181. package/dist/tools/fs/read-deny.d.ts +14 -0
  182. package/dist/tools/fs/read-deny.js +8 -0
  183. package/dist/tools/fs/repo-map.d.ts +3 -2
  184. package/dist/tools/fs/repo-map.js +11 -3
  185. package/dist/tools/fs/safety.d.ts +41 -2
  186. package/dist/tools/fs/safety.js +12 -3
  187. package/dist/tools/fs/shell-read-check.d.ts +12 -3
  188. package/dist/tools/fs/shell-read-check.js +44 -24
  189. package/dist/tools/fs/shell-read-device.d.ts +1 -1
  190. package/dist/tools/fs/shell-read-face.d.ts +15 -0
  191. package/dist/tools/fs/shell-read-face.js +5 -0
  192. package/dist/tools/fs/shell-read-judge.d.ts +9 -4
  193. package/dist/tools/fs/shell-read-judge.js +1 -1
  194. package/dist/tools/loop-tick.d.ts +2 -2
  195. package/package.json +3 -1
  196. package/test/export-surface.snapshot.json +39 -1
  197. package/wire-changes/core@7.50.0.json +1699 -0
  198. package/wire-changes/core@7.50.1.json +65 -0
  199. package/dist/core/runner/persisted-path-rows.d.ts +0 -88
  200. package/dist/core/runner/persisted-path-rows.js +0 -108
@@ -2,23 +2,7 @@ import { deliverEngineNotice } from "../types.js";
2
2
  import { READ_ROOT_CANDIDATE_DIR_MAX } from "../read-root-candidate.js";
3
3
  import { DEFAULT_GATE_AWAIT_TIMEOUT_MS } from "../gate-bounded-await.js";
4
4
  import { thrownText } from "../thrown-value.js";
5
- import { pathFamilyOf, withinAnyRoot } from "../../tools/fs/safety.js";
6
- export function rootAdmittingArtifactStore(storePhysical, rootsCanonical) {
7
- const storeFamily = pathFamilyOf({ root: storePhysical });
8
- return rootsCanonical.find((root) => pathFamilyOf({ root }) === storeFamily && (withinAnyRoot([root], storePhysical) || withinAnyRoot([storePhysical], root)));
9
- }
10
- export function withdrawStandingGrantsAdmitting(storePhysical, ref) {
11
- const standing = ref.standing;
12
- if (standing === undefined)
13
- return [];
14
- const withdrawn = standing.keys.filter((key) => rootAdmittingArtifactStore(storePhysical, [key]) !== undefined);
15
- for (const key of withdrawn) {
16
- const at = standing.readRoots.indexOf(key);
17
- if (at >= 0)
18
- standing.readRoots.splice(at, 1);
19
- }
20
- return withdrawn;
21
- }
5
+ import { rootAdmittingArtifactStore } from "../../tools/fs/safety.js";
22
6
  export function readRootGrantOver(seats) {
23
7
  const { env, writeRoots, readRoots, artifactStore } = seats;
24
8
  return async (candidate, signal) => {
@@ -1,7 +1,7 @@
1
1
  import type { RunnerDeps } from "../types.js";
2
2
  import type { ResumeTaskConfig, RunInternals } from "./contracts.js";
3
- /** Runs on the caller's own argument at `new Runner` (the only point that still sees an inherited or
4
- * non-enumerable seat — construction spreads deps into plain objects), and again in the pre-first-await
3
+ /** Runs on the caller's own argument at `new Runner` (the only point that still sees an inherited seat — construction then
4
+ * reads every own seat once into the plain copy the Runner keeps, `retainRunnerDeps`), and again in the pre-first-await
5
5
  * config-doors phase and pre-CAS on a resume. */
6
6
  export declare function assertNoRetiredRunnerDeps(deps: RunnerDeps): void;
7
7
  /** The resume ladder's pre-claim door: the retired seats of the deps, then the retired members of the caller policies this
@@ -6,7 +6,7 @@ import type { RunInternals, RunState } from "./contracts.js";
6
6
  export interface RunBrainSinksInput {
7
7
  /** borrowed-mutable — the run's event queue: the `status` frames. */
8
8
  queue: PushQueue<TaskEvent>;
9
- /** borrowed-readonly — the trusted run-scoped channel: `agentName` (the display name), `onStatusEvent` (the second sink). */
9
+ /** borrowed-readonly — the trusted run-scoped channel: `onStatusEvent` (the second sink). */
10
10
  internals: RunInternals | undefined;
11
11
  /** borrowed-readonly — the run's mutable state, read for the telemetry frames' tracer and task id. */
12
12
  rs: RunState;
@@ -16,16 +16,12 @@ export interface RunBrainSinksInput {
16
16
  parentToolCallId?: string;
17
17
  sourceTaskId?: string;
18
18
  };
19
- /** borrowed-readonly — the spawning tool call's id when this task runs AS A SUB-AGENT; absent on a top-level run. */
20
- parentToolCallId: string | undefined;
21
19
  /** borrowed-readonly — the reasoning lane's wire-facts consumer, the third sink the composition installs. */
22
20
  observeReasoningWireFacts: (facts: ReasoningWireFacts) => void;
23
21
  /** borrowed-readonly — the notice sink and the leg's ids; the fourth sink's announcer is minted from them. */
24
22
  notice: ExtraBodyDroppedSeatInput;
25
23
  }
26
24
  export interface RunBrainSinksResult {
27
- /** The subagent's human display name for its `task_progress` ticks, or undefined. */
28
- subagentName: string | undefined;
29
25
  /** Compose the three brain ALS sinks around a brain-driving call. */
30
26
  withBrainSinks: <T>(fn: () => Promise<T>) => Promise<T>;
31
27
  withExtraBodyDroppedSink: <T>(fn: () => Promise<T>) => Promise<T>;
@@ -2,10 +2,8 @@ import { runWithBrainTelemetry, runWithReasoningWireFacts, runWithStatusSink } f
2
2
  import { createExtraBodyDroppedScope } from "./extra-body-dropped-seat.js";
3
3
  import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
4
4
  import { emitTrace } from "../trace.js";
5
- import { inlineUntrusted } from "../untrusted-text.js";
6
5
  export function runBrainSinks(input) {
7
- const { queue, internals, rs, ident, parentToolCallId, observeReasoningWireFacts, notice } = input;
8
- const subagentName = parentToolCallId !== undefined && internals?.agentName !== undefined ? inlineUntrusted(internals.agentName.slice(0, 320), 80) : undefined;
6
+ const { queue, internals, rs, ident, observeReasoningWireFacts, notice } = input;
9
7
  const statusSinkNotifier = createSafeNotifier({
10
8
  onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals status sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
11
9
  });
@@ -62,5 +60,5 @@ export function runBrainSinks(input) {
62
60
  : { kind: "vision.placeholder", version: 1, taskId: rs.telemetry.taskId, count: t.count, ts: Date.now() });
63
61
  };
64
62
  const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, () => runWithReasoningWireFacts(observeReasoningWireFacts, fn)));
65
- return { subagentName, withBrainSinks, withExtraBodyDroppedSink: createExtraBodyDroppedScope(notice) };
63
+ return { withBrainSinks, withExtraBodyDroppedSink: createExtraBodyDroppedScope(notice) };
66
64
  }
@@ -1,5 +1,6 @@
1
1
  import { observeThenableRejection } from "../safe-notify.js";
2
2
  import { forwardSafely } from "./forward-sink.js";
3
+ import { emitsChildRowFrames } from "../delegation-frames.js";
3
4
  import { emitTrace } from "../trace.js";
4
5
  import { primaryActivityArg } from "../arg-summary.js";
5
6
  import { redactSecrets } from "../untrusted-egress.js";
@@ -22,8 +23,8 @@ export function createHarnessHandlers(input) {
22
23
  const toolCallIdFoldSeat = createToolCallIdFoldSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId });
23
24
  const toolTurnIncompleteSeat = createToolTurnIncompleteSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId });
24
25
  const finishReasonSeat = createFinishReasonSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId });
25
- const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, toolProgress, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs, internalsNotifier } = deps;
26
- const frames = { push: (f) => pushContent({ ...f, ...ident() }), structured: prepared.structuredProjector };
26
+ const { spec, queue, internals, ident, pushContent, emitCommitted, startedToolCallIds, toolStartAt, toolProgress, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs, internalsNotifier, mediaByteCapBytes } = deps;
27
+ const frames = { push: (f) => pushContent({ ...f, ...ident() }), structured: prepared.structuredProjector, mediaByteCapBytes };
27
28
  const startOf = (e) => {
28
29
  const label = toolLabels.get(e.toolName);
29
30
  return { toolCallId: e.toolCallId, spelledName: e.toolName, wireName: rosterToolName(prepared.toolRosterDeltas.current, e.toolName), ...(label !== undefined ? { label } : {}), args: e.args };
@@ -306,14 +307,14 @@ export function createHarnessHandlers(input) {
306
307
  rs.turn.turnUsageMissing = false;
307
308
  rs.turn.turnStopReason = undefined;
308
309
  rs.turn.turnResponseModel = undefined;
309
- if (parentToolCallId !== undefined) {
310
+ if (emitsChildRowFrames(prepared.hookIdentity)) {
310
311
  const progress = {
311
312
  type: "task_progress",
312
313
  taskId: rs.telemetry.taskId,
313
314
  ...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
314
315
  ...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
315
316
  ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
316
- ...(subagentName ? { name: subagentName } : {}),
317
+ ...(prepared.hookIdentity.agentName ? { name: prepared.hookIdentity.agentName } : {}),
317
318
  ...(internals?.explicitAgentName !== undefined ? { spawnName: internals.explicitAgentName } : {}),
318
319
  model: prepared.model.id,
319
320
  usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
@@ -8,14 +8,14 @@ import type { CaptureOptOutRef, LiveHandle, ManualCompactRef, McpRedialRef, Noti
8
8
  export interface RunIdentityWiringInput {
9
9
  /** borrowed-readonly — the task spec: the task id and the memory / notification seats it names. */
10
10
  spec: TaskSpec;
11
- /** borrowed-mutable — the run's event queue: the manifest, roster-delta, compaction_outcome and redelivered
12
- * task_notification frames. */
11
+ /** borrowed-mutable — the run's event queue: the manifest, roster-delta, task_started, compaction_outcome and
12
+ * redelivered task_notification frames. */
13
13
  queue: PushQueue<TaskEvent>;
14
14
  /** borrowed-mutable — the leg's prepared seat: the harness sinks (`onUndrainedEngineNotes`, `engineInjectionsHeld`,
15
15
  * `onUndrainedUserInputs`, `onEngineNoteConsumed`) are installed, `nextTurn` is appended to; the ids, the identity
16
16
  * envelope, the hold refs, the memory session and the listing / roster faces are read. */
17
17
  prepared: Prepared;
18
- /** borrowed-readonly — the trusted run-scoped channel: `parentToolCallId`, `onNotifyInjectorReady`. */
18
+ /** borrowed-readonly — the trusted run-scoped channel: `parentToolCallId`, `onNotifyInjectorReady`, the spawner's delegation facts. */
19
19
  internals: RunInternals | undefined;
20
20
  /** borrowed-mutable — the stream layer's backstop carrier: the memory scopes, the edited-files reader and the
21
21
  * owed delegation terminal are published here the moment they exist. */
@@ -58,7 +58,7 @@ export interface RunIdentityWiringResult {
58
58
  parentToolCallId?: string;
59
59
  sourceTaskId?: string;
60
60
  };
61
- /** The delegation-lifecycle emission closure both stations share (spawn here, terminal in the driver). */
61
+ /** The delegation-lifecycle emission closure both phases share (spawn here, terminal in the driver). */
62
62
  emitDelegationLifecycle: (event: import("../types.js").DelegationLifecycleEvent) => void;
63
63
  /** The loop-liveness latch and the two attribution seats (ended / userInterrupted / userHalted). */
64
64
  loopLatch: {
@@ -1,5 +1,7 @@
1
1
  import { uuidv7 } from "../../internal/harness.js";
2
2
  import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
3
+ import { forwardSafely } from "./forward-sink.js";
4
+ import { emitsChildRowFrames, taskStartedFrame } from "../delegation-frames.js";
3
5
  import { recordFrameReceipt, toolResultReceiptKey } from "../frame-settlement.js";
4
6
  import { discloseDroppedPending, renderTaskNotificationXml, taskNotificationDedupKey } from "../task-notification.js";
5
7
  import { buildAppliedHumanInputEvent } from "../human-input-projection.js";
@@ -35,7 +37,12 @@ export function runIdentityWiring(input) {
35
37
  return;
36
38
  deliverDelegationLifecycle(runner.deps.onDelegationLifecycle, event, delegationLifecycleNotifier, "runtask.onDelegationLifecycle");
37
39
  };
38
- emitDelegationLifecycle({ phase: "spawn", identity: prepared.hookIdentity });
40
+ if (emitsChildRowFrames(prepared.hookIdentity) && prepared.hookIdentity.legKind !== "resume") {
41
+ const started = taskStartedFrame({ taskId: runSourceTaskId, sessionId: prepared.sessionId, model: prepared.model.id, identity: prepared.hookIdentity }, internals, ident());
42
+ queue.push(started);
43
+ forwardSafely(internals, started);
44
+ emitDelegationLifecycle({ phase: "spawn", identity: prepared.hookIdentity });
45
+ }
39
46
  if (taskIdRef !== undefined && prepared.hookIdentity.isDelegatedChild && runner.deps.onDelegationLifecycle !== undefined) {
40
47
  taskIdRef.delegationTerminalOwed = prepared.hookIdentity;
41
48
  }
@@ -79,6 +86,8 @@ export function runIdentityWiring(input) {
79
86
  queue.push({ ...buildAppliedHumanInputEvent({ carrier: "steer", source: "steer", sessionSeq: nextHumanInputSeq(prepared.harness), inputId: steer.inputId, actor: steer.actor, principal: spec.principal }), eventId: uuidv7(), ...identity });
80
87
  };
81
88
  prepared.harness.onFrameCommitted = (carrier, entryId) => {
89
+ if (carrier.kind === "attachment")
90
+ return void prepared.attachmentDelivery.settle(carrier.inputId, carrier.ordinal);
82
91
  const seam = prepared.frameSettlement;
83
92
  if (seam.awaiting.size === 0)
84
93
  return;
@@ -82,7 +82,7 @@ export interface RunLegInput<R> {
82
82
  unsubBoundary: () => void;
83
83
  /** borrowed-readonly — the git lane's harness-event release, called third. */
84
84
  unsub: () => void;
85
- /** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `hooks`, `onNotice`, `allowImageUrl`; the resumed
85
+ /** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `hooks`, `onNotice`, `allowImageUrl`, `mediaByteCapBytes`; the resumed
86
86
  * decision's resolver files its execution record's word through `onNotice` too); the one home of why it is a seat and not a
87
87
  * captured object is {@link RunnerDepsSeat}. */
88
88
  runner: RunnerDepsSeat;
@@ -6,6 +6,8 @@ import { normalizePersistedRuleHit } from "../rule-lane-reads.js";
6
6
  import { decidersNote, SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
7
7
  import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
8
8
  import { buildAppliedHumanInputEvent, buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
9
+ import { humanInputAttachmentsOf, readObjectiveAttachments, userAttachmentDeclarations } from "../user-attachment.js";
10
+ import { uuidv7 } from "../../internal/harness.js";
9
11
  import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
10
12
  import { applyPersistedTightening, disclosedRuleSet } from "../persisted-rule-arms.js";
11
13
  import { parkedSkillApprovalRefusal, skillRowRefusal } from "../skill-grant.js";
@@ -13,7 +15,8 @@ import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
13
15
  import { computeCostMicroUsd } from "../pricing.js";
14
16
  import { mintSystemReminder } from "../reminder-mint.js";
15
17
  import { hasDestroy } from "../remote-env.js";
16
- import { advertisedWritableRootsSeat, exemptRootsSeat, humanRefusalSentence, policyCannotAnswer, policyRefusalMessage, refuseOutOfContractDecision } from "../tool-policy.js";
18
+ import { advertisedWritableRootsSeat, attestationOf, exemptRootsSeat, humanRefusalSentence, policyCannotAnswer, policyRefusalMessage, refuseOutOfContractDecision } from "../tool-policy.js";
19
+ import { pathDenialCardOf } from "../../tools/fs/safety.js";
17
20
  import { callFaceSeat, judgeParkedToolIdentity, parkedRowLiveFace, rosterEntryNamed } from "../tool-roster.js";
18
21
  import { deliverEngineNotice } from "../types.js";
19
22
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
@@ -288,7 +291,7 @@ export async function runLeg(input) {
288
291
  const userPromptSubmit = (spec.hooks ?? runner.deps.hooks)?.userPromptSubmit;
289
292
  if (userPromptSubmit) {
290
293
  try {
291
- const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, permissionMode: prepared.permissionMode.observed, signal: sig, source: "objective", ...(objectiveActor !== undefined ? { actor: snapshotActorAssertion(objectiveActor) } : {}) }));
294
+ const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, permissionMode: prepared.permissionMode.observed, signal: sig, source: "objective", ...(objectiveActor !== undefined ? { actor: snapshotActorAssertion(objectiveActor) } : {}), ...(prepared.userAttachments !== undefined ? { userAttachments: userAttachmentDeclarations(prepared.userAttachments) } : {}) }));
292
295
  if (promptSeat.expired) {
293
296
  if (promptSeat.cause === "timeout") {
294
297
  try {
@@ -326,6 +329,8 @@ export async function runLeg(input) {
326
329
  promptBlocked = true;
327
330
  }
328
331
  }
332
+ const objectiveInputId = uuidv7();
333
+ const { frames: attachmentFrames, chars: attachmentChars } = await readObjectiveAttachments(prepared, objectiveInputId, promptBlocked);
329
334
  let gitLegDelivered;
330
335
  if (!promptBlocked) {
331
336
  const gitBody = await resolveGitLegDelivery(prepared, undefined, (err) => {
@@ -401,14 +406,14 @@ export async function runLeg(input) {
401
406
  const precallMicroUsd = rs.budget.maxCostMicroUsd === undefined
402
407
  ? 0
403
408
  : computeCostMicroUsd({
404
- totalInputTokens: Math.ceil((effectiveObjective.length + gitQueuedChars) / 4),
409
+ totalInputTokens: Math.ceil((effectiveObjective.length + gitQueuedChars + attachmentChars) / 4),
405
410
  cacheReadTokens: 0,
406
411
  cacheWriteTokens: 0,
407
412
  cacheWriteTokensLong: 0,
408
413
  outputTokens: prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS,
409
414
  }, rs.telemetry.pricing);
410
415
  const precallCeilingMicroUsd = prepared.suspendForResource !== undefined ? rs.budget.remainingMicroUsd : rs.budget.maxCostMicroUsd;
411
- const precallTokens = Math.ceil((effectiveObjective.length + gitQueuedChars) / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
416
+ const precallTokens = Math.ceil((effectiveObjective.length + gitQueuedChars + attachmentChars) / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
412
417
  const precallCeilingTokens = prepared.suspendForResource !== undefined ? rs.budget.remainingTokens : rs.budget.maxTokensWindow;
413
418
  const entryUsageRetryAfterMs = prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
414
419
  if (promptBlocked) {
@@ -470,21 +475,25 @@ export async function runLeg(input) {
470
475
  }
471
476
  const enginePrefixChars = effectiveObjective.length - projectedObjective.length;
472
477
  if (!prepared.abortController.signal.aborted) {
478
+ prepared.attachmentDelivery.stage(objectiveInputId, attachmentFrames);
473
479
  queue.push({
474
480
  ...buildHumanInputEvent({
475
481
  carrier: "objective",
476
482
  source: "objective",
477
483
  delivery: "applied",
484
+ inputId: objectiveInputId,
478
485
  sessionSeq: nextHumanInputSeq(prepared.harness),
479
486
  ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
480
487
  ...(objectiveActor?.issuer !== undefined ? { issuer: objectiveActor.issuer } : {}),
481
488
  ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
489
+ ...(attachmentFrames.length > 0 ? { attachments: humanInputAttachmentsOf(attachmentFrames) } : {}),
482
490
  }),
483
491
  ...ident(),
484
492
  });
485
493
  final = await withBrainSinks(() => prepared.harness.prompt(effectiveObjective, {
486
494
  ...(images && images.length > 0 ? { images } : {}),
487
495
  ...(enginePrefixChars > 0 ? { enginePrefixChars } : {}),
496
+ ...(attachmentFrames.length > 0 ? { trailing: attachmentFrames.map((f) => f.seed) } : {}),
488
497
  ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
489
498
  }));
490
499
  await flushGitMirror();
@@ -579,7 +588,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
579
588
  const settleEnd = async (isError, result, refusedBy) => {
580
589
  if (refusedBy !== undefined)
581
590
  occurrences.refuse(pendingAction.toolCallId, refusedBy);
582
- const gate = emitSettled(occurrences, callStart, { result, isError }, { push: emit, structured: prepared.structuredProjector }).gate ?? decidedGate;
591
+ const gate = emitSettled(occurrences, callStart, { result, isError }, { push: emit, structured: prepared.structuredProjector, mediaByteCapBytes: runner.deps.mediaByteCapBytes }).gate ?? decidedGate;
583
592
  const word = await resume.recordExecutionOutcome(gate);
584
593
  settleExecutionRecord(runner.deps.onNotice, word, { toolName: pendingAction.toolName, gate, sessionId: prepared.sessionId, runId: prepared.runId, scope: resume.cp.scope, ...(resume.cp.checkpointId !== undefined ? { checkpointId: resume.cp.checkpointId } : {}) });
585
594
  };
@@ -627,6 +636,10 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
627
636
  resourceSpentMicroUsd: prepared.resourceLedger?.spentMicroUsd ?? 0,
628
637
  suspendCount: resume.cp.suspendCount ?? 0,
629
638
  };
639
+ const denialCard = (d) => {
640
+ const card = pathDenialCardOf(attestationOf(d, { toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName })?.pathDenial);
641
+ return card !== undefined ? { details: card } : {};
642
+ };
630
643
  const beltStamped = prepared.stampCallTarget?.(beltReq(), prepared.abortController.signal);
631
644
  if (beltStamped !== undefined)
632
645
  beltReading = (await beltStamped).targetReading;
@@ -643,7 +656,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
643
656
  ? " (the call as it would run requires a person's approval, and this decision was the deployment's policy)"
644
657
  : " (the policy rewrote these bytes to a value that was not approved)";
645
658
  const editedDenial = formatHookFeedback(`${outcome.updatedInput !== undefined ? "The approver EDITED this call's input; the edited call" : "A PreToolUse hook REWROTE this approved call's input; the rewritten call"} is refused by the deployment's tool policy and was not executed${why}.`, prepared.reminderMark);
646
- await settleEnd(true, { content: editedDenial }, "policy");
659
+ await settleEnd(true, { content: editedDenial, ...denialCard(rechecked) }, "policy");
647
660
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
648
661
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
649
662
  return;
@@ -697,7 +710,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
697
710
  const narrowed = refuseOutOfContractDecision(await prepared.denyNarrowingPolicy.check(beltReq(), prepared.abortController.signal));
698
711
  if (narrowed.action === "deny") {
699
712
  const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by ${ruleSourcePhrase("narrowing")} and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`, prepared.reminderMark);
700
- await settleEnd(true, { content: narrowedDenial }, "policy");
713
+ await settleEnd(true, { content: narrowedDenial, ...denialCard(narrowed) }, "policy");
701
714
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
702
715
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
703
716
  return;
@@ -721,7 +734,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
721
734
  laneRead = Promise.resolve(unreadable);
722
735
  }
723
736
  const read = await settleOrgVerdictWithin(laneRead, unreadable, { signal: prepared.abortController.signal, timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS, onFallback: (cause) => void (laneWaitCancelled = cause === "aborted") });
724
- const tightened = applyPersistedTightening({ action: "allow" }, read);
737
+ const tightened = applyPersistedTightening({ action: "allow" }, read, beltReq());
725
738
  if (laneWaitCancelled || tightened.tightened === "deny" || tightened.tightened === "unreadable") {
726
739
  const why = laneWaitCancelled
727
740
  ? "the task was cancelled before your persisted permission rules could be read"
@@ -729,7 +742,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
729
742
  ? `a standing deny rule of yours (${disclosedRuleSet(read.hit?.rules ?? [])}) now refuses it`
730
743
  : `it could not be read against your standing deny/ask rules (${read.reason})`;
731
744
  const ruleDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${why}. This approval is spent — the call has to be re-issued and approved again.`, prepared.reminderMark);
732
- await settleEnd(true, { content: ruleDenial }, "persisted_rule");
745
+ await settleEnd(true, { content: ruleDenial, ...denialCard(tightened.decision) }, "persisted_rule");
733
746
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, ruleDenial, true));
734
747
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
735
748
  return;
@@ -26,10 +26,6 @@ export interface RunSettleAndTeardownInput {
26
26
  parentToolCallId?: string;
27
27
  sourceTaskId?: string;
28
28
  };
29
- /** borrowed-readonly — the spawning tool call's id when this task runs AS A SUB-AGENT (gates the terminal tick). */
30
- parentToolCallId: string | undefined;
31
- /** borrowed-readonly — the sub-agent display name for the terminal tick. */
32
- subagentName: string | undefined;
33
29
  /** borrowed-readonly — the loop-liveness latch: `userHalted` = the halt verb was ACCEPTED (the unconsumed-halt notice's input; it does not sign the seat). */
34
30
  loopLatch: {
35
31
  readonly userHalted: boolean;
@@ -1,4 +1,5 @@
1
1
  import { MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
2
+ import { emitsChildRowFrames } from "../delegation-frames.js";
2
3
  import { isSyntheticApiErrorMessage } from "../../internal/harness.js";
3
4
  import { adjudicateDerivedRoute, fallbackToPrimaryNotice, sameRouteIdentity } from "../../brain/route-adjudicator.js";
4
5
  import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
@@ -21,11 +22,12 @@ import { generatePromptSuggestions } from "./prompt-suggestions.js";
21
22
  import { settleTeardownLeg } from "./teardown-bounded.js";
22
23
  import { owedDateAnchor } from "./announce-once-ledger.js";
23
24
  import { autoRunConsolidationFromRecommendation, autoRunOnRecommendationEffective, scopeHasUnconsumedRecommendation } from "./memory-consolidation-driver.js";
25
+ import { consolidationWriteJudgeOfLeg } from "./memory-consolidation-write-judge.js";
24
26
  import { amendTerminal, terminalProjection } from "./terminal-projection.js";
25
27
  import { mintCompactedEvent } from "../auto-compaction.js";
26
28
  import { asThrownError } from "../thrown-value.js";
27
29
  export async function runSettleAndTeardown(input) {
28
- const { spec, queue, prepared, resume, internals, rs, stats, ident, parentToolCallId, subagentName, loopLatch, timeout, ownCommittedTailRef, emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, userInterruptedLive, answerSettled, strandedHumanAnswers, reasoningResolution, comp, compactionBreaker, owedUserInput, setResult, onSuggestions, onSuspend, runner, sessions } = input;
30
+ const { spec, queue, prepared, resume, internals, rs, stats, ident, loopLatch, timeout, ownCommittedTailRef, emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, userInterruptedLive, answerSettled, strandedHumanAnswers, reasoningResolution, comp, compactionBreaker, owedUserInput, setResult, onSuggestions, onSuspend, runner, sessions } = input;
29
31
  try {
30
32
  if (comp?.compacted) {
31
33
  queue.push(mintCompactedEvent(comp, "auto", ident()));
@@ -237,7 +239,8 @@ export async function runSettleAndTeardown(input) {
237
239
  await settleTeardownLeg(() => mes.harvest(), "memoryEngineSession.harvest (settle leg)", (e) => runner.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
238
240
  const scope = mes.handle.writeScope;
239
241
  if (scope !== null && autoRunOnRecommendationEffective(runner.deps) && scopeHasUnconsumedRecommendation(mes.engine, scope)) {
240
- void autoRunConsolidationFromRecommendation({ deps: runner.deps, engine: mes.engine, scope, sessionId: prepared.sessionId, onNotice: runner.deps.onNotice, onError: runner.deps.onError });
242
+ const writeJudge = consolidationWriteJudgeOfLeg(prepared.syntheticWriteVerdict);
243
+ void autoRunConsolidationFromRecommendation({ deps: runner.deps, engine: mes.engine, scope, sessionId: prepared.sessionId, writeJudge, onNotice: runner.deps.onNotice, onError: runner.deps.onError });
241
244
  }
242
245
  }
243
246
  emitTrace(rs.telemetry.tracer, () => ({
@@ -262,14 +265,14 @@ export async function runSettleAndTeardown(input) {
262
265
  ts: Date.now(),
263
266
  }));
264
267
  setResult(result);
265
- if (parentToolCallId !== undefined && result.terminal.kind !== "paused") {
268
+ if (emitsChildRowFrames(prepared.hookIdentity) && result.terminal.kind !== "paused") {
266
269
  const terminalTick = {
267
270
  type: "task_progress",
268
271
  taskId: rs.telemetry.taskId,
269
272
  ...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
270
273
  ...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
271
274
  ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
272
- ...(subagentName ? { name: subagentName } : {}),
275
+ ...(prepared.hookIdentity.agentName ? { name: prepared.hookIdentity.agentName } : {}),
273
276
  ...(internals?.explicitAgentName !== undefined ? { spawnName: internals.explicitAgentName } : {}),
274
277
  model: prepared.model.id,
275
278
  usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
@@ -78,7 +78,7 @@ export interface RunTerminalAdoptionInput<R> {
78
78
  abortedLive: boolean;
79
79
  /** borrowed-readonly — the leg's snapshot of `loopLatch.userInterrupted` (gates the interruption marker). */
80
80
  userInterruptedLive: boolean;
81
- /** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `checkpointStore`, `onNotice`); the one home of why it is
81
+ /** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `checkpointStore`, `onNotice`, `mediaByteCapBytes`); the one home of why it is
82
82
  * a seat and not a captured object is {@link RunnerDepsSeat}. */
83
83
  runner: RunnerDepsSeat;
84
84
  /** borrowed-readonly — the session store: the end-of-task pass touches the leg's session through it (`finish`'s teardown legs). */
@@ -43,7 +43,7 @@ export async function runTerminalAdoption(input) {
43
43
  const report = await reconcileInterruptedSession(prepared.session, prepared.toolEffects, undefined, startedToolCallIds);
44
44
  orphansClosed = report.recovered.length;
45
45
  for (const orphan of report.recovered) {
46
- emitSettled(prepared.toolOccurrences, { toolCallId: orphan.toolCallId, spelledName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), args: orphan.args }, reconciledEnd(orphan), { push: (f) => queue.push({ ...f, ...ident() }), structured: prepared.structuredProjector });
46
+ emitSettled(prepared.toolOccurrences, { toolCallId: orphan.toolCallId, spelledName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), args: orphan.args }, reconciledEnd(orphan), { push: (f) => queue.push({ ...f, ...ident() }), structured: prepared.structuredProjector, mediaByteCapBytes: runner.deps.mediaByteCapBytes });
47
47
  emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
48
48
  }
49
49
  reconcileComplete = true;
@@ -10,7 +10,7 @@ import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFi
10
10
  import { hookSeatExpiredError, runHookSeat } from "../hooks.js";
11
11
  import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
12
12
  import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.js";
13
- import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
13
+ import { formatDiagnosticsBlock, withholdListedDiagnostics } from "../lsp-diagnostics.js";
14
14
  import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
15
15
  import { awaitChargeWithSlowDisclosure, discloseUnevaluableWindow, ENV_DUE_GOVERNANCE_READ_BUDGET_MS, GOVERNANCE_READ_STALLED, LIMIT_APPROACH_DEFAULT_THRESHOLDS, limitApproachFrames, platformLimitTerminal, raceUntilDeadline } from "./clock-and-limits.js";
16
16
  import { appendBoundaryFrames, compactionRestateOptions } from "./compaction-restate.js";
@@ -203,7 +203,7 @@ export function createTurnBoundary(input) {
203
203
  !prepared.lspDiagnostics.registry.isEmpty() &&
204
204
  !prepared.abortController.signal.aborted &&
205
205
  prepared.batchHaltRef.current === undefined) {
206
- const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
206
+ const files = await withholdListedDiagnostics(prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent), prepared.lspDiagnostics.withheldOf);
207
207
  if (files.length > 0) {
208
208
  queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
209
209
  const block = mintSystemReminder(formatDiagnosticsBlock(files), prepared.reminderMark);
@@ -45,7 +45,7 @@ import { streamLifecycleVerbs } from "./stream-lifecycle-verbs.js";
45
45
  import { streamHaltVerbs } from "./stream-halt-verbs.js";
46
46
  import { streamModeVerb } from "./stream-mode-verb.js";
47
47
  import { runNotificationLane } from "./run-notification-lane.js";
48
- import { assertRunnerDepsDoors } from "./construction-doors.js";
48
+ import { retainRunnerDeps } from "./construction-doors.js";
49
49
  import { runLeg } from "./run-leg.js";
50
50
  import { runTerminalAdoption } from "./run-terminal-adoption.js";
51
51
  import { runSettleAndTeardown } from "./run-settle-and-teardown.js";
@@ -77,10 +77,10 @@ export class Runner {
77
77
  static PARENT_CONSTRAINT_REGISTRY_CAP = 1024;
78
78
  depsSeat;
79
79
  constructor(deps) {
80
- assertRunnerDepsDoors(deps);
80
+ const retained = retainRunnerDeps(deps);
81
81
  const runner = this;
82
82
  this.depsSeat = { get deps() { return runner.deps; } };
83
- this.deps = withDeploymentAllowedTools(deps);
83
+ this.deps = withDeploymentAllowedTools(retained);
84
84
  if (this.deps.tiers) {
85
85
  this.deps = { ...this.deps, models: expandTiers(this.deps.models, this.deps.tiers) };
86
86
  }
@@ -121,7 +121,7 @@ export class Runner {
121
121
  if (!this.deps.toolResultStore) {
122
122
  this.deps = { ...this.deps, toolResultStore: new RunnerSharedToolResultStore({ maxTotalChars: 64_000_000 }) };
123
123
  }
124
- this.sessions = deps.sessionStore ?? new TtlSessionStore();
124
+ this.sessions = this.deps.sessionStore ?? new TtlSessionStore();
125
125
  }
126
126
  emitTaskOutcome(outcome) {
127
127
  emitTaskOutcome(this.deps, outcome);
@@ -465,10 +465,10 @@ export class Runner {
465
465
  const { effectiveTimeoutMs, walltimeMonotonicDeadline, timeout, pushContent, ownCommittedTailRef, emitCommitted } = runClockAndContent({ spec, prepared, queue, internals, rs, ident, parentToolCallId, startedToolCallIds });
466
466
  const toolProgress = createToolProgressLane(pushContent, ident, prepared.toolOccurrences);
467
467
  prepared.toolProgressRef.current = toolProgress;
468
- const { subagentName, withBrainSinks, withExtraBodyDroppedSink } = runBrainSinks({ queue, internals, rs, ident, parentToolCallId, observeReasoningWireFacts, notice: { onNotice: this.deps.onNotice, sessionId: prepared.sessionId, runId: prepared.runId } });
468
+ const { withBrainSinks, withExtraBodyDroppedSink } = runBrainSinks({ queue, internals, rs, ident, observeReasoningWireFacts, notice: { onNotice: this.deps.onNotice, sessionId: prepared.sessionId, runId: prepared.runId } });
469
469
  const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
470
470
  for (const orphan of prepared.wakeRecovered) {
471
- emitSettled(prepared.toolOccurrences, { toolCallId: orphan.toolCallId, spelledName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), args: orphan.args }, reconciledEnd(orphan), { push: (f) => queue.push({ ...f, ...ident() }), structured: prepared.structuredProjector });
471
+ emitSettled(prepared.toolOccurrences, { toolCallId: orphan.toolCallId, spelledName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), args: orphan.args }, reconciledEnd(orphan), { push: (f) => queue.push({ ...f, ...ident() }), structured: prepared.structuredProjector, mediaByteCapBytes: this.deps.mediaByteCapBytes });
472
472
  emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
473
473
  }
474
474
  const postToolBatchHook = (spec.hooks ?? this.deps.hooks)?.postToolBatch;
@@ -477,7 +477,7 @@ export class Runner {
477
477
  const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove } = createHarnessHandlers({
478
478
  prepared, stats, rs,
479
479
  deps: {
480
- spec, queue, internals, ident, parentToolCallId, subagentName,
480
+ spec, queue, internals, ident, mediaByteCapBytes: this.deps.mediaByteCapBytes,
481
481
  pushContent, emitCommitted, startedToolCallIds, toolStartAt, toolProgress, writeFamilyOf,
482
482
  toolLabels, postToolBatchHook, batchArgs, internalsNotifier,
483
483
  runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info), onNotice: this.deps.onNotice },
@@ -512,7 +512,7 @@ export class Runner {
512
512
  spec, queue, prepared, rs, stats, ident, emitCommitted, startedToolCallIds, toolLabels, timeout, compactionBrain, compactionBreaker, windowSafetyOptions,
513
513
  undrainedUserAtEnd, userInputLostAtEnd, final, threw, abortedLive, userInterruptedLive, runner: this.depsSeat, sessions: this.sessions,
514
514
  next: ({ threw, comp, answerSettled, owedUserInput }) => runSettleAndTeardown({
515
- spec, queue, prepared, resume, internals, rs, stats, ident, parentToolCallId, subagentName, loopLatch, timeout, ownCommittedTailRef,
515
+ spec, queue, prepared, resume, internals, rs, stats, ident, loopLatch, timeout, ownCommittedTailRef,
516
516
  emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, userInterruptedLive, answerSettled, strandedHumanAnswers, reasoningResolution, comp, compactionBreaker, owedUserInput,
517
517
  setResult, onSuggestions, onSuspend, runner: this.depsSeat, sessions: this.sessions,
518
518
  }),
@@ -31,15 +31,22 @@
31
31
  * (the model re-reads), which is the pre-existing cost, not a new failure.
32
32
  */
33
33
  import type { AgentMessage } from "../../internal/harness.js";
34
- /** The whole text of one file at the moment the transcript recorded it. */
35
- export interface TranscriptFileRecord {
34
+ import { type UserAttachmentObservation } from "../../internal/llm.js";
35
+ /** What the transcript proves about one file: its whole text at that moment (a tool's card), or the read-state entry an
36
+ * attachment frame's delivery committed (its record's observation, verbatim — hash, view and partial-view flag). */
37
+ export type TranscriptFileRecord = {
36
38
  /** The path as the tool recorded it — absolute (see {@link wholeFileRecordsFromTranscript}). */
37
39
  path: string;
38
40
  /** The file's ENTIRE text at that moment, in the decoded coordinate the fs tools hash. */
39
41
  content: string;
40
- /** The recording tool result's own timestamp — the most honest "last read at" available. */
42
+ /** The recording message's own timestamp — the most honest "last read at" available. */
41
43
  at: number;
42
- }
44
+ } | {
45
+ path: string;
46
+ /** The entry the delivery committed. */
47
+ observation: UserAttachmentObservation["entry"];
48
+ at: number;
49
+ };
43
50
  /**
44
51
  * Per-file, the last whole-file content the transcript proves — in transcript order, last record wins.
45
52
  *
@@ -57,6 +64,9 @@ export interface TranscriptFileRecord {
57
64
  * the narrowed model-facing union: this reads the branch as the session stores it, and every lane that
58
65
  * is not a tool result falls out on the first line of the loop.
59
66
  *
67
+ * A user-attachment frame (`UserMessage.attachment` with an observation) proves the read state its delivery committed,
68
+ * keyed by the canonical key it carries — the same entry the live commit wrote, so a rebuilt partial view stays one.
69
+ *
60
70
  * Paths are reported only when the tool recorded an ABSOLUTE one. A relative spelling was resolved
61
71
  * against the tracked working directory of that moment, which this task cannot reconstruct; re-
62
72
  * resolving it here could key the entry to a different file.
@@ -1,3 +1,4 @@
1
+ import { userAttachmentOf } from "../../internal/llm.js";
1
2
  import { isAbsoluteForFamily, pathFamilyOf } from "../../tools/fs/safety.js";
2
3
  import { applyRecordedEdit } from "../../tools/fs/fs-shared.js";
3
4
  import { normalizeFileText } from "../../tools/fs/encoding.js";
@@ -64,6 +65,13 @@ function wholeFileFromReadCard(rest) {
64
65
  export function wholeFileRecordsFromTranscript(messages) {
65
66
  const byPath = new Map();
66
67
  for (const m of messages) {
68
+ const attachment = userAttachmentOf(m);
69
+ if (attachment?.observation !== undefined) {
70
+ const { key, entry } = attachment.observation;
71
+ if (isAbsoluteForFamily(pathFamilyOf({ root: key }), key))
72
+ byPath.set(key, { path: key, observation: entry, at: m.timestamp });
73
+ continue;
74
+ }
67
75
  if (m.role !== "toolResult" || m.isError)
68
76
  continue;
69
77
  const card = cardOf(m.details);
@@ -13,6 +13,9 @@ export function streamSteerVerb(input) {
13
13
  const { spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner } = input;
14
14
  let steerChain = Promise.resolve();
15
15
  const steer = async (text, options) => {
16
+ if (options?.userAttachments !== undefined) {
17
+ throw steeringError("a steer carries no attachments — declare them in TaskSpec.userAttachments on the next objective", "steering.invalid_content");
18
+ }
16
19
  const trusted = options?.trusted ? true : false;
17
20
  if (trusted && sanitizeUntrustedText(text) !== text) {
18
21
  throw steeringError("trusted steering text must not contain a </system-reminder> tag", "steering.invalid_content");
@@ -32,9 +32,8 @@ export declare function batchResponseDigest(result: unknown): string | undefined
32
32
  * showed an empty body for every durable-approved call. Same projection = same source as the transcript.
33
33
  * Also the one place the gate outcome reaches a frame — see the parameter. */
34
34
  export declare function toolEndBodyFrom(result: unknown, isError: boolean,
35
- /** The leg's card projector — see the doc above. A parameter, never a module default: a body minted with the engine
36
- * set alone would silently drop every declared caller card and never announce. */
37
- structured: StructuredProjector,
35
+ /** The leg's card projector (never a module default: the engine set alone drops every declared caller card unannounced) and media budget. */
36
+ leg: Pick<OccurrenceFrames, "structured" | "mediaByteCapBytes">,
38
37
  /** The gate's record of the pass that admitted or refused this call, supplied by the CALLER of this
39
38
  * projection — the end projection ({@link emitSettled}) reads it off the call's occurrence (its latest gate
40
39
  * record). Deliberately a parameter and never derived from `result`: a tool's own `details`
@@ -63,10 +62,12 @@ export type OccurrenceFrame = Omit<Extract<TaskEvent, {
63
62
  }>, keyof TaskEventIdentity> | Omit<Extract<TaskEvent, {
64
63
  type: "tool_end";
65
64
  }>, keyof TaskEventIdentity>;
66
- /** Where an occurrence's frames go: the leg's push (it stamps the identity and picks the queue) and its card projector. */
65
+ /** Where an occurrence's frames go: the leg's push (it stamps the identity and picks the queue), its card projector and its media budget. */
67
66
  export interface OccurrenceFrames {
68
67
  push(frame: OccurrenceFrame): void;
69
68
  structured: StructuredProjector;
69
+ /** `RunnerDeps.mediaByteCapBytes` as given — the request strip's budget, read through the same `mediaBudgetBytes`; required, so no site reads a second one. */
70
+ mediaByteCapBytes: number | undefined;
70
71
  }
71
72
  /** The START projection: the call's occurrence opens and its start frame goes out. Every leg that announces a call
72
73
  * comes through here (the live loop's `tool_execution_start`, the resumed leg's approved call). */
@@ -33,9 +33,9 @@ export function batchResponseDigest(result) {
33
33
  ? `${text.slice(0, BATCH_RESPONSE_MAX_CHARS)}…[+${text.length - BATCH_RESPONSE_MAX_CHARS} chars truncated]`
34
34
  : text;
35
35
  }
36
- export function toolEndBodyFrom(result, isError, structured, gate, gatedCallIdOfRun) {
37
- const o = toolOutputFrom(result);
38
- const st = structured(result);
36
+ export function toolEndBodyFrom(result, isError, leg, gate, gatedCallIdOfRun) {
37
+ const o = toolOutputFrom(result, leg.mediaByteCapBytes);
38
+ const st = leg.structured(result);
39
39
  const det = isError ? readMember(result, "details") : undefined;
40
40
  const codeRaw = readMember(det, "code");
41
41
  const kindRaw = readMember(det, "errorKind");
@@ -71,7 +71,7 @@ export function emitSettled(table, start, end, frames) {
71
71
  toolName: occ.spelledName,
72
72
  ...(occ.label !== undefined ? { label: occ.label } : {}),
73
73
  isError,
74
- ...toolEndBodyFrom(end.result, isError, frames.structured, gate, end.gatedCallIdOfRun),
74
+ ...toolEndBodyFrom(end.result, isError, frames, gate, end.gatedCallIdOfRun),
75
75
  ...(gate?.disposition.kind === "denied" && occ.wireName !== undefined ? { wireName: occ.wireName } : {}),
76
76
  ...(end.notExecuted === true ? { notExecuted: true } : {}),
77
77
  });