@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,99 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.30.0 — 2026-08-13
4
+
5
+ No API-BREAKING changes (exports grow only: `resolveReadFace`, `ReadFace`, `ReadFaceInputs`,
6
+ `READ_FACE_DEFAULT_DENY_ENTRIES`, `compileReadDeny`, `ReadDenyEntry`, `ReadDenyMatcher`,
7
+ `NormalizedReadDenyEntry`; `TaskSpec`/`RunnerDeps`/`HandsToolkitOptions` gain `readFace`/
8
+ `readDenyPatterns`). Narrowings are tighten-direction and named below.
9
+
10
+ ### Added
11
+
12
+ - **A read-face containment knob (`readFace: "open" | "roots"`, default `"roots"` — byte-compatible
13
+ with prior behavior).** `TaskSpec`/`RunnerDeps`/`HandsToolkitOptions` each gain the seat; task-layer
14
+ may only tighten under governance, deployment-layer sets the default. Beside a read-only (verifier)
15
+ mount, a task-seat `"open"` is a genuine contradiction and refuses loudly; a deployment-seat
16
+ `"open"` there silently clamps to `"roots"` (a deployment default cannot override a load-bearing
17
+ containment wall). Every workflow/subagent/SendMessage delegation lane inherits the resolved face
18
+ tighten-only (stricter-wins), including `run_workflow`'s script-spawned children, which previously
19
+ did not inherit it at all. Checkpoint schema bumps to v9 (`FACE_CHECKPOINT_VERSION`) to carry the
20
+ resolved face across suspend/resume; an absent v9 section on an older checkpoint row is the default
21
+ posture (roots) — no existing row's behavior changes.
22
+ - **A built-in sensitive-path READ deny set**, exported as `READ_FACE_DEFAULT_DENY_ENTRIES` (SSH
23
+ keys, cloud/VCS credential files, browser profile directories, crypto wallet files, shell history,
24
+ and similar). Enforced under BOTH containment modes — an `"open"` face does not exempt it — and not
25
+ shrinkable by any deployment configuration, including the `readFace: "roots"` compatibility seat
26
+ (the two axes are orthogonal). `.env`-family files are deliberately excluded (write-side guards
27
+ already cover them). Deployments may extend the table (`readDenyPatterns` on the same three seats,
28
+ additive-only; `compileReadDeny` lets a host pre-validate additions with the engine's own rules).
29
+
30
+ ### Narrowed (behavior)
31
+
32
+ - **A UNC-rooted deployment's `..` can no longer walk out of its declared root.** Lexical folding now
33
+ normalizes `..` segments and Win32 device-namespace prefixes (`\\?\`, `\\.\`) before they reach the
34
+ containment check; this is a security repair with no compatibility escape hatch (unaffected by any
35
+ "keep old behavior" knob).
36
+ - **`bashReversibilityProbe` no longer auto-executes an operand with an unexpanded shell glob
37
+ metacharacter it can't verify against the deny/boundary check.** A command whose read target is
38
+ undecided at the classifier layer now routes to the ask flow instead of running unattended.
39
+ - **A hands-less resume no longer clones the `realApproval` governance marker onto an ungoverned
40
+ re-park.** The seed-carry now picks only `{face, denyEntries}`; previously, a worker resuming into a
41
+ plain (non-governed) ask could inherit a stale `realApproval: true` and permanently wedge the row on
42
+ its next resume (`real_approval_damaged`, fail-closed by design).
43
+ - **The auto-mode classifier can no longer resolve an ask that originated from an explicit
44
+ user-written permission rule or a hook-raised promotion.** Both are now excluded from classifier
45
+ judgment and the persisted-rule-clearing lane, closing two asymmetric gaps against the existing
46
+ `decisionReason === "hook"` exclusion.
47
+ - **`resolveAsk`'s object-arm reads share one exception boundary.** A throwing getter on `settledBy`/
48
+ `allow`/`updatedInput` now fails closed (deny) instead of propagating an unhandled rejection; a
49
+ non-boolean `allow` is refused loudly by name instead of silently rendering as a human refusal.
50
+
51
+ ### Fixed
52
+
53
+ - `run_workflow`'s durable-store off switch (`spec.checkpointStore === null`) and design/148's
54
+ center-artifact inheritance (`centerArtifactDigest`/`centerSourceRevision`) were dead reads on the
55
+ tool's auto-mounted execute context (which never carries them) — neither ever reached a
56
+ script-spawned child. Both now fall back to a mount-time dependency, matching the tool's other two
57
+ ctx-or-deps fields.
58
+ - Under `readFace: "open"`, compaction's attachment-reader legs (`readTaskFile`/
59
+ `normalizeAttachmentPath`) refused files the Read tool had already legitimately served out-of-root;
60
+ they now thread the resolved face like the structured read tools do.
61
+ - `deleteBySession` joins the `ToolResultStore` contract kit as an honest optional member: when
62
+ implemented, the four-state semantics (same-session rows deleted, other-session rows untouched,
63
+ unowned rows counted not deleted, idempotent re-delete) are asserted; when absent, the contract
64
+ probe reports it by name instead of silently passing.
65
+
66
+ ## 5.29.0 — 2026-08-13
67
+
68
+ No API-BREAKING changes (exports grow only: `AskOutcome.reason`, `AskRequest.isDelegatedChild`).
69
+
70
+ ### Changed (behavior)
71
+
72
+ - **A human deny speaks a posture and can carry the decider's words.** The sync approval seat
73
+ `AskOutcome` gains `reason?: string` on its object arm: attached to a deny, the text reaches the
74
+ model inside the untrusted fence, bounded to 2048 code points — the same containment and bound as
75
+ the durable leg's reviewer note (whose four relay sites were previously unbounded and now share
76
+ the cap). Refusal messages fork on the issuing run's position (`AskRequest.isDelegatedChild`,
77
+ forks included): a parent-thread run is told to STOP and wait for the user, a delegated child is
78
+ told to adapt or report; the machine-greppable head line is unchanged. On an allow the seat is
79
+ never read; a non-string reason on a deny is refused loudly; timeout denials carry the note under
80
+ a neutral bridge. NOTE: the posture is model-facing text — making a parent-thread denial abort the
81
+ in-flight batch is registered follow-up work.
82
+ - **`worktreeDir` never names a non-host path.** A remote isolated agent's sandbox cwd is no longer
83
+ recorded (`ResolvedWorkspace` gains a required `remote` key; absence on the record now means
84
+ "shared tree OR remote sandbox"), and a workspace observation is authoritative replacement state —
85
+ a local→remote retry clears the stale host path.
86
+ - **Session freshness is judged by conversation content, not branch entries.** A pre-minted
87
+ sessionId or a host-pre-seeded marker (naming the chat, recording a model pick before the first
88
+ turn) no longer stamps `legacy_migration` on a brand-new session's first epoch pin — and no longer
89
+ silently costs such a session its published center prompt (the adoption arm shared the same
90
+ refuted entry-count proxy; all three freshness sites now share one predicate).
91
+ - **The ask-question tool teaches previews.** The tool description carries the Preview feature block
92
+ (CC markdown variant, verbatim), so the preview card form — the only question form whose answer
93
+ carries user notes — is reachable.
94
+ - The resident execution-environment prompt line about denied calls no longer instructs a blanket
95
+ "adjust and continue" — it defers to the denial message's own guidance (prompt golden re-frozen).
96
+
3
97
  ## 5.28.0 — 2026-08-12
4
98
 
5
99
  No API-BREAKING changes (exports grow only). Narrowings are tighten-direction and named below.
@@ -35,9 +129,11 @@ No API-BREAKING changes (exports grow only). Narrowings are tighten-direction an
35
129
 
36
130
  ### Added
37
131
 
38
- - `onNotice` structured notice seat (first two families: bash timeout, MATERIALIZE) — absent keeps
39
- the `console.warn` text verbatim; a present non-function seat is announced once and falls back
40
- loudly instead of silencing both channels.
132
+ - `onNotice` structured notice seat (three families: `config.env_timeout_discarded`,
133
+ `config.materialize_env_discarded`, `tool_result.offload_put_failed` this entry originally said
134
+ "first two families"; corrected 2026-08-12, the third family shipped in the same release) — absent
135
+ keeps the `console.warn` text verbatim; a present non-function seat is announced once and falls
136
+ back loudly instead of silencing both channels.
41
137
  - `tool_end.errorCode` now reads both discriminator spellings — `details.code` first (string), then
42
138
  `details.errorKind` — so loop-thrown error frames are classifiable; `code` wins when both exist.
43
139
  - `ToolResultStore.deleteBySession?` (optional member; File + in-memory implementations, not on the
@@ -9,7 +9,7 @@ import type { ModelRef, TaskResult, TaskSpec } from "../core/types.js";
9
9
  * APPLICABILITY: cascade only helps when the gate is **decidable** (a schema check, a verifier model, a
10
10
  * concrete assertion). Open-ended "completeness" tasks (find every bug, writing quality) have no oracle —
11
11
  * the gate keeps passing the first plausible cheap answer and escalation idles. Those want breadth /
12
- * adversarial debate (a `team`), not a depth ladder.
12
+ * falsification-style debate (a `team`), not a depth ladder.
13
13
  *
14
14
  * Cost note (design/27 §2): each rung is an independent COLD `runTask` — every escalation repays the
15
15
  * full input cost. Cascade wins when (a) the cheap rung usually passes and (b) the prompt isn't so large
@@ -36,11 +36,11 @@ export type ObserverDigestEvent = {
36
36
  type: "turn_ended";
37
37
  reason: string;
38
38
  };
39
- /** CC `S$r` — anti-injection: a forged envelope tag inside observed content is defused in place. */
39
+ /** CC `S$r` — anti-injection: a caller-declared envelope tag inside observed content is defused in place. */
40
40
  export declare function escapeObserverTags(text: string): string;
41
41
  /**
42
42
  * CC `zZe` @8339092 — verbatim: `t.replace(new RegExp(`<(?=/?${e}(?:[>\\s/]|$))`, "gi"), "<\\")`.
43
- * Defuses a forged occurrence of ONE specific tag (opening AND closing) inside content — the second
43
+ * Defuses a caller-declared occurrence of ONE specific tag (opening AND closing) inside content — the second
44
44
  * escape layer CC applies to the DYNAMIC envelope tag (`<{slug}-activity>`) after joining, which the
45
45
  * static four-tag `escapeObserverTags` (Scg layer) cannot cover. Deviation from CC (hardening): the
46
46
  * tag is regex-escaped before entering the RegExp — CC interpolates raw, safe only because its slugs
@@ -281,7 +281,7 @@ export interface ObserverReportToolOptions {
281
281
  * `<${$Bt} from="${Yp(e)}">\n${zZe($Bt, t)}\n</${$Bt}>` with `$Bt = "agent-message"` @1344442.
282
282
  * Deviation from CC ($Bt=agent-message → observer-report, sovereign choice): we keep a dedicated
283
283
  * frame tag so the observed agent can tell an observer report from generic agent traffic. The
284
- * escape mechanics are CC-faithful: the frame tag itself is zZe-defused in the body (so a forged
284
+ * escape mechanics are CC-faithful: the frame tag itself is zZe-defused in the body (so a caller-declared
285
285
  * `</observer-report>` cannot break the frame) and the `from=` attribute value is Yp-escaped.
286
286
  * Additional hardening beyond CC: the body also rides through the four-event-tag Scg layer.
287
287
  *
@@ -145,7 +145,7 @@ export declare function resetPeerAdmissionRegistryForTests(): void;
145
145
  * mounting is structurally single-sourced and survives any downstream projection of the payload).
146
146
  * Layered WITH the session-level `SUBAGENT_CONSENT_NOTICE` (prompts/default.ts), not replacing it:
147
147
  * that section governs COMMAND authority at session level; this block narrows AUTHORIZATION per
148
- * message. Placement per leg: OUTSIDE the `<teammate-message>` frame (a forged copy inside the
148
+ * message. Placement per leg: OUTSIDE the `<teammate-message>` frame (a caller-declared copy inside the
149
149
  * sender-controlled body arrives escaped, so position distinguishes the real block); the L3 resume
150
150
  * leg carries it in the TRUST-FRAME layer of the peer-resume prompt, never inside the data fence
151
151
  * (a load-bearing instruction inside a "this is DATA" fence would demote itself).
@@ -680,6 +680,8 @@ export function createSendMessageTool(opts) {
680
680
  ...(ctx.onQuestion !== undefined && row.sessionScoped !== true ? { currentOnQuestion: ctx.onQuestion } : {}),
681
681
  currentClamps: {
682
682
  ...(ctx.handsReadOnly === true ? { handsReadOnly: true } : {}),
683
+ ...(ctx.readFace === "roots" ? { readFaceRoots: true } : {}),
684
+ ...(ctx.readDenyPatterns !== undefined ? { readDenyPatterns: ctx.readDenyPatterns } : {}),
683
685
  ...(ctx.interactiveTools === false ? { interactiveTools: false } : {}),
684
686
  ...(ctx.oneShot === true ? { oneShot: true } : {}),
685
687
  ...(ctx.memoryPersistenceCapable === false ? { memoryPersistenceCapable: false } : {}),
@@ -404,6 +404,8 @@ export declare function createSubagentResume(deps: {
404
404
  interactiveTools?: false;
405
405
  oneShot?: true;
406
406
  memoryPersistenceCapable?: false;
407
+ readFaceRoots?: true;
408
+ readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
407
409
  };
408
410
  /** The RESUMING caller's own handback-review seat (its trusted `ToolExecuteContext.autoModeReview`),
409
411
  * same turn-bound reasoning as `currentOnQuestion` above: a resumed cycle is a completion like any
@@ -520,6 +520,10 @@ export function createSubagentResume(deps) {
520
520
  requireExistingSession: true,
521
521
  ...(deps.currentOnQuestion !== undefined ? { onQuestion: deps.currentOnQuestion } : {}),
522
522
  ...(entry.specSnapshot.handsReadOnly === true || deps.currentClamps?.handsReadOnly === true ? { handsReadOnly: true } : {}),
523
+ ...(entry.specSnapshot.readFace === "roots" || deps.currentClamps?.readFaceRoots === true ? { readFace: "roots" } : {}),
524
+ ...(entry.specSnapshot.readDenyPatterns !== undefined || deps.currentClamps?.readDenyPatterns !== undefined
525
+ ? { readDenyPatterns: [...(entry.specSnapshot.readDenyPatterns ?? []), ...(deps.currentClamps?.readDenyPatterns ?? [])] }
526
+ : {}),
523
527
  ...(entry.specSnapshot.interactiveTools === false || deps.currentClamps?.interactiveTools === false ? { interactiveTools: false } : {}),
524
528
  ...(entry.specSnapshot.oneShot === true || deps.currentClamps?.oneShot === true ? { oneShot: true } : {}),
525
529
  ...(entry.specSnapshot.memoryPersistenceCapable === false || deps.currentClamps?.memoryPersistenceCapable === false
@@ -1693,6 +1697,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1693
1697
  ...(ctx.onQuestion !== undefined ? { onQuestion: ctx.onQuestion } : {}),
1694
1698
  ...(ctx.interactiveTools === false ? { interactiveTools: false } : {}),
1695
1699
  ...(ctx.handsReadOnly === true ? { handsReadOnly: true } : {}),
1700
+ ...(ctx.readFace === "roots" ? { readFace: "roots" } : {}),
1701
+ ...(ctx.readDenyPatterns !== undefined ? { readDenyPatterns: [...ctx.readDenyPatterns] } : {}),
1696
1702
  ...(childMemoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: childMemoryPersistenceCapable } : {}),
1697
1703
  ...(ctx.oneShot === true ? { oneShot: true } : {}),
1698
1704
  ...(ctx.clientContext !== undefined ? { clientContext: ctx.clientContext } : {}),
@@ -13,7 +13,7 @@ import type { ModelRef, TaskResult, TaskSpec } from "../core/types.js";
13
13
  * creative quality — because there is no oracle for *completeness*: the verifier can't know how many bugs
14
14
  * the code has or which one was missed, so a student that finds one obvious issue passes the rubric while
15
15
  * silently missing others, and the escalation machinery idles (verified in practice: 0 escalations, the
16
- * verifier becomes pure added cost). Such tasks are solved by **breadth + adversarial debate** (a `team`
16
+ * verifier becomes pure added cost). Such tasks are solved by **breadth + falsification-style debate** (a `team`
17
17
  * council), not by **depth escalation** — the two are orthogonal. See design/12 §六 for the full reasoning.
18
18
  */
19
19
  /** Default teacher (advisor) system prompt — returns ONLY structured JSON guidance. */
@@ -180,6 +180,8 @@ async function runTeacherCore(runner, studentSpec, teacher) {
180
180
  ...(studentSpec.clientContext !== undefined ? { clientContext: studentSpec.clientContext } : {}),
181
181
  ...(studentSpec.promptProfile !== undefined ? { promptProfile: studentSpec.promptProfile } : {}),
182
182
  ...(studentSpec.handsReadOnly === true ? { handsReadOnly: true } : {}),
183
+ ...(studentSpec.readFace === "roots" ? { readFace: "roots" } : {}),
184
+ ...(studentSpec.readDenyPatterns !== undefined ? { readDenyPatterns: [...studentSpec.readDenyPatterns] } : {}),
183
185
  ...(studentSpec.interactiveTools === false ? { interactiveTools: false } : {}),
184
186
  ...(studentSpec.excludeTools !== undefined ? { excludeTools: [...studentSpec.excludeTools] } : {}),
185
187
  ...(studentSpec.deferTools !== undefined ? { deferTools: [...studentSpec.deferTools] } : {}),
@@ -3,7 +3,7 @@ import type { Runner, ResumeTaskConfig } from "../core/runner/runtask.js";
3
3
  import type { CheckpointGate, CheckpointToken, ResumeOutcome } from "../core/checkpoint-store.js";
4
4
  import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js";
5
5
  /**
6
- * Verification gate (developer mode, design/28 §4). An **independent adversarial verifier** runs after
6
+ * Verification gate (developer mode, design/28 §4). An **independent falsification-style verifier** runs after
7
7
  * an implementation task and tries to BREAK it — read-only, evidence-required, returning a structured
8
8
  * verdict — then the gate loops fix→re-verify until PASS or a round cap.
9
9
  *
@@ -20,7 +20,7 @@ import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js"
20
20
  * SKILL.md` closest to the code it covers … and if that file does not exist, create it." So upstream's
21
21
  * position is "verification is project-specific; teach the user's repo to describe its own", not "here
22
22
  * is a verifier". This primitive is therefore a SUPERSET of what upstream ships in a different
23
- * direction than the old note implied: a spawned adversarial verifier with a fix loop, bundled. It is
23
+ * direction than the old note implied: a spawned falsification-style verifier with a fix loop, bundled. It is
24
24
  * orthogonal to and composable with the Stop hook (wire the verdict into a stop() hook to make it a
25
25
  * hard completion gate).
26
26
  *
@@ -31,7 +31,7 @@ import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js"
31
31
  * per task via {@link runWithVerification} (or {@link runDeveloperTask}).
32
32
  *
33
33
  * Boundary vs teacher mode: teacher's Tier-1 is a *lenient* rubric verifier that triggers escalation to
34
- * an advisor (stuck/wrong recovery); this is a *strict adversarial* completion gate with a fix loop.
34
+ * an advisor (stuck/wrong recovery); this is a *strict falsification-style* completion gate with a fix loop.
35
35
  * Orthogonal, composable, not merged.
36
36
  */
37
37
  export declare const VERIFICATION_PROMPT = "You are a verification specialist. Your job is NOT to confirm the implementation works \u2014 it is to try to BREAK it.\n\nYou have two documented failure patterns. First, verification avoidance: faced with a check, you find reasons not to run it \u2014 you read code, narrate what you would test, declare \"PASS,\" and move on. Second, being seduced by the first 80%: a polished result or a passing test suite makes you inclined to pass it, not noticing the edge that crashes, the state that vanishes, the bad input that is unhandled. The first 80% is the easy part. Your entire value is in finding the last 20%.\n\n## Hard boundary \u2014 do not modify the project\nYou are STRICTLY a verifier. Do NOT create, modify, or delete project files; do NOT install packages; do NOT run version-control write operations. Use only the read/probe/execute tools available to you. (If you need a scratch file, use a temp directory, and clean up.)\n\n## Evidence is mandatory\nReading code is NOT verification. Every check must actually run something \u2014 execute the code, hit the endpoint, run the build/tests \u2014 and record the command and its real output. A \"PASS\" with no command output is a skip, not a pass.\n\n## Strategy (adapt to what changed)\n- Build/lib changes: build it, run the full test suite, exercise the public API as a consumer would.\n- Backend/API: start it, call endpoints, check response *shapes* (not just status codes), test error paths.\n- CLI/script: run with representative AND edge inputs (empty, malformed, boundary); check stdout/stderr/exit codes.\n- Bug fix: reproduce the original bug first, verify the fix, then check for regressions and side effects.\n- Refactor (no behavior change): the existing suite must pass unchanged; diff the public surface; same inputs \u2192 same outputs.\nRun the project's own build/tests/linters as a baseline, then apply the type-specific checks. Test results are context, not proof \u2014 the implementer is an LLM too; its tests may be happy-path or circular.\n\n## Adversarial probes (pick the ones that fit)\nBoundary values (0, -1, empty, very long, unicode, max), idempotency (same mutating call twice), orphan operations (ids that don't exist), concurrency (parallel create-if-not-exists). Your verdict must include at least one adversarial probe you actually ran and its result \u2014 even if it was handled correctly.\n\n## Before you FAIL\nCheck you haven't missed why it's actually fine: defensive code elsewhere, intentional behavior documented in comments/specs, or an unfixable external-contract limitation (note that as an observation, not a FAIL). Don't wave away real issues, but don't FAIL on intentional behavior.\n\n## Verdict\nSubmit exactly one verdict via the provided output tool:\n- PASS \u2014 you ran real checks (including \u22651 adversarial probe) and it holds up. Put the commands + observed output in `evidence`.\n- FAIL \u2014 something is broken. Put each concrete problem (with how to reproduce) in `findings`.\n- PARTIAL \u2014 environmental limitation only (no test framework, a tool/server unavailable). Not for \"I'm unsure\": if you can run the check, decide PASS or FAIL. Note what you couldn't verify and why in `findings`.";
@@ -202,7 +202,7 @@ export interface VerificationResult extends TaskResult {
202
202
  verification: VerificationOutcome;
203
203
  }
204
204
  /**
205
- * Verify an already-**completed** implementation `result` behind the independent adversarial verifier,
205
+ * Verify an already-**completed** implementation `result` behind the independent falsification-style verifier,
206
206
  * looping fix→re-verify until PASS (or a round cap) — **without re-running the implementation**. This is the
207
207
  * L3 entry for the L2+L3 composition (design/54 §4): in a fan-out, a worker's module has already been
208
208
  * produced, so the orchestrator runs the mechanical L2 gate ({@link runExecGate}), computes the diff, then
@@ -219,7 +219,7 @@ export interface VerificationResult extends TaskResult {
219
219
  */
220
220
  export declare function verifyCompleted(runner: Runner, result: TaskResult, specBase: ResumeTaskConfig, objective: string, config: VerifyConfig): Promise<VerificationResult>;
221
221
  /**
222
- * Run an implementation task, then gate it behind an independent adversarial verifier, looping
222
+ * Run an implementation task, then gate it behind an independent falsification-style verifier, looping
223
223
  * fix→re-verify until PASS (or a round cap). Returns the implementation result plus the
224
224
  * {@link VerificationOutcome}. The caller decides WHEN to use this (explicit opt-in) — it always
225
225
  * verifies once invoked. If the impl **suspends on a durable HITL gate**, it is surfaced as
@@ -232,7 +232,7 @@ export declare function runWithVerification(runner: Runner, implSpec: TaskSpec,
232
232
  * durable + HITL + verify integration (design/51 P1-b). The mirror of {@link runWithVerification} for the
233
233
  * resume path: `runWithVerification` surfaces a HITL suspend as failed-with-token; once the human
234
234
  * adjudicates, the caller calls this with the `token` + `outcome`, and it resumes the implementation and —
235
- * **if it COMPLETES** — runs the identical adversarial verifier + fix loop. If the resumed run suspends
235
+ * **if it COMPLETES** — runs the identical falsification-style verifier + fix loop. If the resumed run suspends
236
236
  * AGAIN (a later durable gate), it is surfaced as failed-with-token (`unverified`) for the caller to resume
237
237
  * once more. `objective` is the ORIGINAL task objective (the resume carries none of its own) — the verifier
238
238
  * needs it as context; pass the same objective the original `runWithVerification` ran with.
@@ -99,6 +99,8 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
99
99
  roles: specBase.roles,
100
100
  tools: verifierTools,
101
101
  handsReadOnly: config.verifierHandsReadOnly ?? true,
102
+ ...(specBase.readFace === "roots" ? { readFace: "roots" } : {}),
103
+ ...(specBase.readDenyPatterns !== undefined ? { readDenyPatterns: [...specBase.readDenyPatterns] } : {}),
102
104
  interactiveTools: false,
103
105
  outputSchema: VerdictSchema,
104
106
  enableBlockedReport: false,
@@ -458,7 +458,7 @@ export interface PairedBinaryComparison {
458
458
  /**
459
459
  * codex B2 — compare two arms' delivered truly-correct as PAIRED binary. Pairs runs by the COMPOSITE
460
460
  * `(taskId, seed)` key (codex MAJOR-A: a bare `seed` collides across tasks — the same repeat index
461
- * recurs per task — so cross-task input would overwrite pairs and poison the McNemar sample);
461
+ * recurs per task — so cross-task input would overwrite pairs and contaminate the McNemar sample);
462
462
  * only rows where BOTH arms delivered (not withheld, both scored) form a pair — a
463
463
  * withheld run has no delivered binary to pair (it is scored in the withhold/avoided-loss axis, not
464
464
  * here). `mde` is the pre-registered minimum detectable effect (default 0.20 absolute = a 20pp swing).
@@ -13,7 +13,7 @@
13
13
  * - `Retry-After` (delta-seconds or HTTP-date), on any retryable response.
14
14
  * - `anthropic-ratelimit-unified-reset` (an absolute epoch-seconds stamp of when the rate-limit window
15
15
  * reopens), read only off a 429 — it is a rate-limit signal.
16
- * When both are present the larger wins. Both are clamped so a hostile/buggy value
16
+ * When both are present the larger wins. Both are clamped so a untrusted/buggy value
17
17
  * (`Retry-After: 2147483647`, a reset stamp years out) cannot pin the caller for hours.
18
18
  */
19
19
  /** Parse a `Retry-After` header (delta-seconds or HTTP-date) to ms; undefined if absent/unparseable. */
@@ -52,7 +52,7 @@ export interface SummarizableFinding {
52
52
  export declare function summarizeRedactions(findings: readonly SummarizableFinding[]): string;
53
53
  /** The optional collector every redaction entry point accepts. `preexistingMarkers` (ANG-11) counts
54
54
  * marker-shaped literals ALREADY present in the input before any pass ran — a nonzero value means the
55
- * text arrived pre-redacted or carries forged markers, either way "marker present" is not proof this
55
+ * text arrived pre-redacted or carries caller-declared markers, either way "marker present" is not proof this
56
56
  * pipeline ran. Caller-owned: pass `{ findings: [] }` and read it back after the call. */
57
57
  export interface RedactionReport {
58
58
  findings: RedactionFinding[];
@@ -8,7 +8,7 @@
8
8
  * self-reported "this ask is sandbox-local" field would be the same trust hole as the
9
9
  * `decisionReason:"safety"` self-declaration this codebase already refuses. Instead, BUILT-IN policy
10
10
  * factories register their instances here (private WeakMap brand — nothing a deployment policy can
11
- * forge by shaping its return value), and everything unregistered reads as `unknown`.
11
+ * self-declare by shaping its return value), and everything unregistered reads as `unknown`.
12
12
  *
13
13
  * Classes and their dominance (STRICT — merge keeps the strongest; only an all-`sandbox_local` set
14
14
  * may auto-admit):
@@ -27,7 +27,7 @@ export type AskClass = "sandbox_local" | "external_authority" | "hook" | "mandat
27
27
  /**
28
28
  * Brand a built-in policy instance with its ask class (engine factories only — not exported from the
29
29
  * package). The `check` binding is LOCKED as a non-writable, non-configurable DATA property before the
30
- * policy is returned (codex adversarial round 4, HIGH): reading `policy.check` and INVOKING it must
30
+ * policy is returned (codex falsification-style round 4, HIGH): reading `policy.check` and INVOKING it must
31
31
  * name the same callable, atomically. A plain `===` re-read was TOCTOU-able by an accessor — a getter
32
32
  * could return the branded function to `policyAskClassOf` and a deployment ask to the gate's
33
33
  * invocation. Locking the property makes the callable un-swappable and un-redefinable, so the class
@@ -234,7 +234,7 @@ export declare function askQuestionContinuationCard(questionId: string, continua
234
234
  * The ONE shape validator for a question batch, shared by the tool body and by the engine's routing lane
235
235
  * (which validates BEFORE it puts anything in front of a person — a malformed batch must be refused to
236
236
  * the model, not rendered). Returns the model-facing error text, or `undefined` when the batch is well
237
- * formed. Total: it never throws on hostile field shapes.
237
+ * formed. Total: it never throws on untrusted field shapes.
238
238
  */
239
239
  export declare function validateAskQuestions(questions: unknown): string | undefined;
240
240
  /** design/173 件3 — engine-side options for the mounted tool (prepare wires them; a deployment
@@ -184,7 +184,20 @@ export function createAskUserQuestionTool(onQuestion, source, opts) {
184
184
  "Usage notes:\n" +
185
185
  '- Users will always be able to select "Other" to provide custom text input\n' +
186
186
  "- Use multiSelect: true to allow multiple answers to be selected for a question\n" +
187
- '- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label',
187
+ '- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label' +
188
+ "\n" +
189
+ "\n" +
190
+ "Preview feature:\n" +
191
+ "Use the optional `preview` field on options when presenting concrete artifacts that users need to visually compare:\n" +
192
+ "- ASCII mockups of UI layouts or components\n" +
193
+ "- Code snippets showing different implementations\n" +
194
+ "- Diagram variations\n" +
195
+ "- Configuration examples\n" +
196
+ "\n" +
197
+ "Preview content is rendered as markdown in a monospace box. Multi-line text with newlines is supported. " +
198
+ "When any option has a preview, the UI switches to a side-by-side layout with a vertical option list on " +
199
+ "the left and preview on the right. Do not use previews for simple preference questions where labels and " +
200
+ "descriptions suffice. Note: previews are only supported for single-select questions (not multiSelect).",
188
201
  parameters: Type.Object({
189
202
  questions: Type.Array(Type.Object({
190
203
  question: Type.String({
@@ -244,7 +244,11 @@ export interface MaybeCompactOptions {
244
244
  */
245
245
  workingFileAttachments?: {
246
246
  /** Read a task workspace file; null/throw = skip it. Wired by the Runner from the task's ExecutionEnv. */
247
- readFile: (path: string) => Promise<string | null>;
247
+ readFile: (path: string) => Promise<string | null | {
248
+ withheld: {
249
+ pattern: string;
250
+ };
251
+ }>;
248
252
  /** Max modified files to attach. Default 3. */
249
253
  maxFiles?: number;
250
254
  /** Per-file char cap (≈4 chars/token). Default 16_000 (~4k tokens). */
@@ -397,12 +397,21 @@ export async function maybeCompact(opts) {
397
397
  if (remaining <= 0)
398
398
  break;
399
399
  let content = null;
400
+ let withheldPattern;
400
401
  try {
401
- content = await att.readFile(path);
402
+ const r = await att.readFile(path);
403
+ if (typeof r === "string")
404
+ content = r;
405
+ else if (r !== null && typeof r === "object")
406
+ withheldPattern = r.withheld.pattern;
402
407
  }
403
408
  catch {
404
409
  content = null;
405
410
  }
411
+ if (withheldPattern !== undefined) {
412
+ blocks.push(`<working-file path="${escapeWorkingFilePathAttr(path)}" withheld="content withheld: matches the sensitive-path read deny list (pattern ${escapeWorkingFilePathAttr(withheldPattern)})"/>`);
413
+ continue;
414
+ }
406
415
  if (content === null)
407
416
  continue;
408
417
  const cap = Math.min(perFileCap, remaining);
@@ -6,16 +6,16 @@
6
6
  * 这是一个 **env 上的可选能力**(非一种独立 env 类型),所以不 `extends ExecutionEnv` —— 用交叉类型挂到具体实现上,并经
7
7
  * {@link hasBackgroundShell} 运行时检测(对齐 remote-env.ts 的 `hasDestroy`/`isRemoteExecutionEnv` 模式,零 vendor 接口改动)。
8
8
  *
9
- * 设计裁定(经 codex + workflow 5-lens 双轨对抗复审收敛,design/103 v2):
9
+ * 设计裁定(经 codex + workflow 5-lens 双轨证伪式复审收敛,design/103 v2):
10
10
  * - **不跨 durable suspend**:后台进程在每条退出路径被 dispose;Runner 必须在 suspendVM **之前** 调
11
11
  * {@link BackgroundShellCapability.disposeBackgroundShells}(detached job 不在 suspendVM 的 in-flight 契约射程内)。
12
- * - **越权隔离**:`pollBackground`/`killBackground` 的 shellId MUST 被校验为本 env 自己 spawn 过的;非本 env 走 `not_found`。
12
+ * - **越界隔离**:`pollBackground`/`killBackground` 的 shellId MUST 被校验为本 env 自己 spawn 过的;非本 env 走 `not_found`。
13
13
  * - **按-id-可重读**:实现 MUST 保证按 shellId 跨多次独立调用可重复读取累积/增量输出(execStream 的 consume-once 不满足)。
14
14
  */
15
15
  import type { ExecutionEnv, ExecutionEnvExecOptions, Result } from "../internal/harness-types.js";
16
16
  /**
17
17
  * 一个 long-running / detached 进程的句柄。**env-local、非 durable、对调用方不透明** —— adapter 内部把它映射到真实进程/
18
- * provider job,**绝不**把可猜的 raw provider job id 暴露成 shellId(否则跨租户可枚举,design/103 §3.8 越权红线)。
18
+ * provider job,**绝不**把可猜的 raw provider job id 暴露成 shellId(否则跨租户可枚举,design/103 §3.8 越界红线)。
19
19
  */
20
20
  export type BackgroundShellId = string & {
21
21
  readonly __brand: "BackgroundShellId";
@@ -105,11 +105,11 @@ export interface BackgroundShellCapability {
105
105
  /**
106
106
  * 读一个后台进程**自上次 poll 以来的新增**输出 + 当前状态(cursor 语义)。退出后仍可读残余 + exitCode,直到被 dispose/reap。
107
107
  *
108
- * 🔴 越权契约:`shellId` MUST 被校验为**本 env 自己 spawnBackground 返回过**的;非本 env 一律 `not_found`,绝不解析外部 job id。
108
+ * 🔴 越界契约:`shellId` MUST 被校验为**本 env 自己 spawnBackground 返回过**的;非本 env 一律 `not_found`,绝不解析外部 job id。
109
109
  * 🔴 重读契约:实现 MUST 保证「按 shellId 跨多次独立调用可重复读取累积/增量」(remote 的 execStream consume-once 不满足 —— 见 design/103 §5.2 两条路径)。
110
110
  */
111
111
  pollBackground(shellId: BackgroundShellId): Promise<Result<BackgroundPoll, BackgroundShellError>>;
112
- /** 杀一个后台进程(幂等:杀已死的是 no-op,返回 ok)。`shellId` 同 {@link pollBackground} 的越权校验。 */
112
+ /** 杀一个后台进程(幂等:杀已死的是 no-op,返回 ok)。`shellId` 同 {@link pollBackground} 的越界校验。 */
113
113
  killBackground(shellId: BackgroundShellId): Promise<Result<void, BackgroundShellError>>;
114
114
  /**
115
115
  * 杀掉并清理**本 env 的所有**后台进程。Runner 在每条退出路径调:finish/abort/throw 在 run-loop tail finally,**suspend/