@sema-agent/core 7.14.0 → 7.16.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 (136) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/core/ask-origin.d.ts +2 -2
  3. package/dist/core/checkpoint-store.d.ts +14 -15
  4. package/dist/core/governance-codes.js +2 -0
  5. package/dist/core/hooks.d.ts +4 -3
  6. package/dist/core/memory-engine/delegation-settlement.d.ts +37 -12
  7. package/dist/core/memory-engine/delegation-settlement.js +128 -39
  8. package/dist/core/memory-engine/engine.d.ts +9 -8
  9. package/dist/core/memory-engine/engine.js +115 -120
  10. package/dist/core/memory-engine/file-backend.d.ts +17 -5
  11. package/dist/core/memory-engine/file-backend.js +78 -34
  12. package/dist/core/memory-engine/index.d.ts +2 -2
  13. package/dist/core/memory-engine/index.js +2 -2
  14. package/dist/core/memory-engine/layout.d.ts +152 -54
  15. package/dist/core/memory-engine/layout.js +441 -104
  16. package/dist/core/memory-engine/types.d.ts +8 -3
  17. package/dist/core/memory-engine/types.js +5 -0
  18. package/dist/core/runner/abort-race.d.ts +3 -3
  19. package/dist/core/runner/active-skill-scope.d.ts +2 -2
  20. package/dist/core/runner/assemble-result.d.ts +29 -29
  21. package/dist/core/runner/checkpoint-scope.d.ts +4 -4
  22. package/dist/core/runner/clock-and-limits.d.ts +3 -3
  23. package/dist/core/runner/compaction-call-options.d.ts +11 -11
  24. package/dist/core/runner/compaction-knobs.d.ts +4 -4
  25. package/dist/core/runner/compaction-seams.d.ts +5 -5
  26. package/dist/core/runner/contracts.d.ts +224 -226
  27. package/dist/core/runner/decide-continuation.d.ts +6 -6
  28. package/dist/core/runner/denial-limit-arms.d.ts +8 -8
  29. package/dist/core/runner/derived-route-fallback.d.ts +2 -2
  30. package/dist/core/runner/gate-exit.d.ts +6 -6
  31. package/dist/core/runner/git-status-frame.d.ts +7 -7
  32. package/dist/core/runner/inherited-ask-grants.d.ts +6 -6
  33. package/dist/core/runner/memory-capture-optout.d.ts +1 -1
  34. package/dist/core/runner/memory-consolidation-driver.d.ts +4 -4
  35. package/dist/core/runner/memory-consolidation.d.ts +6 -6
  36. package/dist/core/runner/park-commit.d.ts +6 -6
  37. package/dist/core/runner/permission-rule-lanes.d.ts +22 -20
  38. package/dist/core/runner/permission-rule-lanes.js +1 -1
  39. package/dist/core/runner/prepare-acquire-reconcile.d.ts +4 -4
  40. package/dist/core/runner/prepare-ask-lane.d.ts +3 -3
  41. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -3
  42. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -5
  43. package/dist/core/runner/prepare-config-doors.d.ts +21 -21
  44. package/dist/core/runner/prepare-context-lane.d.ts +6 -6
  45. package/dist/core/runner/prepare-defer-classify.d.ts +3 -3
  46. package/dist/core/runner/prepare-defer-classify.js +12 -10
  47. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
  48. package/dist/core/runner/prepare-file-history.d.ts +2 -2
  49. package/dist/core/runner/prepare-gate-stations.d.ts +8 -9
  50. package/dist/core/runner/prepare-gate-stations.js +2 -2
  51. package/dist/core/runner/prepare-hands-readface.d.ts +41 -41
  52. package/dist/core/runner/prepare-hands-readface.js +27 -29
  53. package/dist/core/runner/prepare-inherited-gate.d.ts +6 -6
  54. package/dist/core/runner/prepare-listings.d.ts +3 -3
  55. package/dist/core/runner/prepare-lsp.d.ts +3 -3
  56. package/dist/core/runner/prepare-memory-engine-session.d.ts +5 -5
  57. package/dist/core/runner/prepare-memory.js +6 -7
  58. package/dist/core/runner/prepare-offload-wrappers.d.ts +2 -2
  59. package/dist/core/runner/prepare-park-ask.d.ts +11 -12
  60. package/dist/core/runner/prepare-park-ask.js +3 -3
  61. package/dist/core/runner/prepare-policy-chain.d.ts +6 -6
  62. package/dist/core/runner/prepare-policy-chain.js +2 -2
  63. package/dist/core/runner/prepare-prompt-inputs.d.ts +5 -5
  64. package/dist/core/runner/prepare-protocol-tools.d.ts +1 -1
  65. package/dist/core/runner/prepare-question-face.d.ts +4 -4
  66. package/dist/core/runner/prepare-run-refs.d.ts +11 -11
  67. package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
  68. package/dist/core/runner/prepare-suspend-saga.d.ts +4 -4
  69. package/dist/core/runner/prepare-task.d.ts +12 -12
  70. package/dist/core/runner/prepare-task.js +5 -7
  71. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +2 -3
  72. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -4
  73. package/dist/core/runner/prepare-workspace-restore.d.ts +1 -1
  74. package/dist/core/runner/remote-env-retry.d.ts +3 -3
  75. package/dist/core/runner/resume-admission.d.ts +1 -1
  76. package/dist/core/runner/resume-claim.d.ts +1 -1
  77. package/dist/core/runner/resume-internals-and-config.d.ts +1 -1
  78. package/dist/core/runner/resume-policy-outcome.d.ts +1 -1
  79. package/dist/core/runner/resume-preflight.d.ts +1 -1
  80. package/dist/core/runner/resume-review-outcome.d.ts +1 -1
  81. package/dist/core/runner/run-brain-sinks.d.ts +1 -1
  82. package/dist/core/runner/run-clock-and-content.d.ts +4 -4
  83. package/dist/core/runner/run-compaction-machinery.d.ts +3 -3
  84. package/dist/core/runner/run-git-lane.d.ts +1 -1
  85. package/dist/core/runner/run-harness-handlers.d.ts +2 -2
  86. package/dist/core/runner/run-identity-wiring.d.ts +4 -4
  87. package/dist/core/runner/run-leg.d.ts +3 -3
  88. package/dist/core/runner/run-notification-lane.d.ts +2 -2
  89. package/dist/core/runner/run-reasoning-seat.d.ts +2 -2
  90. package/dist/core/runner/run-settle-and-teardown.d.ts +4 -4
  91. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  92. package/dist/core/runner/run-terminal-adoption.d.ts +5 -5
  93. package/dist/core/runner/runtask.d.ts +14 -14
  94. package/dist/core/runner/session-file-state-replay.d.ts +3 -3
  95. package/dist/core/runner/session-rule-policy.d.ts +2 -2
  96. package/dist/core/runner/steer-admission.d.ts +2 -2
  97. package/dist/core/runner/stream-halt-verbs.d.ts +1 -1
  98. package/dist/core/runner/stream-reap.d.ts +1 -1
  99. package/dist/core/runner/synthetic-tools.d.ts +8 -8
  100. package/dist/core/runner/teardown-bounded.d.ts +2 -2
  101. package/dist/core/runner/tool-defer-gate.d.ts +86 -0
  102. package/dist/core/runner/tool-defer-gate.js +57 -0
  103. package/dist/core/runner/tool-disclosure.d.ts +33 -69
  104. package/dist/core/runner/tool-disclosure.js +0 -43
  105. package/dist/core/runner/tool-end-body.d.ts +3 -3
  106. package/dist/core/runner/tool-face-overlay.d.ts +2 -2
  107. package/dist/core/runner/tool-output-projection.d.ts +2 -2
  108. package/dist/core/runner/turn-attachments.d.ts +53 -53
  109. package/dist/core/runner/usage-accounting.d.ts +4 -4
  110. package/dist/core/runner/workspace-path.d.ts +1 -1
  111. package/dist/core/runner-deps.d.ts +16 -9
  112. package/dist/core/task-spec.d.ts +8 -2
  113. package/dist/core/tool-spec.d.ts +7 -4
  114. package/dist/core/types.d.ts +1 -1
  115. package/dist/engine/llm/types.d.ts +1 -1
  116. package/dist/index.d.ts +4 -4
  117. package/dist/index.js +3 -3
  118. package/dist/tools/fs/bash-readonly-classifier.d.ts +40 -40
  119. package/dist/tools/fs/encoding.d.ts +31 -40
  120. package/dist/tools/fs/fs-bash.d.ts +76 -35
  121. package/dist/tools/fs/fs-bash.js +86 -29
  122. package/dist/tools/fs/fs-pdf.d.ts +8 -8
  123. package/dist/tools/fs/fs-search-tools.d.ts +4 -4
  124. package/dist/tools/fs/fs-shared.d.ts +144 -158
  125. package/dist/tools/fs/fs-write.d.ts +8 -8
  126. package/dist/tools/fs/gh-rate-limit.d.ts +13 -14
  127. package/dist/tools/fs/index.d.ts +74 -78
  128. package/dist/tools/fs/notebook.d.ts +24 -25
  129. package/dist/tools/fs/pdf.d.ts +3 -3
  130. package/dist/tools/fs/read-deny.d.ts +22 -22
  131. package/dist/tools/fs/read-face.d.ts +21 -24
  132. package/dist/tools/fs/repo-map.d.ts +8 -8
  133. package/dist/tools/fs/safety.d.ts +116 -129
  134. package/dist/tools/fs/search.d.ts +63 -71
  135. package/package.json +5 -2
  136. package/test/export-surface.snapshot.json +9 -1
@@ -4,11 +4,11 @@ import { type CwdRef } from "./fs-shared.js";
4
4
  import type { ReversibilityVerdict } from "../../core/types.js";
5
5
  import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
6
6
  /**
7
- * design/80 D-2 (part-1): a parsed-command classifier for the `bash` tool, exposed as a
7
+ * A parsed-command classifier for the `bash` tool, exposed as a
8
8
  * `ToolSpec.reversibilityProbe`. A full shell is treated as egress+irreversible by DEFAULT; this probe is the
9
- * "real parsed classifier" that lets a deployment safely auto-allow the provably-benign subset (the §4-OQ4
10
- * doctrine: shell⇒always-gate UNLESS a parsed classifier is wired). design/154 upgraded it from
11
- * single-command to {@link classifyCompoundReadonly}: a command is reversible ONLY if every `;`/`&&`/`||`/`|`
9
+ * "real parsed classifier" that lets a deployment safely auto-allow the provably-benign subset (the
10
+ * doctrine: shell⇒always-gate UNLESS a parsed classifier is wired). It is COMPOUND-aware
11
+ * ({@link classifyCompoundReadonly}): a command is reversible ONLY if every `;`/`&&`/`||`/`|`
12
12
  * connected segment is a bare command (no redirects/substitution/subshells/escapes/`&` anywhere — those
13
13
  * whole-string reject, so it can't chain to `curl`/`git push`/`rm` or background past TaskStop) whose
14
14
  * `argv[0]` is in the reversible allowlist (default
@@ -23,7 +23,7 @@ import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
23
23
  * Wire it on the `bash` tool with `irreversibility: "maybe"` (or via `TaskSpec.shellGate: "classify"`); without
24
24
  * a classifier a deployment should mark `bash` `irreversibility: "always"` (`shellGate: "always"`) — fail-closed.
25
25
  *
26
- * RB-412 closed the DATA-side residual named above for the read direction: pass `boundary` and a listed
26
+ * The DATA-side residual named above is closed for the read direction: pass `boundary` and a listed
27
27
  * reader whose path arguments leave the allowed directories stops being reversible, so the gate asks
28
28
  * instead of auto-allowing. Pass it as a FUNCTION wherever the working directory is observable (an
29
29
  * in-task `cd` moves the base a relative operand resolves against, and the probe is called per command).
@@ -38,7 +38,7 @@ import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
38
38
  * safety argument lives on that function). Additive by construction: the plain verdict is tried
39
39
  * first and is never overridden.
40
40
  *
41
- * backlog #239 — the RECURSIVE-READ demotion returns a STRUCTURED cause alongside the verdict. That
41
+ * The RECURSIVE-READ demotion returns a STRUCTURED cause alongside the verdict. That
42
42
  * demotion is the one arm whose reason is genuinely unrecoverable downstream: a recursive read is
43
43
  * refused for what the traversal REACHES, and the reached set is by definition not in the command text
44
44
  * an approval card shows. The cause is minted from the SAME classification pass that produced the
@@ -57,7 +57,48 @@ import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
57
57
  */
58
58
  export declare function bashReversibilityProbe(allow?: readonly string[], boundary?: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined)): (args: unknown) => ReversibilityVerdict;
59
59
  /**
60
- * design/199 D-6 the FULL shell's contract id, single-sourced: both shell faces share the wire
60
+ * The READ-BOUNDARY seat of the full shell: a `ToolSpec.reversibilityProbe` that answers exactly ONE
61
+ * question — does the deployment's read boundary object to where this command reads? — and nothing about
62
+ * the command's shape. It is the shell face of the rule every read face obeys: the structured readers
63
+ * (Read/Grep/Glob) refuse a deny-listed or out-of-root target; the shell reader gets a MANDATED ask for the
64
+ * same target. The engine installs it on Bash and Monitor whenever a real writable shell is mounted and the
65
+ * shellGate doctrine adds no residual-risk classifier of its own (`shellGate:"off"`, the default); under
66
+ * `"classify"` the doctrine installs {@link bashReversibilityProbe} instead, which composes THIS judgment
67
+ * with the read-only shape classifier, and under `"always"` the tier itself is the mandate and no probe runs.
68
+ * The boundary is therefore judged under every doctrine; the doctrine decides only what happens to the
69
+ * residual shell risk.
70
+ *
71
+ * The verdict, in the probe vocabulary the gate reads (`reversible` here means "the boundary raised no
72
+ * question — the seat vouches the call may run"):
73
+ * · the boundary DECLARED an objection — a deny-listed operand, a recursive walk under the deny judge, an
74
+ * operand outside the roots — ⇒ `{ reversible: false, mandated: true }` (+ the recursive-reach `cause`);
75
+ * · the boundary could NOT READ where the command reads — an unexpanded glob operand (the compound face
76
+ * reports it as undecided), a `~`-rooted operand with no home declared, a working-directory move the walk
77
+ * cannot model — ⇒ `{ reversible: false }` (fail-closed; a plain ask, which a stored allow rule may retire —
78
+ * the seat did not declare anything, it declined to guess);
79
+ * · otherwise ⇒ `{ reversible: true }`. This includes every command whose SHAPE the read faces refuse whole
80
+ * (an unlisted program, a redirection, a substitution, a segment that is not a reader): those shapes carry
81
+ * no read-face verdict, so this seat has no question to ask — the residual is the doctrine's, and under
82
+ * `"off"` the doctrine asks nothing. `run_in_background` is likewise not this seat's question (it changes
83
+ * WHEN the command runs, never WHERE it reads).
84
+ *
85
+ * Reach = the read faces' reach, and nothing wider: the compound face walks the operands of a command whose
86
+ * every segment is a listed reader, and the bounded poll-loop face walks a loop body of listed readers. A
87
+ * read hidden in a shape the faces refuse whole (`cat x | <unlisted>`, `$(cat x)`, `cat $DIR/x`, a launcher
88
+ * `env cat x`, a path-prefixed `/bin/cat x`) is not seen here — deny-listed or out-of-root alike — exactly
89
+ * the shapes the `"classify"` doctrine exists to ask about. The text-level containment gate the classify
90
+ * seat consults on its demotion arms is deliberately not consulted here ({@link boundaryDeclared}: it may
91
+ * add a mandate to an ask, never originate one). The poll-loop face reports its refusal as one sentence,
92
+ * so the boundary's share of it is read by removing the boundary's judging seats
93
+ * ({@link pollLoopBoundaryVerdict}).
94
+ *
95
+ * The reader set is the engine's classify set ({@link BASH_CLASSIFY_DEFAULT_ALLOW}) — here it is NOT an
96
+ * auto-allow list but the set of programs whose operands the faces read; a wider set means more reads are
97
+ * judged, never that more runs are vouched.
98
+ */
99
+ export declare function bashReadBoundaryProbe(boundary: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined)): (args: unknown) => ReversibilityVerdict;
100
+ /**
101
+ * The FULL shell's contract id, single-sourced: both shell faces share the wire
61
102
  * name "Bash", so this id is the ONE structural discriminator between the write-capable shell and
62
103
  * the read-only allowlist face (`core.bash_readonly@1`). Consumed by prepare-task's
63
104
  * fullShellReachable roster assertion (its first load-bearing consumer).
@@ -66,36 +107,36 @@ export { FULL_SHELL_CONTRACT_ID } from "../../core/tool-catalog-entries.js";
66
107
  /** The CC-verbatim exit-1 interpretation for `command`, or undefined when exit 1 means a real error.
67
108
  * Conservative parse: last `;`/`&&`/`||`/newline statement → last `|` pipeline segment → leading
68
109
  * command name (env-assignments skipped, path prefix stripped); `git grep`/`git diff` special-cased
69
- * (CC cLp). Exported for the 批④ unit tests. */
110
+ * (CC `cLp`). Exported for its unit tests. */
70
111
  export declare function bashExitOneInterpretation(command: string): string | undefined;
71
112
  export declare function canAutoBackground(command: string, depth?: number): boolean;
72
113
  /**
73
114
  * `bash` (effect:write) — a full shell. ⚠️ It runs with `rootCanonical` as the initial cwd but is NOT
74
115
  * sandboxed: a command can `cd` out, read/write/delete any path the process can reach, and use the
75
- * network. `rootPath` is a file-tool guard rail, NOT a bash sandbox (design/44 §5, DESIGN#6) real
76
- * isolation is the deployment's job (inject a chroot/container `ExecutionEnv`). Every call still goes
77
- * through the design/37 policy gate, which a multi-tenant deployment MUST wire to constrain it. The cwd
78
- * persists across calls (design/64 §8.1A): a per-task `cwdRef` starts at `rootCanonical` and is updated
116
+ * network. `rootPath` is a file-tool guard rail, NOT a bash sandbox real isolation is the
117
+ * deployment's job (inject a chroot/container `ExecutionEnv`). Every call still goes through the
118
+ * tool-policy gate, which a multi-tenant deployment MUST wire to constrain it. The cwd
119
+ * persists across calls: a per-task `cwdRef` starts at `rootCanonical` and is updated
79
120
  * from the shell's final pwd after each command.
80
121
  */
81
122
  export declare function createBashTool(env: ExecutionEnv, rootCanonical: string, coAuthor?: string | false, cwdRef?: CwdRef, taskOpts?: {
82
123
  taskRegistry?: TaskRegistry;
83
124
  taskOwner?: string;
84
125
  taskScope?: string;
85
- /** (design/129, mirrors MonitorToolOptions.sessionId): when the task runs INSIDE a session,
126
+ /** (mirrors MonitorToolOptions.sessionId): when the task runs INSIDE a session,
86
127
  * a background command registers session-resident (owner = sessionId, sessionScoped flag) — it
87
128
  * survives the run teardown like CC's bg shells and is reaped at the session terminal. Absent ⇒
88
129
  * run-scoped registration exactly as before (killed-with-receipt at teardown). */
89
130
  sessionId?: string;
90
- /** design/116 §7 G2b: completion-notification sink — a finished background command fires ONE
131
+ /** Completion-notification sink — a finished background command fires ONE
91
132
  * task-notification (priority "next": boundary interrupt, CC LocalShellTask posture). */
92
133
  onTaskNotification?: (n: import("../../core/task-notification.js").TaskNotificationPayload, opts?: {
93
134
  priority?: "now" | "next" | "later";
94
135
  }) => void;
95
- /** design/116 detach: per-tool-call detach hub — a fired signal adopts the running command as background. */
136
+ /** Per-tool-call detach hub — a fired signal adopts the running command as background. */
96
137
  detachHub?: import("../../core/tool-detach.js").ToolDetachHub;
97
138
  /**
98
- * RB-198 F1 (CC 220 `Zry`/`WZi.#m` parity) — EXPLICIT deployment opt-in: on this command's own
139
+ * EXPLICIT deployment opt-in: on this command's own
99
140
  * timeout, an eligible foreground command (see `canAutoBackground`'s own docstring — a
100
141
  * DELIBERATELY simplified, narrower-than-CC classifier) is adopted as a background task instead
101
142
  * of being killed. Defaults to `false` (the pre-existing, unconditional kill-on-timeout behavior)
@@ -110,7 +151,7 @@ export declare function createBashTool(env: ExecutionEnv, rootCanonical: string,
110
151
  */
111
152
  autoBackgroundOnTimeout?: boolean;
112
153
  /**
113
- * RB-220 — mirrors {@link import("../../core/types.js").TaskSpec.oneShot}: this run has no later
154
+ * Mirrors {@link import("../../core/types.js").TaskSpec.oneShot}: this run has no later
114
155
  * turn for an async background notification to land in (a headless `-p` process exits once the
115
156
  * turn ends). Branches the background-launch receipt's guidance toward an active block-wait
116
157
  * instruction instead of "you will be notified — do not poll", the same failure form
@@ -119,18 +160,18 @@ export declare function createBashTool(env: ExecutionEnv, rootCanonical: string,
119
160
  */
120
161
  oneShot?: boolean;
121
162
  /**
122
- * RB-371 ②: the EXTRA containment roots of the structured file tools (additionalDirectories,
163
+ * The EXTRA containment roots of the structured file tools (additionalDirectories,
123
164
  * canonical) — combined with `rootCanonical` and consulted ONLY for the post-`cd` disclosure
124
165
  * note (a committed cwd outside every root gets one trailer line saying the structured tools'
125
166
  * relative paths now resolve there and may be refused). Bash itself stays deliberately
126
167
  * unconfined; passing this never gates anything.
127
168
  */
128
169
  additionalRoots?: readonly string[];
129
- /** RB-370 ② (cli): override of the `timeout` parameter's DEFAULT budget, in ms — see
170
+ /** Override of the `timeout` parameter's DEFAULT budget, in ms — see
130
171
  * {@link import("./index.js").HandsToolkitOptions.bashDefaultTimeoutMs} for the resolution
131
172
  * contract (options over env over constant; invalid values discarded). */
132
173
  bashDefaultTimeoutMs?: number;
133
- /** RB-370 ② (cli): override of the `timeout` parameter's engine CEILING, in ms — only-widen
174
+ /** Override of the `timeout` parameter's engine CEILING, in ms — only-widen
134
175
  * vs the resolved default; see {@link import("./index.js").HandsToolkitOptions.bashMaxTimeoutMs}. */
135
176
  bashMaxTimeoutMs?: number;
136
177
  /** Structured sink for this leg's timeout-cap discard announcements — consumed by
@@ -138,36 +179,36 @@ export declare function createBashTool(env: ExecutionEnv, rootCanonical: string,
138
179
  * {@link import("./index.js").HandsToolkitOptions.onNotice} for the replace-vs-warn contract). */
139
180
  onNotice?: (n: import("../../core/types.js").EngineNotice) => void;
140
181
  /**
141
- * #181-F6 — whether the Monitor tool is on THIS run's roster (the Runner's own mount predicate is
182
+ * Whether the Monitor tool is on THIS run's roster (the Runner's own mount predicate is
142
183
  * `backgroundTaskToolsActive`; it, not this tool, mounts Monitor). Read by the SR-2 gh rate-limit
143
184
  * hint, whose closing clause teaches the Monitor tool: `false` ⇒ the clause is dropped (the sleep
144
185
  * advice stays), absent/`true` ⇒ historic full wording (a standalone toolkit consumer may well have
145
186
  * mounted Monitor itself — the omitted default must not silently rewrite its hint).
146
187
  */
147
188
  monitorToolActive?: boolean;
148
- /** design/319 (A ticket) — the session's reminder provenance mark: stamped on the gh rate-limit
189
+ /** The session's reminder provenance mark: stamped on the gh rate-limit
149
190
  * hint's open tag by the mint home (see {@link import("./index.js").HandsToolkitOptions.reminderMark}).
150
191
  * Absent ⇒ historic bare tag. */
151
192
  reminderMark?: string;
152
193
  }): AgentTool;
153
194
  /**
154
- * `bash_readonly` (effect:read) — a restricted shell for the verifier read-only boundary (design/44 M2):
155
- * a single allowlisted, bare command with no shell operators. effect:read lets it survive the verifier's
156
- * read-only filter, but it is NOT a policy circumvention — it still goes through the design/37 gate (council #7:
157
- * a read can still be a side channel, e.g. dumping a secret file). The allowlist is a coarse pre-filter;
195
+ * `bash_readonly` (effect:read) — a restricted shell for the verifier read-only boundary: a single
196
+ * allowlisted, bare command with no shell operators. effect:read lets it survive the verifier's
197
+ * read-only filter, but it is NOT a policy circumvention — it still goes through the tool-policy gate,
198
+ * because a read can still be a side channel (dumping a secret file). The allowlist is a coarse pre-filter;
158
199
  * the policy gate is the authoritative control.
159
200
  *
160
- * RB-413 (安全面, adjudicated: hard refusal) that allowlist vets argv[0] and nothing else, so until this
161
- * leg carried a path boundary an allowlisted reader could name ANY file on the host and get it: `cat
162
- * /etc/passwd` went straight through. RB-412 gave the full `bash` leg the same boundary as a DEMOTION —
163
- * that leg's verdict feeds an approval gate, so "outside the roots" can become a prompt. This face has no
201
+ * Hard refusal, not a demotion: the allowlist vets argv[0] and nothing else, so without a path
202
+ * boundary an allowlisted reader could name ANY file on the host and get it (`cat /etc/passwd` would go
203
+ * straight through). The full `bash` leg carries the same boundary as a DEMOTION — that leg's verdict
204
+ * feeds an approval gate, so "outside the roots" can become a prompt. This face has no
164
205
  * such channel: `effect:"read"` is precisely what lets it run where the write shell may not (the verifier's
165
206
  * read-only band), so an out-of-root read here is not something to escalate, it is outside what the tool
166
207
  * is. It is refused, fail-closed, before anything executes — including the conservative direction, where
167
208
  * the operand cannot be resolved statically (there is nothing to degrade to).
168
209
  */
169
210
  export declare function createBashReadonlyTool(env: ExecutionEnv, rootCanonical: string, allow: ReadonlySet<string>, opts?: {
170
- /** RB-370 ② (cli): same timeout-cap overrides the full `bash` leg takes — see
211
+ /** Same timeout-cap overrides the full `bash` leg takes — see
171
212
  * {@link import("./index.js").HandsToolkitOptions.bashDefaultTimeoutMs} /
172
213
  * {@link import("./index.js").HandsToolkitOptions.bashMaxTimeoutMs} (类修完整域: both foreground
173
214
  * legs share runShell's clamp, so both must share the configurable resolution). */
@@ -176,21 +217,21 @@ export declare function createBashReadonlyTool(env: ExecutionEnv, rootCanonical:
176
217
  /** Structured sink for the timeout-cap discard announcements — same seat the full `bash` leg
177
218
  * takes (both foreground legs share the resolver, so both must share the sink). */
178
219
  onNotice?: (n: import("../../core/types.js").EngineNotice) => void;
179
- /** RB-413: the extra containment roots the structured file tools got (design/119 `--add-dir`,
220
+ /** The extra containment roots the structured file tools got (the CC `--add-dir` family,
180
221
  * canonical). A read this deployment already sanctions for Read/Grep/Glob is equally sanctioned
181
222
  * here — the two faces must not disagree about which directories exist for this session. */
182
223
  additionalRoots?: readonly string[];
183
224
  }): AgentTool;
184
225
  /**
185
- * design/115 P0 `TaskOutput` (legacy aliases: BashOutput/AgentOutput*) — read a background shell's NEW output
226
+ * `TaskOutput` — read a background shell's NEW output
186
227
  * since the last call (cursor), by task_id. Optional legacy `filter` regex is applied BEFORE the per-poll truncation so a watched line survives even when it
187
228
  * falls in a high-throughput middle window. Untrusted process output is fenced (delimitUntrusted) — observe-only,
188
229
  * never re-fed as instructions.
189
230
  */
190
231
  export declare function createEnvTaskOutputTool(env: ExecutionEnv): AgentTool;
191
- /** design/115 P0 `TaskStop` (RB-476-A: legacy aliases cleared) — terminate a background shell by task_id.
232
+ /** `TaskStop` — terminate a background shell by task_id.
192
233
  *
193
- * design/134 KNOWN-ISSUES close-out: this band kills ENV-DIRECT (it mounts precisely when the toolkit has
234
+ * This band kills ENV-DIRECT (it mounts precisely when the toolkit has
194
235
  * no registry — the createHandsToolkit ternary routes registry deployments to createTaskStopTool), so a
195
236
  * row for the SAME shell in the process-local {@link defaultTaskRegistry} (a Runner-mounted run sharing
196
237
  * this env) used to settle via the watcher's no-claimant floor as stoppedBy:"system". The tool now marks
@@ -25,40 +25,100 @@ function operandFamily(paths) {
25
25
  export function bashReversibilityProbe(allow, boundary) {
26
26
  const allowSet = new Set(allow ?? BASH_CLASSIFY_DEFAULT_ALLOW);
27
27
  return (args) => {
28
- const a = args;
29
- const command = a?.command;
30
- if (typeof command !== "string")
28
+ const command = shellCommandOf(args);
29
+ if (command === undefined)
31
30
  return { reversible: false };
32
31
  const resolved = typeof boundary === "function" ? boundary() : boundary;
33
- const outOfRootGate = () => resolved !== undefined && classifyOutOfRootReadGate(command, allowSet, resolved).gated ? { mandated: true } : {};
34
- const boundaryGate = (verdict) => verdict.readDenied === true || (verdict.recursiveReadPaths !== undefined && verdict.recursiveReadPaths.length > 0) ? { mandated: true } : outOfRootGate();
35
- const backgrounded = a?.run_in_background === true;
32
+ const boundaryGate = (verdict) => readBoundaryMandate(command, allowSet, resolved, verdict);
33
+ const outOfRootGate = () => readBoundaryMandate(command, allowSet, resolved, {});
34
+ const backgrounded = isBackgroundShellCall(args);
36
35
  const detailed = classifyCompoundReadonlyDetailed(command, allowSet, resolved);
37
36
  if (backgrounded)
38
37
  return { reversible: false, ...boundaryGate(detailed) };
39
38
  if (detailed.reason === undefined) {
40
39
  if (detailed.undecidedPaths !== undefined && detailed.undecidedPaths.length > 0) {
41
- const recursive = detailed.recursiveReadPaths;
42
- if (recursive !== undefined && recursive.length > 0) {
43
- const recursiveSet = new Set(recursive);
44
- const others = detailed.undecidedPaths.filter((p) => !recursiveSet.has(p));
45
- return {
46
- reversible: false,
47
- ...boundaryGate(detailed),
48
- cause: {
49
- code: RECURSIVE_READ_CAUSE_CODE,
50
- roots: operandFamily(recursive),
51
- ...(others.length > 0 ? { further: operandFamily(others) } : {}),
52
- },
53
- };
54
- }
40
+ const cause = recursiveReadCause(detailed);
41
+ if (cause !== undefined)
42
+ return { reversible: false, ...boundaryGate(detailed), cause };
55
43
  return { reversible: false, ...outOfRootGate() };
56
44
  }
57
45
  return { reversible: true };
58
46
  }
59
- return classifyBoundedReadonlyPollLoop(command, allowSet, resolved) === undefined
60
- ? { reversible: true }
61
- : { reversible: false, ...boundaryGate(detailed) };
47
+ const loopRefusal = classifyBoundedReadonlyPollLoop(command, allowSet, resolved);
48
+ if (loopRefusal === undefined)
49
+ return { reversible: true };
50
+ return { reversible: false, ...(pollLoopBoundaryVerdict(command, allowSet, resolved, loopRefusal) === "declared" ? { mandated: true } : boundaryGate(detailed)) };
51
+ };
52
+ }
53
+ function attributeRefusal(face, resolved, fullRefusal) {
54
+ if (resolved === undefined)
55
+ return "shape";
56
+ const { denyMatch: _deny, ...withoutDenyJudge } = resolved;
57
+ const opened = face({ ...withoutDenyJudge, face: "open" });
58
+ if (opened === undefined || opened !== fullRefusal)
59
+ return "declared";
60
+ return face(undefined) === opened ? "shape" : "unread";
61
+ }
62
+ function pollLoopBoundaryVerdict(command, allowSet, resolved, fullRefusal) {
63
+ return attributeRefusal((b) => classifyBoundedReadonlyPollLoop(command, allowSet, b), resolved, fullRefusal);
64
+ }
65
+ function boundaryDeclared(verdict) {
66
+ return denyJudgeSpoke(verdict) || verdict.outOfRootRead === true;
67
+ }
68
+ function denyJudgeSpoke(verdict) {
69
+ return verdict.readDenied === true || (verdict.recursiveReadPaths !== undefined && verdict.recursiveReadPaths.length > 0);
70
+ }
71
+ function shellCommandOf(args) {
72
+ const command = args?.command;
73
+ return typeof command === "string" ? command : undefined;
74
+ }
75
+ function isBackgroundShellCall(args) {
76
+ return args?.run_in_background === true;
77
+ }
78
+ function readBoundaryMandate(command, allowSet, resolved, verdict) {
79
+ if (denyJudgeSpoke(verdict))
80
+ return { mandated: true };
81
+ return resolved !== undefined && classifyOutOfRootReadGate(command, allowSet, resolved).gated ? { mandated: true } : {};
82
+ }
83
+ function recursiveReadCause(detailed) {
84
+ const recursive = detailed.recursiveReadPaths;
85
+ if (recursive === undefined || recursive.length === 0)
86
+ return undefined;
87
+ const recursiveSet = new Set(recursive);
88
+ const others = (detailed.undecidedPaths ?? []).filter((p) => !recursiveSet.has(p));
89
+ return {
90
+ code: RECURSIVE_READ_CAUSE_CODE,
91
+ roots: operandFamily(recursive),
92
+ ...(others.length > 0 ? { further: operandFamily(others) } : {}),
93
+ };
94
+ }
95
+ export function bashReadBoundaryProbe(boundary) {
96
+ const allowSet = new Set(BASH_CLASSIFY_DEFAULT_ALLOW);
97
+ return (args) => {
98
+ const command = shellCommandOf(args);
99
+ if (command === undefined)
100
+ return { reversible: false };
101
+ const resolved = typeof boundary === "function" ? boundary() : boundary;
102
+ if (resolved === undefined)
103
+ return { reversible: true };
104
+ const detailed = classifyCompoundReadonlyDetailed(command, allowSet, resolved);
105
+ if (boundaryDeclared(detailed)) {
106
+ const cause = recursiveReadCause(detailed);
107
+ return { reversible: false, mandated: true, ...(cause !== undefined ? { cause } : {}) };
108
+ }
109
+ if (detailed.reason === undefined) {
110
+ return detailed.undecidedPaths !== undefined && detailed.undecidedPaths.length > 0 ? { reversible: false } : { reversible: true };
111
+ }
112
+ const compound = attributeRefusal((b) => classifyCompoundReadonlyDetailed(command, allowSet, b).reason, resolved, detailed.reason);
113
+ if (compound === "declared")
114
+ return { reversible: false, mandated: true };
115
+ if (compound === "unread")
116
+ return { reversible: false };
117
+ const loopRefusal = classifyBoundedReadonlyPollLoop(command, allowSet, resolved);
118
+ if (loopRefusal === undefined)
119
+ return { reversible: true };
120
+ const loop = pollLoopBoundaryVerdict(command, allowSet, resolved, loopRefusal);
121
+ return loop === "shape" ? { reversible: true } : loop === "declared" ? { reversible: false, mandated: true } : { reversible: false };
62
122
  };
63
123
  }
64
124
  export { FULL_SHELL_CONTRACT_ID } from "../../core/tool-catalog-entries.js";
@@ -563,13 +623,10 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
563
623
  })),
564
624
  }),
565
625
  isConcurrencySafe: (args) => {
566
- const cmd = args?.command;
567
- if (typeof cmd !== "string")
568
- return false;
569
- const bg = args.run_in_background;
570
- if (bg === true)
626
+ const command = shellCommandOf(args);
627
+ if (command === undefined || isBackgroundShellCall(args))
571
628
  return false;
572
- return coarseReadonlyCheck(cmd, new Set(BASH_READONLY_DEFAULT_ALLOW)) === undefined;
629
+ return coarseReadonlyCheck(command, new Set(BASH_READONLY_DEFAULT_ALLOW)) === undefined;
573
630
  },
574
631
  execute: async (args, ctx) => {
575
632
  const { command, timeout, run_in_background, description } = args;
@@ -4,7 +4,7 @@ import type { ExecutionEnv } from "../../internal/harness-types.js";
4
4
  import { type PdfModelCapabilities } from "./pdf.js";
5
5
  import { type ReadImageDownsamplerOption } from "./fs-shared.js";
6
6
  import { type ReminderDisclosureCounts } from "../../core/reminder-disclosure.js";
7
- /** design/319 (B ticket) — the Read tool's disclosure state, threaded into the PDF TEXT legs (the
7
+ /** The Read tool's disclosure state, threaded into the PDF TEXT legs (the
8
8
  * two `pdftotext` extraction arms — the only PDF returns with a model-facing text projection of
9
9
  * the document; the native document block and rendered page images have no text to scan). Shares
10
10
  * the Read closure's throttle windows so a PDF and its text read dedup on the same file key. */
@@ -21,7 +21,7 @@ type ReadPdfReturn = string | {
21
21
  details: unknown;
22
22
  };
23
23
  /**
24
- * PDF read pipeline (CC FileReadTool parity — mechanism取证 + limits in fs/pdf.ts):
24
+ * PDF read pipeline (CC FileReadTool parity — mechanism + limits in fs/pdf.ts):
25
25
  * - no `pages` → whole file as a native `document` block, gated at ≤{@link PDF_TARGET_RAW_SIZE} raw and
26
26
  * ≤{@link PDF_INLINE_PAGE_THRESHOLD} pages (page count via `pdfinfo`, byte-scan fallback; unknown = fail-open,
27
27
  * CC-identical — the size gate still bounds the request);
@@ -30,11 +30,11 @@ type ReadPdfReturn = string | {
30
30
  * degradation with the working alternative, never a fake success.
31
31
  * Wire discipline: the base64 payload rides `details.file` only while ≤48K chars (same OMIT rule as the
32
32
  * image variant — a bigger card would be dropped whole by the card projector's 4×cap and blind the shell).
33
- * structured-card audit item B (2026-08-05): that budget management assumed `"document"` was on `CC_DETAIL_TYPES`, which
34
- * it was not — every `document` card, sized or not, was dropped at the gate, making the 48K rule dead
35
- * code and the pages/fallback-level disclosure invisible to a host. The word is registered now.
33
+ * The budget rule only means anything while `"document"` is registered on `CC_DETAIL_TYPES`: without
34
+ * that word every `document` card, sized or not, is dropped at the gate, which makes the 48K rule dead
35
+ * code and the pages/fallback-level disclosure invisible to a host.
36
36
  *
37
- * Degradation chain v2 (裁定: the brain-level placeholder is NOT the end state): when the SERVING model
37
+ * Degradation chain (the brain-level placeholder is NOT the end state): when the SERVING model
38
38
  * cannot take a native document block (`caps.document === false` — e.g. any openai-compatible API), the
39
39
  * whole-PDF path degrades IN THE TOOL (the only layer holding env.exec), best first:
40
40
  * 1. `pdftotext -layout` text extraction → the content arrives as TEXT (Read byte-cap discipline applies);
@@ -42,10 +42,10 @@ type ReadPdfReturn = string | {
42
42
  * pages via the existing `pdftoppm` pipeline (note directs `pages` for more);
43
43
  * 3. honest placeholder explaining WHY levels 1–2 did not run + the Bash self-help.
44
44
  * Every degraded return carries `details.fallback = { level, reason }` (telemetry on the structured frame).
45
- * `caps` absent ⇒ fully capable (byte-compat: native document block; the brain placeholder still guards).
45
+ * `caps` absent ⇒ fully capable (native document block; the brain placeholder still guards).
46
46
  */
47
47
  export declare function readPdfFile(env: ExecutionEnv, path: string, key: string, pages: string | undefined, signal: AbortSignal | undefined, downsamplerOpt: ReadImageDownsamplerOption, cwd: string, preRead?: Uint8Array, caps?: PdfModelCapabilities, readDeny?: import("./read-deny.js").ReadDenyMatcher, disclosure?: PdfReminderDisclosure): Promise<ReadPdfReturn>;
48
- /** E1: readPdfFile's own return type stays `ReadPdfReturn` (its INTERNAL string-means-error dispatch
48
+ /** readPdfFile's own return type stays `ReadPdfReturn` (its INTERNAL string-means-error dispatch
49
49
  * contract, shared with pdfPagesToImageBlocks) — the isError flag is applied once, here, at the tool's
50
50
  * actual execute() boundary, not inside the helper. */
51
51
  export declare function pdfResultToToolReturn(r: ReadPdfReturn): ReturnType<typeof errorResult> | Exclude<ReadPdfReturn, string>;
@@ -5,10 +5,10 @@ import type { CwdRef } from "./fs-shared.js";
5
5
  /**
6
6
  * The Grep card's details assembly, PURE over the engine text ({mode, offset} from the request) —
7
7
  * exported so the text→structured mapping is pinnable with synthetic texts (the byte-truncation and
8
- * fenced-partial shapes are impractical to construct through a live tool call). #313 moved PATH
9
- * IDENTITY off the text and onto the engines' served rows (both legs supply them now); the counts
10
- * and totals below still read the engine's own honesty markers out of the text, which is what keeps
11
- * this seam the honesty boundary.
8
+ * fenced-partial shapes are impractical to construct through a live tool call). PATH IDENTITY comes
9
+ * off the engines' served rows, not the text (both legs supply them); the counts and totals below
10
+ * still read the engine's own honesty markers out of the text, which is what keeps this seam the
11
+ * honesty boundary.
12
12
  */
13
13
  export declare function grepDetailFields(text: string, mode: "files_with_matches" | "content" | "count", offset?: number, structuredRows?: readonly import("./search.js").GrepRow[]): Record<string, unknown>;
14
14
  export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace, cwdRef?: CwdRef): AgentTool;