@sema-agent/core 5.28.0 → 5.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +99 -3
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/observer.d.ts +3 -3
  4. package/dist/agents/peer-admission.d.ts +1 -1
  5. package/dist/agents/send-message-tool.js +2 -0
  6. package/dist/agents/subagent.d.ts +2 -0
  7. package/dist/agents/subagent.js +6 -0
  8. package/dist/agents/teacher.d.ts +1 -1
  9. package/dist/agents/teacher.js +2 -0
  10. package/dist/agents/verify.d.ts +6 -6
  11. package/dist/agents/verify.js +2 -0
  12. package/dist/bench/metrics.d.ts +1 -1
  13. package/dist/brain/retry.d.ts +1 -1
  14. package/dist/core/arg-summary.d.ts +1 -1
  15. package/dist/core/ask-class.d.ts +2 -2
  16. package/dist/core/ask-question.d.ts +1 -1
  17. package/dist/core/ask-question.js +14 -1
  18. package/dist/core/auto-compaction.d.ts +5 -1
  19. package/dist/core/auto-compaction.js +10 -1
  20. package/dist/core/background-shell.d.ts +5 -5
  21. package/dist/core/checkpoint-store.d.ts +65 -19
  22. package/dist/core/checkpoint-store.js +2 -1
  23. package/dist/core/file-snapshot-store.d.ts +1 -1
  24. package/dist/core/hooks.d.ts +15 -4
  25. package/dist/core/hooks.js +8 -2
  26. package/dist/core/human-input-projection.d.ts +2 -2
  27. package/dist/core/memory-admission.d.ts +2 -2
  28. package/dist/core/memory-engine/engine.d.ts +1 -1
  29. package/dist/core/memory-engine/file-backend.d.ts +2 -2
  30. package/dist/core/memory-engine/header-hints.d.ts +1 -1
  31. package/dist/core/memory-engine/layout.d.ts +2 -2
  32. package/dist/core/memory-recall.d.ts +1 -1
  33. package/dist/core/memory.d.ts +2 -2
  34. package/dist/core/oracle-isolation.d.ts +2 -2
  35. package/dist/core/permission-rule-consent.d.ts +1 -1
  36. package/dist/core/permission-rule-model.d.ts +1 -1
  37. package/dist/core/permission-rule-org.d.ts +1 -1
  38. package/dist/core/permission-rule-store.d.ts +1 -1
  39. package/dist/core/permission-rules.js +2 -2
  40. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  41. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  42. package/dist/core/runner/prepare-task.d.ts +29 -8
  43. package/dist/core/runner/prepare-task.js +118 -22
  44. package/dist/core/runner/runtask.d.ts +1 -1
  45. package/dist/core/runner/runtask.js +34 -8
  46. package/dist/core/runner/session-rule-policy.d.ts +4 -3
  47. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  48. package/dist/core/runner/tool-disclosure.d.ts +1 -1
  49. package/dist/core/runner/tool-output-projection.js +1 -1
  50. package/dist/core/scheduler.d.ts +4 -4
  51. package/dist/core/sensitive-path-policy.js +5 -16
  52. package/dist/core/session-policy-store.d.ts +3 -3
  53. package/dist/core/shared-memory/normalize.d.ts +1 -1
  54. package/dist/core/skill-tool-specifier.d.ts +1 -1
  55. package/dist/core/store-contracts/tool-result-store-contract.js +23 -0
  56. package/dist/core/task-notification.d.ts +2 -2
  57. package/dist/core/task-registry-agent.d.ts +9 -9
  58. package/dist/core/task-registry-shared.d.ts +4 -4
  59. package/dist/core/tighten-task-spec.js +18 -0
  60. package/dist/core/tool-errors.d.ts +2 -2
  61. package/dist/core/tool-policy.d.ts +64 -12
  62. package/dist/core/tool-policy.js +68 -8
  63. package/dist/core/tool-result-store.d.ts +2 -2
  64. package/dist/core/tool-result-store.js +3 -1
  65. package/dist/core/trace.d.ts +1 -1
  66. package/dist/core/types.d.ts +68 -13
  67. package/dist/core/untrusted-text.d.ts +6 -0
  68. package/dist/core/untrusted-text.js +1 -0
  69. package/dist/engine/compaction/compaction.d.ts +1 -1
  70. package/dist/engine/harness/messages.d.ts +1 -1
  71. package/dist/engine/harness/types.d.ts +12 -2
  72. package/dist/engine/llm/types.d.ts +1 -1
  73. package/dist/engine/loop/types.d.ts +2 -2
  74. package/dist/engine/session/import-validate.d.ts +1 -1
  75. package/dist/engine/session/log-digest.d.ts +1 -1
  76. package/dist/fixtures/index.d.ts +18 -2
  77. package/dist/fixtures/index.js +11 -0
  78. package/dist/index.d.ts +3 -1
  79. package/dist/index.js +3 -1
  80. package/dist/orchestration/goal.d.ts +1 -1
  81. package/dist/orchestration/run-workflow-tool.d.ts +21 -0
  82. package/dist/orchestration/run-workflow-tool.js +6 -3
  83. package/dist/orchestration/workflow-primitives.d.ts +10 -1
  84. package/dist/orchestration/workflow-primitives.js +12 -1
  85. package/dist/orchestration/workflow-types.d.ts +5 -4
  86. package/dist/orchestration/workflow.js +11 -3
  87. package/dist/prompt-assembly/artifact.d.ts +1 -1
  88. package/dist/prompt-assembly/epoch.js +2 -0
  89. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  90. package/dist/prompt-assembly/types.d.ts +4 -0
  91. package/dist/prompts/default.d.ts +14 -9
  92. package/dist/prompts/default.js +14 -4
  93. package/dist/scenarios/scenario-registry.d.ts +3 -3
  94. package/dist/stores/file/tool-result-store.d.ts +2 -2
  95. package/dist/tools/fs/bash-readonly-classifier.d.ts +23 -2
  96. package/dist/tools/fs/bash-readonly-classifier.js +11 -0
  97. package/dist/tools/fs/fs-bash.d.ts +8 -1
  98. package/dist/tools/fs/fs-bash.js +8 -3
  99. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  100. package/dist/tools/fs/fs-pdf.js +2 -2
  101. package/dist/tools/fs/fs-read.d.ts +1 -1
  102. package/dist/tools/fs/fs-read.js +11 -7
  103. package/dist/tools/fs/fs-search-tools.d.ts +5 -3
  104. package/dist/tools/fs/fs-search-tools.js +15 -8
  105. package/dist/tools/fs/fs-shared.d.ts +6 -2
  106. package/dist/tools/fs/fs-shared.js +8 -3
  107. package/dist/tools/fs/index.d.ts +18 -0
  108. package/dist/tools/fs/index.js +13 -2
  109. package/dist/tools/fs/read-deny.d.ts +105 -0
  110. package/dist/tools/fs/read-deny.js +151 -0
  111. package/dist/tools/fs/read-face.d.ts +43 -0
  112. package/dist/tools/fs/read-face.js +38 -0
  113. package/dist/tools/fs/repo-map.d.ts +3 -1
  114. package/dist/tools/fs/repo-map.js +11 -5
  115. package/dist/tools/fs/safety.d.ts +34 -12
  116. package/dist/tools/fs/safety.js +88 -7
  117. package/dist/tools/fs/search.d.ts +54 -5
  118. package/dist/tools/fs/search.js +103 -21
  119. package/dist/tools/scheduler-tools.d.ts +1 -1
  120. package/dist/tools/task-list.d.ts +1 -1
  121. package/dist/tools/web.d.ts +3 -3
  122. package/package.json +1 -1
@@ -283,7 +283,7 @@ export declare function buildToolResultRef(sessionId: string, toolCallId: string
283
283
  */
284
284
  export declare const MAX_MINTED_TOOL_RESULT_REF_CHARS: number;
285
285
  /**
286
- * Backlog #119 (adversarial review, round 2) — the CONTENT coordinate of an offload ref.
286
+ * Backlog #119 (falsification review, round 2) — the CONTENT coordinate of an offload ref.
287
287
  *
288
288
  * A tool-call id carries no cross-turn uniqueness contract: a BYOM brain legitimately mints recurring
289
289
  * ids like `call-1`, and the Runner shares one store across a session's tasks on purpose. Keyed on
@@ -397,7 +397,7 @@ export declare const OFFLOAD_TOOL_NAME = "ReadToolResult";
397
397
  * - **revival**: the put and a session purge can race. When the put loses, the marker write lands
398
398
  * AFTER the purge — re-inserting a row into a session the deployment just deleted, alive until the
399
399
  * backend's own TTL. A failure arm must not be able to out-write a deletion;
400
- * - **retry poisoning**: the per-request re-clear legitimately re-invokes this persist with the same
400
+ * - **retry contamination**: the per-request re-clear legitimately re-invokes this persist with the same
401
401
  * (toolCallId, fullText) — the write-once retry path. With a marker occupying the ref, that
402
402
  * retry's put of the REAL bytes hit the write-once no-op (same owner ⇒ keep what is there), so a
403
403
  * transient first failure served the marker forever even though the full text was still in hand.
@@ -366,7 +366,9 @@ async function offloadOversizedDetailStrings(details, store, thresholdChars, ses
366
366
  const value = walk(details, []);
367
367
  const settled = await Promise.allSettled(pending.map((p) => p.done));
368
368
  const notices = new Map();
369
- pending.forEach((p, i) => void notices.set(p.marker, p.render(settled[i].status === "fulfilled")));
369
+ pending.forEach((p, i) => {
370
+ notices.set(p.marker, p.render(settled[i].status === "fulfilled"));
371
+ });
370
372
  const seen = new Set();
371
373
  const finalize = (node) => {
372
374
  if (typeof node !== "object" || node === null)
@@ -283,7 +283,7 @@ export type TraceEvent = {
283
283
  * which anchors on the FIRST DELTA and reads 0 for a zero-delta call). Distinguishes an
284
284
  * in-flight call cut before its first delta (present, latencyMs 0, stopReason "aborted")
285
285
  * from a call that was never issued (absent). Compaction-summarizer rows never carry it (they
286
- * bypass the loop's per-call closures). */
286
+ * circumvent the loop's per-call closures). */
287
287
  callStartedAt?: number;
288
288
  /** ABSENT when THIS call's serving model was unpriced (RB-368 — no fabricated 0). */
289
289
  costMicroUsd?: number;
@@ -599,7 +599,7 @@ export interface ToolExecuteContext {
599
599
  * ruled 2026-08-04 — the RUNNING task's EFFECTIVE synchronous approver (`spec.onAsk ?? deps.onAsk`,
600
600
  * the exact value this task's own gate resolves an `ask` at), Runner-filled read-only on the same
601
601
  * trusted seat as {@link principal}: never a model/tool argument, so a worker can neither substitute
602
- * nor suppress it. Absent when the run has no approver at all (headless) — the seat is never forged.
602
+ * nor suppress it. Absent when the run has no approver at all (headless) — the seat is never caller-declared.
603
603
  *
604
604
  * A delegation tool (`createSubagentTool`) forwards it into every child it spawns (sync / background /
605
605
  * fork), wrapped so the approver learns which delegation raised the ask ({@link
@@ -619,7 +619,7 @@ export interface ToolExecuteContext {
619
619
  * The RUNNING task's EFFECTIVE content-ask seam (`spec.onQuestion ?? deps.onQuestion` — the exact
620
620
  * value this task's own `AskUserQuestion` resolves at), Runner-filled read-only on the same trusted
621
621
  * seat as {@link onAsk}: never a model/tool argument, so a worker can neither substitute nor suppress
622
- * it. Absent when the run has no live question face at all — the seat is never forged.
622
+ * it. Absent when the run has no live question face at all — the seat is never caller-declared.
623
623
  *
624
624
  * A delegation tool (`createSubagentTool`) forwards it into every child it spawns (sync / background /
625
625
  * fork), because the tool's own MOUNT predicate is keyed on it: without the seat, a deployment that
@@ -650,6 +650,21 @@ export interface ToolExecuteContext {
650
650
  * read-only boundary ended at one level. Strictly a capability REMOVAL — it can never widen a child.
651
651
  */
652
652
  handsReadOnly?: true;
653
+ /**
654
+ * design/199 — the READ-face clamp carrier ({@link TaskSpec.readFace}): present (as "roots") ONLY
655
+ * when this run RESOLVED to the roots face, so a delegation tool pins every child spec to roots —
656
+ * without it a roots-narrowed parent's children would resolve the shared deployment seat (possibly
657
+ * "open") and read wider than the parent could. Strictly a tightening carrier: an open parent adds
658
+ * no key, and nothing in the delegation lane can spell "open". F10 ruling (v1): this seat exists
659
+ * ONLY as the delegation carrier — no tool adapts its model-facing behavior on it.
660
+ */
661
+ readFace?: "roots";
662
+ /**
663
+ * design/199 件B — the parent task's OWN deny-set additions ({@link TaskSpec.readDenyPatterns}),
664
+ * traveling the delegation tree add-only (a child judges at least every entry its parent judged).
665
+ * Runner-filled trusted seat, never a model argument.
666
+ */
667
+ readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
653
668
  /**
654
669
  * The HARD-HEADLESS clamp ({@link TaskSpec.interactiveTools} set to `false` — "never mount a
655
670
  * human-facing tool on this run, whatever faces exist"), Runner-filled read-only on the same trusted
@@ -775,7 +790,7 @@ export interface ToolExecuteContext {
775
790
  * CC's classifier covers two more scenes beyond a single tool-call ask that sema had NOT ported: a
776
791
  * delegation tool reviews the CHILD's prompt+toolset BEFORE spawning it (CC `Ipd` — a main agent
777
792
  * blocked from a dangerous action directly could otherwise write it into a sub-agent's prompt and
778
- * have the child execute it, a delegation-based classifier bypass), and reviews the child's COMPLETED
793
+ * have the child execute it, a delegation-based classifier circumvention), and reviews the child's COMPLETED
779
794
  * work when it hands control back (CC `ein` — flags the main agent with a security warning before it
780
795
  * acts on unreviewed sub-agent output). `createSubagentTool`/the workflow spawn legs read this at
781
796
  * BOTH points and thread it into the child (never a model/tool argument), same posture as
@@ -860,7 +875,7 @@ export interface ToolExecuteContext {
860
875
  * transcript classes too (text_delta/reasoning_delta/tool_start/tool_end); other event types never cross it.
861
876
  * The delegation lane's OWN tap is trusted and forwards the child's FULL event stream (bg frames tagged
862
877
  * with bgAgentId). ⚠️ Forwarded ticks are UNTRUSTED display hints — any
863
- * tool holding this ctx could forge one, so a consumer validates `parentTaskId` against its known runs.
878
+ * tool holding this ctx could self-declare one, so a consumer validates `parentTaskId` against its known runs.
864
879
  */
865
880
  forwardEvent?: (event: TaskEvent) => void;
866
881
  /**
@@ -882,7 +897,7 @@ export interface ToolExecuteContext {
882
897
  /**
883
898
  * design/135 §0 — the TRUSTED per-call worktree-isolation lane for the Agent tool (CC
884
899
  * `isolation: "worktree"`). The Runner fills it (prepare-task builds it over the run's ExecutionEnv +
885
- * task root via `createSubagentWorktreeHelper`) for write-capable runs; a tool cannot forge it and the
900
+ * task root via `createSubagentWorktreeHelper`) for write-capable runs; a tool cannot self-declare it and the
886
901
  * model only REQUESTS isolation via the Agent tool's `isolation` parameter — the capability itself is
887
902
  * never a model argument. Absent ⇒ the deployment cannot mint worktrees (read-only run / outside a
888
903
  * Runner) and the Agent tool reports isolation as honestly unavailable.
@@ -1137,7 +1152,7 @@ export interface McpServerSpec {
1137
1152
  * design/99 §E23 — opt in to INBOUND elicitation for THIS server: when `true` AND a {@link RunnerDeps.onElicit}
1138
1153
  * seam is wired, core advertises the `elicitation` capability to this server and registers a handler, so the
1139
1154
  * server may send an `elicitation/create` request to ask the END USER for input mid-tool-call. **Default OFF
1140
- * (fail-closed)**: never advertise unless explicitly wired, so a hostile/over-eager server cannot pull a human
1155
+ * (fail-closed)**: never advertise unless explicitly wired, so a untrusted/over-eager server cannot pull a human
1141
1156
  * into the loop. v1 is LIVE-ONLY — `onElicit` answers synchronously on the open connection (no durable
1142
1157
  * suspend; a durable/cross-replica elicitation needs the deferred E22 longer-lived connection).
1143
1158
  */
@@ -1247,7 +1262,7 @@ export interface McpElicitResponse {
1247
1262
  * 🔴 THROTTLING IS THE DEPLOYMENT'S JOB: once a server is opted in, it may issue elicitations at any frequency
1248
1263
  * (one per tool call, or many) — core imposes NO rate/count limit (an elicitation is not a tool call and does
1249
1264
  * not ride the policy gate). `onElicit` is the choke point: throttle, aggregate, or `decline`/`cancel` an
1250
- * over-eager or hostile server here (the request's `AbortSignal` is forwarded so a deadline can cancel it).
1265
+ * over-eager or untrusted server here (the request's `AbortSignal` is forwarded so a deadline can cancel it).
1251
1266
  */
1252
1267
  export type OnElicit = (req: McpElicitRequest, signal?: AbortSignal) => Promise<McpElicitResponse>;
1253
1268
  /**
@@ -1447,7 +1462,7 @@ export interface TaskSpec {
1447
1462
  * 🔴 **Trust contract (design/53 — authority stays in the control plane):** this is a control-plane value,
1448
1463
  * **NOT model/worker input.** Set it only from the authenticated task-submission context; it is not exposed
1449
1464
  * as a tool argument and the model cannot read or change it (a worker filling its own principal = a privilege
1450
- * bypass). Treat it as write-once for the run. The MCP server MUST trust this header only on the Runner's
1465
+ * circumvention). Treat it as write-once for the run. The MCP server MUST trust this header only on the Runner's
1451
1466
  * authenticated/TLS channel and default to deny/public when it is absent — never default-admin.
1452
1467
  *
1453
1468
  * 🔴🔴 **This is the GOVERNANCE key.** Per-principal entitlements are resolved off this value:
@@ -1455,7 +1470,7 @@ export interface TaskSpec {
1455
1470
  * self-orchestration / `RunWorkflow` is permitted). So a caller MUST set `spec.principal` to a
1456
1471
  * **cryptographically VERIFIED** identity — **never a raw, spoofable request header**. A deployment that holds
1457
1472
  * both an unverified header value AND a verified one (e.g. a "direct-door" path) MUST pass the VERIFIED one
1458
- * here: keying a gate on a spoofable principal is a multi-tenant governance BYPASS (a forged header would
1473
+ * here: keying a gate on a spoofable principal is a multi-tenant governance BYPASS (a caller-declared header would
1459
1474
  * inherit another tenant's entitlements). Core cannot verify identity itself (no crypto context) — it trusts
1460
1475
  * this value by contract; getting it wrong is a privilege-escalation hole, not a core bug.
1461
1476
  */
@@ -2052,7 +2067,7 @@ export interface TaskSpec {
2052
2067
  /**
2053
2068
  * Restrict the injected "hand" band (design/44) to its effect:read tools for this task — read_file /
2054
2069
  * grep / glob (+ `bash_readonly` when a shell is available); edit_file / write_file / `bash` are not
2055
- * mounted. This is the verifier read-only boundary (§6): an adversarial verifier inspects but must not
2070
+ * mounted. This is the verifier read-only boundary (§6): an falsification-style verifier inspects but must not
2056
2071
  * modify the project. No effect unless an `executionEnv` is injected. Default false.
2057
2072
  */
2058
2073
  handsReadOnly?: boolean;
@@ -2109,6 +2124,27 @@ export interface TaskSpec {
2109
2124
  * config read.
2110
2125
  */
2111
2126
  additionalReadDirectories?: string[];
2127
+ /**
2128
+ * design/199 件B — TASK-layer ADDITIONS to the built-in sensitive-path READ deny set
2129
+ * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}). Judged by the
2130
+ * structured read faces (Read/Grep/Glob/RepoMap, their traversals, the classify shell gate's
2131
+ * auto-allow probe and the compaction attachment reader) in BOTH containment modes. Add-only
2132
+ * everywhere (D-4 zero-shrink ruling): entries here UNION with the built-ins and the deployment's
2133
+ * {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); nothing can remove a
2134
+ * built-in. Bad entry shapes refuse loudly at prepare (#123). The write faces are untouched.
2135
+ */
2136
+ readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
2137
+ /**
2138
+ * design/199 件A — the TASK layer's read-face containment declaration
2139
+ * ({@link import("../tools/fs/read-face.js").ReadFace}). "roots" pins this task (and, via the
2140
+ * tighten-only clamp, its whole delegation subtree) to the historical containment; "open" lifts
2141
+ * the roots judgment for the structured read faces — refused loudly under org governance (the
2142
+ * task layer only tightens there; the deployment opens via {@link RunnerDeps.readFace}) and
2143
+ * beside `handsReadOnly: true` (the verifier containment is load-bearing). Absent ⇒ the
2144
+ * deployment seat, then the engine default ("roots" — D-1b: an upgrade never opens implicitly).
2145
+ * Bad values refuse loudly (#123). Never affects the write faces.
2146
+ */
2147
+ readFace?: import("../tools/fs/read-face.js").ReadFace;
2112
2148
  /**
2113
2149
  * design/80 D-B — opt in to PLAN MODE: mount the first-party `present_plan` tool (CC `ExitPlanMode` parity).
2114
2150
  * The model calls it to present a plan; the engine then pauses with a durable `plan_review` checkpoint
@@ -3278,7 +3314,7 @@ export type TaskEvent = ({
3278
3314
  * `errorKind` leg is what makes a LOOP-THROWN error's frame classifiable: the loop's thrown-error
3279
3315
  * fold and the resume legs write the discriminator under that name. Lifted so a consumer never
3280
3316
  * has to parse the (contract-stable) result text. Engine-minted vocabulary today includes
3281
- * `"tool.not_found"` (unknown tool name) and `"gate.parked"` (an abort short-circuit poisoned
3317
+ * `"tool.not_found"` (unknown tool name) and `"gate.parked"` (an abort short-circuit contaminated
3282
3318
  * this call because a durable gate parked the batch — the "Operation aborted" family). Additive:
3283
3319
  * absent on error frames minted before this field existed, and on error results whose details
3284
3320
  * carry no string discriminator under either name.
@@ -3938,7 +3974,7 @@ export interface RuntimeCaps {
3938
3974
  allowWorkflows?: boolean;
3939
3975
  /** `false` DENIES fork (`Agent(subagent_type:"fork")` — design/136: the only fork face since the standalone
3940
3976
  * tool was retired; the deny is the honest `fork.disabled` result, and since 1.256 it binds the Agent-fork
3941
- * route too, closing the 1.254 governance bypass) for this principal server-side — even on a fork-capable
3977
+ * route too, closing the 1.254 governance circumvention) for this principal server-side — even on a fork-capable
3942
3978
  * (durable-store) deployment. Fork is a compute-amplification primitive (a principal spawns
3943
3979
  * context-inheriting children, each spending on its own quota), so a multi-tenant operator gates it
3944
3980
  * per-principal the SAME way as {@link allowWorkflows}. `undefined`/`true` = no per-principal restriction
@@ -4261,10 +4297,29 @@ export interface RunnerDeps {
4261
4297
  * `createHandsToolkit` 整厂(部署自装配塞 `spec.tools` 会被 Runner 的自动装配同名覆盖=反模式;
4262
4298
  * CC 亦无 options-bag 开放面,走单例+hook)。只开放安全子集,Runner 装配时合成:
4263
4299
  * - `beforeWrite` 与引擎的 MemoryEngine 写扫描门 **COMPOSE 而非替换**(部署 hook 先判,拒即拒;
4264
- * 放行后引擎门仍全量生效——写保护不可能被部署配置绕过)。
4300
+ * 放行后引擎门仍全量生效——写保护不可能被部署配置规避)。
4265
4301
  * Runner 私有编排字段(taskRegistry/detachHub/execClamp/cwdRef/…)不在此面,永不开放。
4266
4302
  */
4267
4303
  hands?: HandsBandOptions;
4304
+ /**
4305
+ * design/199 件B — DEPLOYMENT-layer ADDITIONS to the built-in sensitive-path READ deny set
4306
+ * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}); see
4307
+ * {@link TaskSpec.readDenyPatterns} for the judged surfaces. Add-only (D-4 zero-shrink ruling):
4308
+ * unions with the built-ins and any task-layer additions; there is no whole-table replacement
4309
+ * escape hatch, and `[]` ≡ absent. Bad entry shapes refuse loudly at prepare (#123).
4310
+ */
4311
+ readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
4312
+ /**
4313
+ * design/199 件A — the DEPLOYMENT's read-face declaration
4314
+ * ({@link import("../tools/fs/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
4315
+ * task layer and the resolution order). An interactive single-user product declares
4316
+ * `readFace: "open"` here in one line; the engine default stays "roots" (D-1b). Under org
4317
+ * governance this seat may still open (it is the deployment's own declaration); the task layer
4318
+ * may not. Beside a read-only (verifier) mount this seat's "open" silently CLAMPS to roots (a
4319
+ * deployment default cannot override a load-bearing containment wall) — TaskSpec.readFace's own
4320
+ * "open" there is the genuine per-task contradiction, and that one still refuses loudly (#123).
4321
+ */
4322
+ readFace?: import("../tools/fs/read-face.js").ReadFace;
4268
4323
  /**
4269
4324
  * design/96 §20 (P1a) — DEPLOYMENT-DECLARED training knowledge cutoff per model id (e.g.
4270
4325
  * `{ "qwen-3.5-35b": "2025-01", "deepseek-pro": "2024-07" }`). When the resolved model id is present, the
@@ -179,6 +179,12 @@ export declare function defuseControlChars(text: string): string;
179
179
  * Defense-in-depth, NOT a guarantee (same posture as the rest of this module).
180
180
  */
181
181
  export declare function inlineUntrusted(text: string, maxLen?: number): string;
182
+ /** The one body bound every "reviewer note" relay passes to {@link delimitUntrusted} — the decider's
183
+ * free text attached to a deny (sync `AskOutcome.reason` and the durable `ResumeOutcome` `reason`
184
+ * legs alike). A note is steering, not payload: unbounded it can flood the transcript/context the
185
+ * refusal lands in. 2048 code points matches the note cap the interactive card layer already
186
+ * enforces on its own input, so a note that fit the card arrives whole. */
187
+ export declare const REVIEWER_NOTE_MAX_BODY = 2048;
182
188
  /**
183
189
  * Wrap untrusted text in a clearly labeled opaque fence. The consuming prompt should instruct the model to
184
190
  * treat everything inside as untrusted data — never as instructions. Sanitizes internally (system-reminder
@@ -111,6 +111,7 @@ export function inlineUntrusted(text, maxLen = LABEL_MAX) {
111
111
  function sanitizeLabel(label) {
112
112
  return inlineUntrusted(label, LABEL_MAX);
113
113
  }
114
+ export const REVIEWER_NOTE_MAX_BODY = 2048;
114
115
  export function delimitUntrusted(label, text, maxBody) {
115
116
  const tag = sanitizeLabel(label);
116
117
  let body = defuseFenceMarkers(sanitizeUntrustedText(text));
@@ -201,7 +201,7 @@ export interface CompactionForkContext {
201
201
  }
202
202
  /** Extract the fork-form summary from its envelope; `undefined` = non-conforming response.
203
203
  *
204
- * Grammar (adversarial round 3, both arms pinned): after stripping the permitted `<analysis>`
204
+ * Grammar (falsification-style round 3, both arms pinned): after stripping the permitted `<analysis>`
205
205
  * scratch blocks, the ENTIRE remaining response must be one envelope — text outside it is a
206
206
  * rejection, not noise ("<summary>placeholder</summary> I refuse" must not pass as "placeholder").
207
207
  * The inner match is GREEDY with the closing tag anchored at the END, because the summary quotes
@@ -18,7 +18,7 @@ export declare function createCustomMessage(customType: string, content: string
18
18
  * product, not authored content. */
19
19
  export declare const NORMALIZED_CONTENT_PREFIX = "[invalid content block normalized to text]";
20
20
  /**
21
- * Fail-soft salvage for a possibly-poisoned LLM message ([868] 件b): any structurally-invalid
21
+ * Fail-soft salvage for a possibly-contaminated LLM message ([868] 件b): any structurally-invalid
22
22
  * `content` shape (bare object as the whole field, or a typeless/unknown-type block in the array) is
23
23
  * normalized to a prefixed text block. Legal messages are returned BY REFERENCE, untouched. Shared by
24
24
  * `convertToLlm` (provider leg) and `serializeConversation` (compaction leg) — one helper, two mouths.
@@ -106,7 +106,7 @@ export declare class FileError extends Error {
106
106
  export type ExecutionErrorCode = "aborted" | "timeout"
107
107
  /** The shell binary/config could not be resolved BEFORE spawning — the command never started. */
108
108
  | "shell_unavailable"
109
- /** CONTRACT (2026-07-13, TB cwd-poison batch): spawn_error MUST mean the command NEVER STARTED (the
109
+ /** CONTRACT (2026-07-13, TB cwd-contamination batch): spawn_error MUST mean the command NEVER STARTED (the
110
110
  * OS-level spawn itself failed: dead cwd, missing binary, EMFILE/ENOMEM…). The Bash tool reroutes
111
111
  * spawn_error + shell_unavailable to a STRUCTURAL error (throw → isError:true) and runs a dead-cwd
112
112
  * diagnosis on them; an env that maps "command ran and failed" onto spawn_error breaks that
@@ -349,6 +349,16 @@ export interface ExecutionEnv extends FileSystem, Shell {
349
349
  * mechanism exists to prevent.
350
350
  */
351
351
  readonly lifetimeStartedAt?: number;
352
+ /**
353
+ * #211 (design/199 seam) — does this environment's path NAMESPACE live on the CONTROL-PLANE HOST's
354
+ * filesystem? Declared by the ADAPTER: `false` for a virtual/sandboxed env whose paths never map
355
+ * onto the host (an in-memory env, a container the host cannot mount) — consumers that record
356
+ * host-path receipts (the workflow workspace observer's `worktreeDir`) then refuse to present this
357
+ * env's cwd as a host path. Omitted ⇒ the historical structural inference stays in force
358
+ * (isRemoteExecutionEnv shape detection; a plain NodeExecutionEnv is host-local). `true` is a
359
+ * harmless explicit spelling of the local default.
360
+ */
361
+ readonly hostLocalPaths?: boolean;
352
362
  }
353
363
  /** Base fields shared by append-only session tree entries. */
354
364
  export interface SessionTreeEntryBase {
@@ -525,7 +535,7 @@ export declare function isValidModelChange(e: {
525
535
  }): boolean;
526
536
  /** RB-128: a `label` / `session_info.name` that is not a string reaches `.trim()` inside
527
537
  * `BaseSessionStorage`'s CONSTRUCTOR — and the file backend validates BEFORE constructing any storage, so
528
- * the poison lands on disk and every later `open()` throws a raw `TypeError` (not even a `SessionError`).
538
+ * the contamination lands on disk and every later `open()` throws a raw `TypeError` (not even a `SessionError`).
529
539
  * Unrecoverable through the API, which is why this belongs at the import door. */
530
540
  export declare function isOptionalDisplayString(v: unknown, max?: number): boolean;
531
541
  export declare function normalizeAnnouncedListing(v: unknown): {
@@ -36,7 +36,7 @@ export interface ResilienceOptions {
36
36
  allowFailover?: boolean;
37
37
  /** true = this task ignores an OPEN circuit breaker's fast-fail (the probe goes through; outcomes
38
38
  * are STILL recorded so shared observation is undiminished). Operator-facing — an open breaker
39
- * exists to protect the provider; bypass only where true failure shape matters (benchmarks). */
39
+ * exists to protect the provider; circumvention only where true failure shape matters (benchmarks). */
40
40
  bypassBreaker?: boolean;
41
41
  }
42
42
  /** 1.296 件A — per-call advisory stall-watchdog values (see {@link StreamOptions.stallTimeouts}). */
@@ -42,7 +42,7 @@ export type AgentToolCall = Extract<AssistantMessage["content"][number], {
42
42
  *
43
43
  * Returning `{ updatedInput }` (without `block`) REWRITES the arguments the tool executes with — the
44
44
  * loop replaces the validated args with `updatedInput` and **re-runs `validateToolArguments` on it**
45
- * before execution, so a rewrite can never smuggle schema-invalid args into `tool.execute()`. This is
45
+ * before execution, so a rewrite can never carry across schema-invalid args into `tool.execute()`. This is
46
46
  * the generic carrier the higher layer uses for arg redaction/clamping (design/37); the loop itself
47
47
  * stays policy-agnostic. `block` wins over `updatedInput` if both are set.
48
48
  *
@@ -377,7 +377,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
377
377
  * family). When supplied, a non-undefined return value is attached as the minted result's
378
378
  * `details` — a host that KNOWS why the signal fired (e.g. a durable approval gate parked the
379
379
  * batch) stamps a structured marker such as `{ code: "gate.parked" }`, so a consumer can
380
- * distinguish a park poison frame from a plain cancel WITHOUT parsing the result text. The
380
+ * distinguish a park contamination frame from a plain cancel WITHOUT parsing the result text. The
381
381
  * abort texts themselves are a consumer contract and never change here. Evaluated at mint time
382
382
  * (after the abort fired), so the host reads its own post-abort state, not a snapshot.
383
383
  */
@@ -7,7 +7,7 @@ export interface ImportValidatorOptions {
7
7
  * re-assert who spoke in a history it did not witness (`hostAsserted:true` is an ingress-derived
8
8
  * claim, and import is not an ingress). `true` = the operator DECLARES the source trusted
9
9
  * (e.g. a same-deployment backend migration) and well-formed assertions are preserved verbatim;
10
- * shape-poisoned ones are still stripped like the sibling metadata fields.
10
+ * shape-contaminated ones are still stripped like the sibling metadata fields.
11
11
  */
12
12
  preserveActorAssertions?: boolean;
13
13
  }
@@ -43,7 +43,7 @@ import type { SessionTreeEntry } from "../harness/types.js";
43
43
  * path, exactly like a value mismatch.
44
44
  *
45
45
  * ## What is hashed, and why not the engine's canonical serializer
46
- * v1 hashed `canonicalize` (the approval-binding serializer). An adversarial review of the shipped v1 found
46
+ * v1 hashed `canonicalize` (the approval-binding serializer). An falsification review of the shipped v1 found
47
47
  * that choice unsound for a WIRE digest, on four counts that all trace to the same root — that serializer is
48
48
  * built for a single runtime comparing a value to ITSELF, not for two runtimes comparing what they hold:
49
49
  *
@@ -34,11 +34,27 @@ export declare const ASK_REQUEST_HOST: AskRequest;
34
34
  * - `fromSubagent: true` — the explicit, unforgeable discriminator (1.378);
35
35
  * - `sourceAgentName` — display identity (UNTRUSTED-for-display; redact + bound before render);
36
36
  * present only when the spawn named the child (an unnamed child omits it);
37
- * - `sourceTaskId` — the CHILD's session id (≠ the host's).
37
+ * - `sourceTaskId` — the CHILD's session id (≠ the host's);
38
+ * - `isDelegatedChild: true` — the refusal-POSTURE fact (the raw trusted internals fact, set by
39
+ * every core spawn leg — a FORK carries it too, its interaction contract being one-shot). NOT a
40
+ * synonym for `fromSubagent` (`parentTaskId` presence): a directly-started workflow's children
41
+ * carry this one WITHOUT `fromSubagent`. Downstream must not fold the two into one flag.
38
42
  */
39
43
  export declare const ASK_REQUEST_SUBAGENT: AskRequest;
40
- /** Both AskRequest forms, keyed for iteration in downstream key-set diff gates. */
44
+ /**
45
+ * AskRequest, DIRECTLY-STARTED WORKFLOW CHILD form — the shape when a workflow with NO launching
46
+ * tool call (e.g. cron-triggered) spawns an agent whose gate raises the ask. THE form that keeps
47
+ * `isDelegatedChild` and `fromSubagent` from being conflated downstream:
48
+ * - `isDelegatedChild: true` — refusals speak the child posture to it;
49
+ * - `fromSubagent` ABSENT — there is no nameable `parentTaskId` and no id is ever fabricated;
50
+ * - `sourceAgentName` — the workflow spawn's display label.
51
+ * A consumer that requires the two flags together, or classifies by `fromSubagent` alone, misreads
52
+ * exactly this request.
53
+ */
54
+ export declare const ASK_REQUEST_WORKFLOW_CHILD: AskRequest;
55
+ /** All AskRequest forms, keyed for iteration in downstream key-set diff gates. */
41
56
  export declare const ASK_REQUEST_FIXTURES: {
42
57
  readonly host: AskRequest;
43
58
  readonly subagent: AskRequest;
59
+ readonly workflowChild: AskRequest;
44
60
  };
@@ -14,8 +14,19 @@ export const ASK_REQUEST_SUBAGENT = {
14
14
  sourceTaskId: "<issuing-session-id>",
15
15
  fromSubagent: true,
16
16
  sourceAgentName: "<agent-name>",
17
+ isDelegatedChild: true,
18
+ };
19
+ export const ASK_REQUEST_WORKFLOW_CHILD = {
20
+ toolName: "target",
21
+ toolCallId: "<tool-call-id>",
22
+ args: { "<arg-key>": "<arg-value>" },
23
+ message: "<message>",
24
+ sourceTaskId: "<issuing-session-id>",
25
+ sourceAgentName: "<agent-name>",
26
+ isDelegatedChild: true,
17
27
  };
18
28
  export const ASK_REQUEST_FIXTURES = {
19
29
  host: ASK_REQUEST_HOST,
20
30
  subagent: ASK_REQUEST_SUBAGENT,
31
+ workflowChild: ASK_REQUEST_WORKFLOW_CHILD,
21
32
  };
package/dist/index.d.ts CHANGED
@@ -87,6 +87,8 @@ export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, Ch
87
87
  export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
88
88
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
89
89
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
90
+ export { READ_FACE_DEFAULT_DENY_ENTRIES, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, } from "./tools/fs/index.js";
91
+ export { resolveReadFace, type ReadFace, type ReadFaceInputs } from "./tools/fs/index.js";
90
92
  export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
91
93
  export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
92
94
  export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, type UsageWindow, type UsageWindowStore, type UsageWindowReading, type UsageWindowRecord, type UsageSlot, type UsageBucketRow, } from "./core/usage-window-store.js";
@@ -168,7 +170,7 @@ export { encodeSurfacedKey, buildManifestText, validateSelectedIds, composeSelec
168
170
  export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATION_SEARCH_LIMIT, DEFAULT_CONSOLIDATION_MAX_NOTES, DEFAULT_CONSOLIDATION_TIMEOUT_SEC, normalizeForExactMatch, type ConsolidationParams, type ConsolidationStats, type ConsolidationNote, type ConsolidationLLM, } from "./core/runner/memory-consolidation.js";
169
171
  export { consolidateScope, advanceCursorAfterInline, type ConsolidateScopeDeps, type ConsolidateScopeOptions, } from "./core/consolidate-scope.js";
170
172
  export { DEFAULT_COMPACTION_INSTRUCTIONS } from "./core/auto-compaction.js";
171
- export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, type EnvironmentFacts, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, type PromptBlock, analyzePromptCacheFriendliness, assertPromptCacheFriendly, type PromptProvider, type StablePromptContext, type PromptCacheReport, type PromptTextDeclaration, } from "./prompts/default.js";
173
+ export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, type EnvironmentFacts, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, type PromptBlock, analyzePromptCacheFriendliness, assertPromptCacheFriendly, type PromptProvider, type StablePromptContext, type PromptCacheReport, type PromptTextDeclaration, } from "./prompts/default.js";
172
174
  export { SUPERVISOR_PROMPT, ORCHESTRATION_GUIDANCE, ORCHESTRATION_AWARENESS, GOAL_COMPLETION_GUIDANCE } from "./prompts/supervisor.js";
173
175
  export { compose, validatePack } from "./prompt-assembly/composer.js";
174
176
  export { SEMA_DEFAULT_PACK } from "./prompt-assembly/packs/sema-default.js";
package/dist/index.js CHANGED
@@ -68,6 +68,8 @@ export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
68
68
  export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
69
69
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, } from "./tools/fs/index.js";
70
70
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
71
+ export { READ_FACE_DEFAULT_DENY_ENTRIES, compileReadDeny, } from "./tools/fs/index.js";
72
+ export { resolveReadFace } from "./tools/fs/index.js";
71
73
  export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, } from "./core/tool-result-store.js";
72
74
  export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, } from "./core/checkpoint-store.js";
73
75
  export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, } from "./core/usage-window-store.js";
@@ -131,7 +133,7 @@ export { encodeSurfacedKey, buildManifestText, validateSelectedIds, composeSelec
131
133
  export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATION_SEARCH_LIMIT, DEFAULT_CONSOLIDATION_MAX_NOTES, DEFAULT_CONSOLIDATION_TIMEOUT_SEC, normalizeForExactMatch, } from "./core/runner/memory-consolidation.js";
132
134
  export { consolidateScope, advanceCursorAfterInline, } from "./core/consolidate-scope.js";
133
135
  export { DEFAULT_COMPACTION_INSTRUCTIONS } from "./core/auto-compaction.js";
134
- export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, analyzePromptCacheFriendliness, assertPromptCacheFriendly, } from "./prompts/default.js";
136
+ export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, analyzePromptCacheFriendliness, assertPromptCacheFriendly, } from "./prompts/default.js";
135
137
  export { SUPERVISOR_PROMPT, ORCHESTRATION_GUIDANCE, ORCHESTRATION_AWARENESS, GOAL_COMPLETION_GUIDANCE } from "./prompts/supervisor.js";
136
138
  export { compose, validatePack } from "./prompt-assembly/composer.js";
137
139
  export { SEMA_DEFAULT_PACK } from "./prompt-assembly/packs/sema-default.js";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * design/96 §C (S2) — **Goal 模式**:LLM 自报完成 + core 校验续跑(CC goal 对标:目标 + 完成判定 + 续跑)。
3
3
  * `runGoal` 是 `runRepairLoop`/`verifyCompleted` 的 SIBLING —— thin composition over `runner.runTask`,零
4
- * Runner core 改动。详 `design/96-references/GOAL-IMPLEMENTER-SPEC.md`(v3,codex r1+r2 异源对抗收敛)。
4
+ * Runner core 改动。详 `design/96-references/GOAL-IMPLEMENTER-SPEC.md`(v3,codex r1+r2 异源证伪式对照收敛)。
5
5
  *
6
6
  * 🔴 **完成判定双闸(reward-hack 红线)**:① LLM 自报完成 = 调内置 `declare_done` 工具(或 `submit_output`,有
7
7
  * outputSchema 时)= **机器信号**,非泛化 `completed`;② core `doneCheck`(机械/oracle 校验)。**AND**:仅二者
@@ -219,6 +219,12 @@ export interface RunWorkflowToolDeps {
219
219
  /** Call-time getter for the HOST task's current thinking level — a spawned agent with no explicit
220
220
  * script/baseline `thinking` inherits it (the parentCwd/model-snapshot companion). */
221
221
  parentThinking?: () => import("../core/types.js").TaskSpec["thinking"];
222
+ /** 5.30 merge-rescan (design/199 parity gap) — call-time getter for the HOST task's RESOLVED
223
+ * read-face containment; folds stricter-wins into every spawned workflow child (see
224
+ * `buildWorkflowPrimitives`'s injection block for the full rationale). */
225
+ parentReadFace?: () => import("../core/types.js").TaskSpec["readFace"];
226
+ /** Twin of the above for the deny-set additions (union, not replace). */
227
+ parentReadDenyPatterns?: () => import("../core/types.js").TaskSpec["readDenyPatterns"];
222
228
  /** [1238](A) — call-time getter for the HOST task's RESOLVED Model object: a spawned agent whose
223
229
  * fold chain produced no model inherits the parent's full object (baseUrl/key routing included),
224
230
  * mirroring the subagent lane's ctx.model semantics. */
@@ -254,6 +260,21 @@ export interface RunWorkflowToolDeps {
254
260
  autoModeReview?: () => {
255
261
  decider: import("../core/auto-mode.js").AutoModeDecider;
256
262
  } | undefined;
263
+ /** 5.30 merge-rescan round 2 — the HOST task's durable OFF SWITCH (`spec.checkpointStore === null`,
264
+ * ruled 2026-08-04 to inherit into every spawned agent, see `buildWorkflowPrimitives`'s call
265
+ * above). Known at prepare-time (no lazy getter needed — `spec` is available at mount). A dep for
266
+ * the same reason as `forwardEvent`/`inheritedGateForChildren`: the auto-mounted tool's execute
267
+ * ctx is minimal `{toolCallId, signal}`, so `ctx.checkpointStoreDisabledForChildren` was a DEAD
268
+ * read there — the off-switch never actually reached this lane's children. */
269
+ parentCheckpointStoreDisabled?: boolean;
270
+ /** Twin of the above for design/148 S1's center-artifact inheritance (see the `ctx.centerArtifactDigest`/
271
+ * `ctx.centerSourceRevision` reads at the `startWorkflow` options site) — `centerAdoption` resolves
272
+ * LATE in prepare-task.ts (well after this tool's mount point), so this is a call-time getter, not
273
+ * a value, mirroring `parentReadFace`. `ctx.centerArtifactDigest` still wins when a wrapping path
274
+ * provides it. */
275
+ parentCenterArtifactDigest?: () => string | undefined;
276
+ /** Publish-provenance companion of the above. */
277
+ parentCenterSourceRevision?: () => string | undefined;
257
278
  }
258
279
  /**
259
280
  * Build the `run_workflow` tool. ASSERTS the runner is a hard sandbox (defense in depth — prepare-task only
@@ -380,7 +380,7 @@ export async function createRunWorkflowTool(d) {
380
380
  return structuredError(`workflow script failed to compile: ${err instanceof Error ? err.message : String(err)}`);
381
381
  }
382
382
  const scriptFn = (wfCtx) => {
383
- const primitives = buildWorkflowPrimitives(wfCtx, governance, d.onAgentSpawn, d.parentThinking, principal, ctx.checkpointStoreDisabledForChildren === true);
383
+ const primitives = buildWorkflowPrimitives(wfCtx, governance, d.onAgentSpawn, d.parentThinking, principal, ctx.checkpointStoreDisabledForChildren === true || d.parentCheckpointStoreDisabled === true, d.parentReadFace, d.parentReadDenyPatterns);
384
384
  return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
385
385
  };
386
386
  if (ctx.signal?.aborted) {
@@ -389,6 +389,9 @@ export async function createRunWorkflowTool(d) {
389
389
  let handle;
390
390
  const workflowTaskId = d.taskRegistry?.mintTaskId("workflow");
391
391
  const autoModeReview = ctx.autoModeReview ?? d.autoModeReview?.();
392
+ const centerFromCtx = ctx.centerArtifactDigest !== undefined;
393
+ const parentCenterArtifactDigest = centerFromCtx ? ctx.centerArtifactDigest : d.parentCenterArtifactDigest?.();
394
+ const parentCenterSourceRevision = centerFromCtx ? ctx.centerSourceRevision : d.parentCenterSourceRevision?.();
392
395
  try {
393
396
  handle = startWorkflow(d.runner, scriptFn, {
394
397
  store: d.store,
@@ -417,8 +420,8 @@ export async function createRunWorkflowTool(d) {
417
420
  ...((ctx.interactionPosture ?? d.parentInteractionPosture) !== undefined ? { interactionPosture: (ctx.interactionPosture ?? d.parentInteractionPosture) } : {}),
418
421
  ...(d.parentModel !== undefined ? { defaultModel: d.parentModel } : {}),
419
422
  ...(d.parentGetApiKeyAndHeaders !== undefined ? { defaultGetApiKeyAndHeaders: d.parentGetApiKeyAndHeaders } : {}),
420
- ...(ctx.centerArtifactDigest !== undefined ? { parentCenterArtifactDigest: ctx.centerArtifactDigest } : {}),
421
- ...(ctx.centerSourceRevision !== undefined ? { parentCenterSourceRevision: ctx.centerSourceRevision } : {}),
423
+ ...(parentCenterArtifactDigest !== undefined ? { parentCenterArtifactDigest } : {}),
424
+ ...(parentCenterSourceRevision !== undefined ? { parentCenterSourceRevision } : {}),
422
425
  ...((ctx.forwardEvent ?? d.forwardEvent) !== undefined ? { onForwardEvent: (ctx.forwardEvent ?? d.forwardEvent) } : {}),
423
426
  ...((ctx.inheritedGateForChildren ?? d.inheritedGateForChildren) !== undefined
424
427
  ? { inheritedGate: (ctx.inheritedGateForChildren ?? d.inheritedGateForChildren)() }
@@ -30,4 +30,13 @@ export interface WorkflowGovernance {
30
30
  export declare function buildWorkflowPrimitives(ctx: WorkflowRunContext, governance?: WorkflowGovernance, onAgentSpawn?: (handle: WorkflowAgentHandle) => void, parentThinking?: () => TaskSpec["thinking"], parentPrincipal?: string,
31
31
  /** ruled 2026-08-04 — the host run set `TaskSpec.checkpointStore: null` (the per-run durable off
32
32
  * switch). Every agent this workflow spawns inherits it; see the injection below. */
33
- parentCheckpointStoreDisabled?: boolean): WorkflowPrimitives;
33
+ parentCheckpointStoreDisabled?: boolean,
34
+ /** 5.30 merge-rescan (design/199 parity gap) — the host run's RESOLVED read-face containment
35
+ * (post deps/org/mount resolution, not the raw spec value). `TaskSpec.readFace`'s own JSDoc
36
+ * promises "roots pins this task, and via the tighten-only clamp, its whole delegation subtree" —
37
+ * a workflow child is that subtree; the Task/SendMessage delegation lane already carries this,
38
+ * the workflow lane was the one gap. Lazy (read at spawn time, after prepare has resolved it). */
39
+ parentReadFace?: () => TaskSpec["readFace"],
40
+ /** Twin of the above for the deny-set additions (built-ins always apply; these are the extra
41
+ * entries the host's own deployment/task layers stacked on). */
42
+ parentReadDenyPatterns?: () => TaskSpec["readDenyPatterns"]): WorkflowPrimitives;
@@ -22,7 +22,7 @@ function formatResourceClampNote(notes) {
22
22
  const parts = notes.map((n) => `${n.field}: requested ${n.requested === undefined ? "unset" : n.requested} → applied ${n.applied}`);
23
23
  return `workflow governance tightened this agent's resource limits (${parts.join("; ")})`;
24
24
  }
25
- export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThinking, parentPrincipal, parentCheckpointStoreDisabled) {
25
+ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThinking, parentPrincipal, parentCheckpointStoreDisabled, parentReadFace, parentReadDenyPatterns) {
26
26
  const agent = (spec, opts) => {
27
27
  if (typeof spec === "string")
28
28
  spec = { objective: spec };
@@ -42,6 +42,17 @@ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThi
42
42
  if (parentCheckpointStoreDisabled === true) {
43
43
  childSpec.checkpointStore = null;
44
44
  }
45
+ if (parentReadFace) {
46
+ const pf = parentReadFace();
47
+ if (pf === "roots" && childSpec.readFace !== "roots")
48
+ childSpec.readFace = "roots";
49
+ }
50
+ if (parentReadDenyPatterns) {
51
+ const pd = parentReadDenyPatterns();
52
+ if (pd !== undefined && pd.length > 0) {
53
+ childSpec.readDenyPatterns = childSpec.readDenyPatterns !== undefined ? [...childSpec.readDenyPatterns, ...pd] : [...pd];
54
+ }
55
+ }
45
56
  if (onAgentSpawn) {
46
57
  return ctx.agentStream(childSpec, agentOpts).then((handle) => {
47
58
  onAgentSpawn(handle);
@@ -95,10 +95,11 @@ export interface WorkflowAgentRun {
95
95
  * `isolation: "worktree"` agent whose worktree the engine actually minted (reported by the runner's
96
96
  * workspace-observation seam and folded on while the agent is still RUNNING, so an interrupted run's
97
97
  * recovery path has the coordinate instead of enumerating the worktrees directory and guessing). ABSENCE
98
- * means the agent ran in the SHARED working tree never "unknown": the shared root is deliberately not
98
+ * means the agent ran in the SHARED working tree OR in a REMOTE isolated sandbox (#197 BGW-7: a sandbox's
99
+ * cwd is remote-internal, not a host path) — never "unknown": neither the shared root nor a remote path is
99
100
  * written here, so a consumer acting on this path (diff / merge / remove, the userland half of the
100
- * isolate-only contract) can never be handed a tree it must not touch. A host path, not content same
101
- * scope-gated, unredacted posture as {@link sessionId}. */
101
+ * isolate-only contract) can never be handed a tree it must not touch or a path the host does not hold.
102
+ * Always a HOST path, not content → same scope-gated, unredacted posture as {@link sessionId}. */
102
103
  worktreeDir?: string;
103
104
  }
104
105
  /**
@@ -288,7 +289,7 @@ export declare class WorkflowBudgetExceededError extends Error {
288
289
  * capped at ONE level (design/97 §H.1, the S8 prerequisite). A workflow's agent (a `runner.runTask` child)
289
290
  * cannot itself start a workflow: the LLM-facing `run_workflow` tool hits this guard, and so does a
290
291
  * trusted-dev nested `runWorkflow(...)` call. The depth is read from the TRUSTED
291
- * {@link WorkflowInternals.workflowDepth} first (the cross-process channel — a worker/script cannot forge it,
292
+ * {@link WorkflowInternals.workflowDepth} first (the cross-process channel — a worker/script cannot self-declare it,
292
293
  * it is not a `TaskSpec` field) then the in-process {@link workflowDepthStore} (AsyncLocalStorage), so neither
293
294
  * a spec field nor a tool param can defeat it (design/98 §0.1 BLOCKER3).
294
295
  */