@celestea/tools 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/dist/args.d.ts +15 -0
  3. package/dist/args.js +55 -0
  4. package/dist/attachments/image-header.d.ts +28 -0
  5. package/dist/attachments/image-header.js +148 -0
  6. package/dist/attachments/store.d.ts +56 -0
  7. package/dist/attachments/store.js +148 -0
  8. package/dist/browser/cdp.d.ts +162 -0
  9. package/dist/browser/cdp.js +269 -0
  10. package/dist/browser/launch.d.ts +92 -0
  11. package/dist/browser/launch.js +232 -0
  12. package/dist/browser/memory-guard.d.ts +55 -0
  13. package/dist/browser/memory-guard.js +210 -0
  14. package/dist/browser/session.d.ts +146 -0
  15. package/dist/browser/session.js +371 -0
  16. package/dist/browser/snapshot.d.ts +69 -0
  17. package/dist/browser/snapshot.js +163 -0
  18. package/dist/browser/types.d.ts +51 -0
  19. package/dist/browser/types.js +8 -0
  20. package/dist/builtin.d.ts +54 -0
  21. package/dist/builtin.js +77 -0
  22. package/dist/desc.d.ts +20 -0
  23. package/dist/desc.js +22 -0
  24. package/dist/disclosure.d.ts +113 -0
  25. package/dist/disclosure.js +141 -0
  26. package/dist/env.d.ts +10 -0
  27. package/dist/env.js +33 -0
  28. package/dist/errors.d.ts +25 -0
  29. package/dist/errors.js +60 -0
  30. package/dist/exposure.d.ts +145 -0
  31. package/dist/exposure.js +244 -0
  32. package/dist/fn-tool.d.ts +14 -0
  33. package/dist/fn-tool.js +14 -0
  34. package/dist/fs/file-io.d.ts +78 -0
  35. package/dist/fs/file-io.js +239 -0
  36. package/dist/guard/path-guard.d.ts +144 -0
  37. package/dist/guard/path-guard.js +289 -0
  38. package/dist/guard/paths.d.ts +35 -0
  39. package/dist/guard/paths.js +100 -0
  40. package/dist/http/errors.d.ts +24 -0
  41. package/dist/http/errors.js +64 -0
  42. package/dist/http/headers.d.ts +19 -0
  43. package/dist/http/headers.js +62 -0
  44. package/dist/http/redirects.d.ts +31 -0
  45. package/dist/http/redirects.js +76 -0
  46. package/dist/http/ssrf.d.ts +105 -0
  47. package/dist/http/ssrf.js +272 -0
  48. package/dist/http/transport.d.ts +50 -0
  49. package/dist/http/transport.js +130 -0
  50. package/dist/index.d.ts +114 -0
  51. package/dist/index.js +129 -0
  52. package/dist/memory/log.d.ts +75 -0
  53. package/dist/memory/log.js +157 -0
  54. package/dist/memory/store.d.ts +47 -0
  55. package/dist/memory/store.js +61 -0
  56. package/dist/platform/exec.d.ts +79 -0
  57. package/dist/platform/exec.js +218 -0
  58. package/dist/platform/index.d.ts +12 -0
  59. package/dist/platform/index.js +12 -0
  60. package/dist/platform/paths.d.ts +51 -0
  61. package/dist/platform/paths.js +60 -0
  62. package/dist/platform/quote.d.ts +72 -0
  63. package/dist/platform/quote.js +102 -0
  64. package/dist/plugin.d.ts +96 -0
  65. package/dist/plugin.js +101 -0
  66. package/dist/process/buffers.d.ts +33 -0
  67. package/dist/process/buffers.js +86 -0
  68. package/dist/process/registry.d.ts +98 -0
  69. package/dist/process/registry.js +282 -0
  70. package/dist/registry.d.ts +52 -0
  71. package/dist/registry.js +161 -0
  72. package/dist/run-code/broker.d.ts +68 -0
  73. package/dist/run-code/broker.js +465 -0
  74. package/dist/run-code/limits.d.ts +69 -0
  75. package/dist/run-code/limits.js +88 -0
  76. package/dist/run-code/lines.d.ts +69 -0
  77. package/dist/run-code/lines.js +199 -0
  78. package/dist/run-code/sdk-ts.d.ts +34 -0
  79. package/dist/run-code/sdk-ts.js +276 -0
  80. package/dist/run-code/sdk.d.ts +39 -0
  81. package/dist/run-code/sdk.js +294 -0
  82. package/dist/sandbox/async.d.ts +10 -0
  83. package/dist/sandbox/async.js +26 -0
  84. package/dist/sandbox/bwrap-argv.d.ts +62 -0
  85. package/dist/sandbox/bwrap-argv.js +113 -0
  86. package/dist/sandbox/bwrap.d.ts +94 -0
  87. package/dist/sandbox/bwrap.js +159 -0
  88. package/dist/sandbox/child.d.ts +38 -0
  89. package/dist/sandbox/child.js +98 -0
  90. package/dist/sandbox/config.d.ts +89 -0
  91. package/dist/sandbox/config.js +149 -0
  92. package/dist/sandbox/fake-sandbox.d.ts +57 -0
  93. package/dist/sandbox/fake-sandbox.js +110 -0
  94. package/dist/sandbox/launch.d.ts +51 -0
  95. package/dist/sandbox/launch.js +134 -0
  96. package/dist/sandbox/limits.d.ts +63 -0
  97. package/dist/sandbox/limits.js +113 -0
  98. package/dist/sandbox/probe.d.ts +46 -0
  99. package/dist/sandbox/probe.js +102 -0
  100. package/dist/sandbox/provider.d.ts +83 -0
  101. package/dist/sandbox/provider.js +126 -0
  102. package/dist/sandbox/rlimit.d.ts +60 -0
  103. package/dist/sandbox/rlimit.js +76 -0
  104. package/dist/sandbox/seccomp.d.ts +48 -0
  105. package/dist/sandbox/seccomp.js +115 -0
  106. package/dist/sandbox/userspace.d.ts +65 -0
  107. package/dist/sandbox/userspace.js +107 -0
  108. package/dist/sandbox/workdir.d.ts +13 -0
  109. package/dist/sandbox/workdir.js +44 -0
  110. package/dist/schema.d.ts +20 -0
  111. package/dist/schema.js +135 -0
  112. package/dist/testing/platform-gates.d.ts +54 -0
  113. package/dist/testing/platform-gates.js +62 -0
  114. package/dist/tool-failure.d.ts +13 -0
  115. package/dist/tool-failure.js +19 -0
  116. package/dist/tools/ask-user.d.ts +32 -0
  117. package/dist/tools/ask-user.js +145 -0
  118. package/dist/tools/browser.d.ts +24 -0
  119. package/dist/tools/browser.js +132 -0
  120. package/dist/tools/http-request.d.ts +31 -0
  121. package/dist/tools/http-request.js +117 -0
  122. package/dist/tools/list-dir.d.ts +9 -0
  123. package/dist/tools/list-dir.js +45 -0
  124. package/dist/tools/load-skill.d.ts +37 -0
  125. package/dist/tools/load-skill.js +76 -0
  126. package/dist/tools/memory.d.ts +46 -0
  127. package/dist/tools/memory.js +131 -0
  128. package/dist/tools/process-control.d.ts +14 -0
  129. package/dist/tools/process-control.js +49 -0
  130. package/dist/tools/read-file.d.ts +11 -0
  131. package/dist/tools/read-file.js +81 -0
  132. package/dist/tools/read-image.d.ts +33 -0
  133. package/dist/tools/read-image.js +144 -0
  134. package/dist/tools/run-code.d.ts +48 -0
  135. package/dist/tools/run-code.js +115 -0
  136. package/dist/tools/run-shell.d.ts +22 -0
  137. package/dist/tools/run-shell.js +81 -0
  138. package/dist/tools/write-file.d.ts +8 -0
  139. package/dist/tools/write-file.js +31 -0
  140. package/package.json +28 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * `exposedRegistry` — the MODEL-VISIBLE face of one session's tool registry.
3
+ *
4
+ * `docs/modes-standard-vs-execution.md` §5.2 #1/#2 (P1): in `execution` mode the
5
+ * four SDK-covered tools (`read_file` / `write_file` / `list_dir` / `run_shell`)
6
+ * are no longer offered for a DIRECT call — they are reached from inside a
7
+ * `run_code` program. The engine's registry keeps every tool registered; what
8
+ * changes is the face the model sees and the door a model-initiated call knocks
9
+ * on.
10
+ *
11
+ * Two invariants make this a decorator and not a second registry:
12
+ *
13
+ * 1. **The verdict never lies** (`registry.ts`, W738 P1): a folded call is
14
+ * REFUSED before anything runs, so it is reported as a `deny` whose reason is
15
+ * the very text the caller sees — never an `allow` for a call the seam
16
+ * declined to execute.
17
+ * 2. **`run_code` sub-calls are NOT folded** (§5.2 #2, M8): the `run_code` tool
18
+ * holds a `RegistryHandle` bound to the INNER registry (`plugin.ts`), and this
19
+ * decorator is only ever the Context-provided face. A program's
20
+ * `tools.read_file(...)` therefore rides the inner pipeline unchanged —
21
+ * nothing here inspects the `:c<n>` id shape of a sub-call.
22
+ *
23
+ * `register` / `addGuard` / `get` pass straight through: the decorator never
24
+ * owns tools or guards, it only filters what is LISTED and gates what is
25
+ * DISPATCHED BY NAME from the model side.
26
+ *
27
+ * W806 (P0) adds the second, dynamic layer without adding a second decorator:
28
+ * `hidden` may be a LIVE PROVIDER, `order` fixes the wire order to a stable
29
+ * disclosure order, and `onHidden` lets a policy remember a refused direct call.
30
+ * See `disclosure.ts` for the policy itself. Nothing about the mode fold or the
31
+ * `run_code` escape hatch changes.
32
+ */
33
+ import type { ToolRegistry, ToolSpec } from "@celestea/core";
34
+ /**
35
+ * Stable marker of a folded call (S3/M8). It is part of the observable contract:
36
+ * a caller branches on this token, never on the prose around it.
37
+ */
38
+ export declare const TOOL_UNAVAILABLE_CODE = "tool_unavailable_in_mode";
39
+ /**
40
+ * The tool face of `execution` mode — module-level data, never a literal list at
41
+ * a call site (K3, §5.2 #3). `standard` mode exposes the whole registry.
42
+ *
43
+ * The list is a KEEP list on purpose: a tool registered later (W783's
44
+ * `ask_user_question`, a future orchestration tool) must be *decided* about
45
+ * rather than silently inherited by both modes. Anything outside it is folded —
46
+ * which is why the execution face is exactly these eight names (M7).
47
+ *
48
+ * W884 adds `load_skill` to the keep list. It is a PURE READ of the session's
49
+ * own skill layers (no writes, no process, no network), and — unlike the four
50
+ * SDK-covered tools — it is NOT reachable from a `run_code` program
51
+ * (`SDK_TOOLS` exposes only read_file / write_file / list_dir / run_shell).
52
+ * Folding it would therefore make skills UNREACHABLE in execution mode while the
53
+ * turn-start catalog still advertises them: a prompt that lies about what the
54
+ * model can do, which §6.5 of the disclosure design forbids. Keeping it costs
55
+ * one schema and preserves progressive disclosure in both modes.
56
+ */
57
+ export declare const EXECUTION_TOOL_NAMES: readonly string[];
58
+ /**
59
+ * The frozen guidance text of one folded call (S3). It names the `{tool}` slot
60
+ * and gives the model the TWO documented ways out (R1/R2): write a program, or
61
+ * switch the session back to standard mode.
62
+ */
63
+ export declare const EXECUTION_GUIDANCE = "'{tool}' is not directly callable in execution mode: write ONE `run_code` program that calls tools.{tool}(...) \u2014 a program's sub-calls always run \u2014 or switch the session back to standard mode";
64
+ /**
65
+ * W806: the refusal text of a tool that is merely NOT YET disclosed. It is
66
+ * deliberately distinct from [EXECUTION_GUIDANCE]: the name was withheld by the
67
+ * dynamic layer, not folded by the mode, so "write a program" would be the wrong
68
+ * advice. The refused call IS the request — the name joins the face at the next
69
+ * turn boundary (design §7.1, Q1/Q4 pending).
70
+ */
71
+ export declare const DISCLOSURE_GUIDANCE = "'{tool}' is not offered yet: it is disclosed at the NEXT turn boundary \u2014 this refused call is the request";
72
+ /** `tool_unavailable_in_mode: '<name>' …` — the refusal text of one folded call. */
73
+ export declare function unavailableError(name: string, guidance?: string): string;
74
+ export interface ExposureOptions {
75
+ /**
76
+ * Tool names the model must not call directly (still registered inside).
77
+ * A fixed array, or a provider read once per `schemas()` / `dispatch()` call
78
+ * (W806: the dynamic layer publishes a new snapshot at a turn boundary).
79
+ */
80
+ hidden: readonly string[] | (() => readonly string[]);
81
+ /** Refusal text template; `{tool}` is replaced with the folded name. */
82
+ guidance?: string;
83
+ /**
84
+ * W806: per-name guidance, wins over [guidance]. A face can fold some names
85
+ * by mode and withhold others dynamically, and the two refusals must not
86
+ * borrow each other's prose.
87
+ */
88
+ guidanceFor?: (name: string) => string;
89
+ /**
90
+ * W806: stable disclosure order. When present, `schemas()` emits the visible
91
+ * specs in exactly this order, appending any visible name the list missed.
92
+ * Because a disclosure order only grows by appending, the wire array stays
93
+ * append-only across a session — the cache-safe shape (design §3.5/S2).
94
+ */
95
+ order?: readonly string[] | (() => readonly string[]);
96
+ /**
97
+ * W806: called once when `dispatch` refuses a name because it is hidden. The
98
+ * dynamic policy records it and discloses it at the NEXT turn boundary; this
99
+ * is the only place a refusal can turn into a proposal.
100
+ */
101
+ onHidden?: (name: string) => void;
102
+ }
103
+ /**
104
+ * The exposure of `execution` mode over a registry holding [names]: every name
105
+ * outside [EXECUTION_TOOL_NAMES] is folded (§5.2 #3).
106
+ */
107
+ export declare function executionExposure(names: readonly string[]): ExposureOptions;
108
+ /**
109
+ * Filter specs by an exposure — the rule `ExposedRegistry.schemas()` starts from.
110
+ * Stable ORDER is the decorator's own stateful projection (see `stableProjection`),
111
+ * not a pure function of this call: a name must keep the wire position it FIRST
112
+ * had, even when a tool registers after the policy was built.
113
+ */
114
+ export declare function exposedSpecs(specs: readonly ToolSpec[], options: ExposureOptions): ToolSpec[];
115
+ /**
116
+ * The model-visible face of a spec list under a mode literal: `execution` folds
117
+ * everything outside [EXECUTION_TOOL_NAMES], `standard` (and any unknown value)
118
+ * keeps the list as it is.
119
+ *
120
+ * This is the COMPOSE-TIME reading of the same rule, and it exists because the
121
+ * face has to be knowable BEFORE the instance that will expose it exists: the
122
+ * system prompt of a session is assembled while that very session is being
123
+ * composed (`sessionSystemPrompt`), so asking the registry for "the live
124
+ * instance" would answer with the PREVIOUS generation — or, on the first
125
+ * compose, with the detached default's 11-tool face (design §10.5 #2).
126
+ *
127
+ * W806 keeps this STATIC on purpose: the rendered `{{tools}}` list is the
128
+ * mode's disclosable universe, never the per-turn disclosed subset. System text
129
+ * is serialized BEFORE tools, so making it follow disclosure would invalidate
130
+ * the whole request prefix from token 0 (design §3.4/P4).
131
+ *
132
+ * W857 adds the optional [blocked] list: the permission baseline's `toolDeny`
133
+ * (W9). It is an INTERSECTION applied AFTER the fold — never a union — so a name
134
+ * the mode already folded cannot be restored and a name outside the folded face
135
+ * simply has no effect. Callers that read the same face as a composed instance
136
+ * (`RealRuntimeAdapter.sessionTools` vs `engineTools`' `DisclosurePolicy`)
137
+ * pass the same list through here so the two paths cannot drift (W791 §10.5 #2).
138
+ */
139
+ export declare function faceForMode(specs: readonly ToolSpec[], mode: string, blocked?: readonly string[]): ToolSpec[];
140
+ /**
141
+ * Wrap [inner] in the mode's model-visible face. The returned registry shares
142
+ * the inner registry's tools and guard chain (nothing is copied), so a sub-call
143
+ * that reaches the inner pipeline runs exactly as a direct call always did.
144
+ */
145
+ export declare function exposedRegistry(inner: ToolRegistry, options: ExposureOptions): ToolRegistry;
@@ -0,0 +1,244 @@
1
+ /**
2
+ * `exposedRegistry` — the MODEL-VISIBLE face of one session's tool registry.
3
+ *
4
+ * `docs/modes-standard-vs-execution.md` §5.2 #1/#2 (P1): in `execution` mode the
5
+ * four SDK-covered tools (`read_file` / `write_file` / `list_dir` / `run_shell`)
6
+ * are no longer offered for a DIRECT call — they are reached from inside a
7
+ * `run_code` program. The engine's registry keeps every tool registered; what
8
+ * changes is the face the model sees and the door a model-initiated call knocks
9
+ * on.
10
+ *
11
+ * Two invariants make this a decorator and not a second registry:
12
+ *
13
+ * 1. **The verdict never lies** (`registry.ts`, W738 P1): a folded call is
14
+ * REFUSED before anything runs, so it is reported as a `deny` whose reason is
15
+ * the very text the caller sees — never an `allow` for a call the seam
16
+ * declined to execute.
17
+ * 2. **`run_code` sub-calls are NOT folded** (§5.2 #2, M8): the `run_code` tool
18
+ * holds a `RegistryHandle` bound to the INNER registry (`plugin.ts`), and this
19
+ * decorator is only ever the Context-provided face. A program's
20
+ * `tools.read_file(...)` therefore rides the inner pipeline unchanged —
21
+ * nothing here inspects the `:c<n>` id shape of a sub-call.
22
+ *
23
+ * `register` / `addGuard` / `get` pass straight through: the decorator never
24
+ * owns tools or guards, it only filters what is LISTED and gates what is
25
+ * DISPATCHED BY NAME from the model side.
26
+ *
27
+ * W806 (P0) adds the second, dynamic layer without adding a second decorator:
28
+ * `hidden` may be a LIVE PROVIDER, `order` fixes the wire order to a stable
29
+ * disclosure order, and `onHidden` lets a policy remember a refused direct call.
30
+ * See `disclosure.ts` for the policy itself. Nothing about the mode fold or the
31
+ * `run_code` escape hatch changes.
32
+ */
33
+ /**
34
+ * Stable marker of a folded call (S3/M8). It is part of the observable contract:
35
+ * a caller branches on this token, never on the prose around it.
36
+ */
37
+ export const TOOL_UNAVAILABLE_CODE = "tool_unavailable_in_mode";
38
+ /**
39
+ * The tool face of `execution` mode — module-level data, never a literal list at
40
+ * a call site (K3, §5.2 #3). `standard` mode exposes the whole registry.
41
+ *
42
+ * The list is a KEEP list on purpose: a tool registered later (W783's
43
+ * `ask_user_question`, a future orchestration tool) must be *decided* about
44
+ * rather than silently inherited by both modes. Anything outside it is folded —
45
+ * which is why the execution face is exactly these eight names (M7).
46
+ *
47
+ * W884 adds `load_skill` to the keep list. It is a PURE READ of the session's
48
+ * own skill layers (no writes, no process, no network), and — unlike the four
49
+ * SDK-covered tools — it is NOT reachable from a `run_code` program
50
+ * (`SDK_TOOLS` exposes only read_file / write_file / list_dir / run_shell).
51
+ * Folding it would therefore make skills UNREACHABLE in execution mode while the
52
+ * turn-start catalog still advertises them: a prompt that lies about what the
53
+ * model can do, which §6.5 of the disclosure design forbids. Keeping it costs
54
+ * one schema and preserves progressive disclosure in both modes.
55
+ */
56
+ export const EXECUTION_TOOL_NAMES = [
57
+ "run_code",
58
+ "http_request",
59
+ "process_control",
60
+ "spawn_worker",
61
+ "send_message",
62
+ "stop_worker",
63
+ "worker_status",
64
+ "load_skill",
65
+ // F4 step 2b: the browser tools are NOT SDK-covered (SDK_TOOLS stays the
66
+ // four file tools), so folding them would make them unreachable in execution
67
+ // mode while the turn-start catalog still advertises them -- the same
68
+ // "prompt must not lie" rule that kept load_skill (W884).
69
+ "browser_open",
70
+ "browser_act",
71
+ // B2 (F3 P1): remember/forget WRITE the workspace's own memory layer. Folding
72
+ // them would make persistent memory unreachable in execution mode while the
73
+ // turn-start memory block still advertises the feature -- the same "prompt
74
+ // must not lie" rule that kept load_skill (W884) and the browser tools (F4).
75
+ "remember",
76
+ "forget",
77
+ ];
78
+ /**
79
+ * The frozen guidance text of one folded call (S3). It names the `{tool}` slot
80
+ * and gives the model the TWO documented ways out (R1/R2): write a program, or
81
+ * switch the session back to standard mode.
82
+ */
83
+ export const EXECUTION_GUIDANCE = "'{tool}' is not directly callable in execution mode: write ONE `run_code` program that calls tools.{tool}(...) — a program's sub-calls always run — or switch the session back to standard mode";
84
+ /**
85
+ * W806: the refusal text of a tool that is merely NOT YET disclosed. It is
86
+ * deliberately distinct from [EXECUTION_GUIDANCE]: the name was withheld by the
87
+ * dynamic layer, not folded by the mode, so "write a program" would be the wrong
88
+ * advice. The refused call IS the request — the name joins the face at the next
89
+ * turn boundary (design §7.1, Q1/Q4 pending).
90
+ */
91
+ export const DISCLOSURE_GUIDANCE = "'{tool}' is not offered yet: it is disclosed at the NEXT turn boundary — this refused call is the request";
92
+ /** `tool_unavailable_in_mode: '<name>' …` — the refusal text of one folded call. */
93
+ export function unavailableError(name, guidance = EXECUTION_GUIDANCE) {
94
+ return `${TOOL_UNAVAILABLE_CODE}: ${guidance.split("{tool}").join(name)}`;
95
+ }
96
+ /**
97
+ * The exposure of `execution` mode over a registry holding [names]: every name
98
+ * outside [EXECUTION_TOOL_NAMES] is folded (§5.2 #3).
99
+ */
100
+ export function executionExposure(names) {
101
+ const kept = new Set(EXECUTION_TOOL_NAMES);
102
+ return { hidden: names.filter((name) => !kept.has(name)), guidance: EXECUTION_GUIDANCE };
103
+ }
104
+ /** Read a fixed list or a live provider EXACTLY once per reader call. */
105
+ function readNames(value) {
106
+ return typeof value === "function" ? value() : value;
107
+ }
108
+ /**
109
+ * Filter specs by an exposure — the rule `ExposedRegistry.schemas()` starts from.
110
+ * Stable ORDER is the decorator's own stateful projection (see `stableProjection`),
111
+ * not a pure function of this call: a name must keep the wire position it FIRST
112
+ * had, even when a tool registers after the policy was built.
113
+ */
114
+ export function exposedSpecs(specs, options) {
115
+ const hidden = new Set(readNames(options.hidden));
116
+ return specs.filter((spec) => !hidden.has(spec.name));
117
+ }
118
+ /**
119
+ * The model-visible face of a spec list under a mode literal: `execution` folds
120
+ * everything outside [EXECUTION_TOOL_NAMES], `standard` (and any unknown value)
121
+ * keeps the list as it is.
122
+ *
123
+ * This is the COMPOSE-TIME reading of the same rule, and it exists because the
124
+ * face has to be knowable BEFORE the instance that will expose it exists: the
125
+ * system prompt of a session is assembled while that very session is being
126
+ * composed (`sessionSystemPrompt`), so asking the registry for "the live
127
+ * instance" would answer with the PREVIOUS generation — or, on the first
128
+ * compose, with the detached default's 11-tool face (design §10.5 #2).
129
+ *
130
+ * W806 keeps this STATIC on purpose: the rendered `{{tools}}` list is the
131
+ * mode's disclosable universe, never the per-turn disclosed subset. System text
132
+ * is serialized BEFORE tools, so making it follow disclosure would invalidate
133
+ * the whole request prefix from token 0 (design §3.4/P4).
134
+ *
135
+ * W857 adds the optional [blocked] list: the permission baseline's `toolDeny`
136
+ * (W9). It is an INTERSECTION applied AFTER the fold — never a union — so a name
137
+ * the mode already folded cannot be restored and a name outside the folded face
138
+ * simply has no effect. Callers that read the same face as a composed instance
139
+ * (`RealRuntimeAdapter.sessionTools` vs `engineTools`' `DisclosurePolicy`)
140
+ * pass the same list through here so the two paths cannot drift (W791 §10.5 #2).
141
+ */
142
+ export function faceForMode(specs, mode, blocked = []) {
143
+ const face = mode !== "execution" ? [...specs] : exposedSpecs(specs, executionExposure(specs.map((spec) => spec.name)));
144
+ if (blocked.length === 0)
145
+ return face;
146
+ const denied = new Set(blocked);
147
+ return face.filter((spec) => !denied.has(spec.name));
148
+ }
149
+ /** A call the decorator REFUSED: a `deny`, with the refusal text as the error. */
150
+ function folded(callId, error) {
151
+ return { call_id: callId, value: null, render: null, error, decision: { kind: "deny", reason: error } };
152
+ }
153
+ class ExposedRegistry {
154
+ inner;
155
+ options;
156
+ guidance;
157
+ /** Names already WIRE-ORDERED, in first-seen order (append-only). */
158
+ emitted = [];
159
+ constructor(inner, options) {
160
+ this.inner = inner;
161
+ this.options = options;
162
+ this.guidance = options.guidance ?? EXECUTION_GUIDANCE;
163
+ }
164
+ /** The inner registry (never a copy) — the handle `run_code` is bound to. */
165
+ get innerRegistry() {
166
+ return this.inner;
167
+ }
168
+ /** The names this face hides (diagnostics / compose assertions). */
169
+ hiddenNames() {
170
+ return [...readNames(this.options.hidden)];
171
+ }
172
+ register(tool) {
173
+ this.inner.register(tool);
174
+ }
175
+ addGuard(guard) {
176
+ this.inner.addGuard(guard);
177
+ }
178
+ get(name) {
179
+ return this.inner.get(name);
180
+ }
181
+ schemas() {
182
+ return this.stableProjection(this.inner.schemas());
183
+ }
184
+ /**
185
+ * The stable disclosure order (W806/S2): a visible name keeps the position it
186
+ * FIRST had on this face and a name seen for the first time is appended at the
187
+ * TAIL — never inserted, never reordered. The policy order is only the seed,
188
+ * so a tool registered after the policy was built (the worker tools do) lands
189
+ * at the end instead of jumping ahead of a later dynamic disclosure.
190
+ */
191
+ stableProjection(specs) {
192
+ const hidden = new Set(readNames(this.options.hidden));
193
+ const visible = specs.filter((spec) => !hidden.has(spec.name));
194
+ const visibleNames = visible.map((spec) => spec.name);
195
+ const visibleSet = new Set(visibleNames);
196
+ const preferred = this.options.order === undefined ? visibleNames : readNames(this.options.order);
197
+ const known = new Set(this.emitted);
198
+ for (const name of preferred) {
199
+ if (!visibleSet.has(name) || known.has(name))
200
+ continue;
201
+ known.add(name);
202
+ this.emitted.push(name);
203
+ }
204
+ for (const name of visibleNames) {
205
+ if (known.has(name))
206
+ continue;
207
+ known.add(name);
208
+ this.emitted.push(name);
209
+ }
210
+ // A name no longer visible leaves the memory; if it ever comes back it is
211
+ // appended at the tail, never re-inserted.
212
+ this.emitted = this.emitted.filter((name) => visibleSet.has(name));
213
+ const byName = new Map(visible.map((spec) => [spec.name, spec]));
214
+ return this.emitted.map((name) => byName.get(name));
215
+ }
216
+ async dispatch(input) {
217
+ const hidden = new Set(readNames(this.options.hidden));
218
+ if (hidden.has(input.name)) {
219
+ this.noteHidden(input.name);
220
+ return folded(input.call_id, unavailableError(input.name, this.options.guidanceFor?.(input.name) ?? this.guidance));
221
+ }
222
+ return this.inner.dispatch(input);
223
+ }
224
+ /**
225
+ * Tell the policy a direct call was refused. A broken observer must never turn
226
+ * a refuse-before-execution into a throw (the deny is already decided).
227
+ */
228
+ noteHidden(name) {
229
+ try {
230
+ this.options.onHidden?.(name);
231
+ }
232
+ catch {
233
+ /* the refusal stands; a policy bug is not a dispatch failure */
234
+ }
235
+ }
236
+ }
237
+ /**
238
+ * Wrap [inner] in the mode's model-visible face. The returned registry shares
239
+ * the inner registry's tools and guard chain (nothing is copied), so a sub-call
240
+ * that reaches the inner pipeline runs exactly as a direct call always did.
241
+ */
242
+ export function exposedRegistry(inner, options) {
243
+ return new ExposedRegistry(inner, options);
244
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `fnTool` — a `Tool` whose behaviour is a plain async closure.
3
+ *
4
+ * Keeps each builtin definition terse while satisfying the `Tool` seam exactly
5
+ * (legacy `fn_tool` in `crates/tools/src/builtin.rs`). Tools that need the full
6
+ * `ToolInput` (call id) or author their own `render` build the object directly.
7
+ */
8
+ import type { Tool, ToolSpec } from "@celestea/core";
9
+ export declare function fnTool(spec: ToolSpec, execute: (args: unknown) => Promise<unknown>): Tool;
10
+ /** The authored-render variant of [fnTool] (canonical value + human view). */
11
+ export declare function renderTool(spec: ToolSpec, executeWith: Tool["executeWith"] & ((input: never) => Promise<{
12
+ value: unknown;
13
+ render: string | null;
14
+ }>), execute: (args: unknown) => Promise<unknown>): Tool;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `fnTool` — a `Tool` whose behaviour is a plain async closure.
3
+ *
4
+ * Keeps each builtin definition terse while satisfying the `Tool` seam exactly
5
+ * (legacy `fn_tool` in `crates/tools/src/builtin.rs`). Tools that need the full
6
+ * `ToolInput` (call id) or author their own `render` build the object directly.
7
+ */
8
+ export function fnTool(spec, execute) {
9
+ return { spec: () => spec, execute };
10
+ }
11
+ /** The authored-render variant of [fnTool] (canonical value + human view). */
12
+ export function renderTool(spec, executeWith, execute) {
13
+ return { spec: () => spec, execute, executeWith };
14
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * File IO for `read_file` / `write_file` / `list_dir`, with the two protections
3
+ * the tool contract promises: **truncation** (a bounded read keeps a huge file
4
+ * from flooding the context) and **binary protection** (a NUL-bearing file is
5
+ * rejected instead of being decoded into mojibake).
6
+ *
7
+ * The guard already arbitrated *where* the path may point; this module only
8
+ * performs the IO and reports structured failures (`<tool>: code=… msg="…"`).
9
+ */
10
+ /** Bytes returned by one `read_file` call (beyond this: `truncated`). */
11
+ export declare const MAX_READ_BYTES: number;
12
+ /** Entry names returned by one `list_dir` call. */
13
+ export declare const MAX_DIR_ENTRIES = 1000;
14
+ /** Window inspected for the binary heuristic. */
15
+ export declare const BINARY_SNIFF_BYTES = 8192;
16
+ /** Default `limit` (lines) for a paged `read_file` when only `offset` is given. */
17
+ export declare const DEFAULT_READ_LIMIT = 2000;
18
+ export interface ReadTextResult {
19
+ text: string;
20
+ truncated: boolean;
21
+ totalBytes: number;
22
+ }
23
+ /** One line-window read (`read_file` pagination, W846). */
24
+ export interface ReadTextLinesResult {
25
+ /** Byte-exact window: start of `offset` through the end of the last captured line. */
26
+ text: string;
27
+ /** Effective 0-based first line (clamped to the file). */
28
+ offset: number;
29
+ /** Effective line budget. */
30
+ limit: number;
31
+ /** Lines in `text`. */
32
+ lineCount: number;
33
+ /** Total lines in the file (requires a scan to EOF). */
34
+ totalLines: number;
35
+ /** `offset + lineCount < totalLines`. */
36
+ hasMore: boolean;
37
+ /** First line after the window when `hasMore`, else null. */
38
+ nextOffset: number | null;
39
+ /** The 256 KiB byte budget clipped the window before `limit` lines. */
40
+ truncated: boolean;
41
+ /** File size in bytes. */
42
+ totalBytes: number;
43
+ }
44
+ export interface ListDirResult {
45
+ names: string[];
46
+ truncated: boolean;
47
+ total: number;
48
+ }
49
+ /** NUL byte inside the sniff window ⇒ binary (the classic, cheap heuristic). */
50
+ export declare function isProbablyBinary(bytes: Buffer): boolean;
51
+ export declare function readTextFile(path: string): Promise<ReadTextResult>;
52
+ /**
53
+ * Line-window read for `read_file` pagination (W846).
54
+ *
55
+ * A SINGLE streaming pass from byte 0: `readTextFile` only ever returns the
56
+ * first MAX_READ_BYTES from offset 0, so an `offset` past that window needs its
57
+ * own reader. The window itself is bounded by MAX_READ_BYTES; `totalLines`
58
+ * costs a scan to EOF (O(file size)) and is the price of exact
59
+ * `hasMore`/`nextOffset`. `truncated` means the byte budget clipped the window
60
+ * before `limit` lines; a line larger than the budget is returned as a
61
+ * UTF-8-safe prefix and `nextOffset` skips past it (use run_shell for such a
62
+ * pathological line).
63
+ */
64
+ export declare function readTextLines(path: string, offset: number, limit: number): Promise<ReadTextLinesResult>;
65
+ export declare function writeTextFile(path: string, content: string): Promise<void>;
66
+ export declare function listDirNames(path: string): Promise<ListDirResult>;
67
+ /**
68
+ * The authored omission note (W855).
69
+ *
70
+ * Discipline: this note — and every `truncated` flag it accompanies — means THE
71
+ * BUDGET kept obtainable content out. An upstream that returned an incomplete
72
+ * body is a different fact and keeps its own domain field; it must never be
73
+ * described by this note.
74
+ *
75
+ * The omission is ALWAYS paired with a retrieval instruction (`retrieve`), so
76
+ * the model is never told "there was more" without being told how to get it.
77
+ */
78
+ export declare function truncationNote(what: string, shown: number, total: number, unit: string, retrieve: string): string;