@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.
- package/LICENSE +21 -0
- package/dist/args.d.ts +15 -0
- package/dist/args.js +55 -0
- package/dist/attachments/image-header.d.ts +28 -0
- package/dist/attachments/image-header.js +148 -0
- package/dist/attachments/store.d.ts +56 -0
- package/dist/attachments/store.js +148 -0
- package/dist/browser/cdp.d.ts +162 -0
- package/dist/browser/cdp.js +269 -0
- package/dist/browser/launch.d.ts +92 -0
- package/dist/browser/launch.js +232 -0
- package/dist/browser/memory-guard.d.ts +55 -0
- package/dist/browser/memory-guard.js +210 -0
- package/dist/browser/session.d.ts +146 -0
- package/dist/browser/session.js +371 -0
- package/dist/browser/snapshot.d.ts +69 -0
- package/dist/browser/snapshot.js +163 -0
- package/dist/browser/types.d.ts +51 -0
- package/dist/browser/types.js +8 -0
- package/dist/builtin.d.ts +54 -0
- package/dist/builtin.js +77 -0
- package/dist/desc.d.ts +20 -0
- package/dist/desc.js +22 -0
- package/dist/disclosure.d.ts +113 -0
- package/dist/disclosure.js +141 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +33 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +60 -0
- package/dist/exposure.d.ts +145 -0
- package/dist/exposure.js +244 -0
- package/dist/fn-tool.d.ts +14 -0
- package/dist/fn-tool.js +14 -0
- package/dist/fs/file-io.d.ts +78 -0
- package/dist/fs/file-io.js +239 -0
- package/dist/guard/path-guard.d.ts +144 -0
- package/dist/guard/path-guard.js +289 -0
- package/dist/guard/paths.d.ts +35 -0
- package/dist/guard/paths.js +100 -0
- package/dist/http/errors.d.ts +24 -0
- package/dist/http/errors.js +64 -0
- package/dist/http/headers.d.ts +19 -0
- package/dist/http/headers.js +62 -0
- package/dist/http/redirects.d.ts +31 -0
- package/dist/http/redirects.js +76 -0
- package/dist/http/ssrf.d.ts +105 -0
- package/dist/http/ssrf.js +272 -0
- package/dist/http/transport.d.ts +50 -0
- package/dist/http/transport.js +130 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +129 -0
- package/dist/memory/log.d.ts +75 -0
- package/dist/memory/log.js +157 -0
- package/dist/memory/store.d.ts +47 -0
- package/dist/memory/store.js +61 -0
- package/dist/platform/exec.d.ts +79 -0
- package/dist/platform/exec.js +218 -0
- package/dist/platform/index.d.ts +12 -0
- package/dist/platform/index.js +12 -0
- package/dist/platform/paths.d.ts +51 -0
- package/dist/platform/paths.js +60 -0
- package/dist/platform/quote.d.ts +72 -0
- package/dist/platform/quote.js +102 -0
- package/dist/plugin.d.ts +96 -0
- package/dist/plugin.js +101 -0
- package/dist/process/buffers.d.ts +33 -0
- package/dist/process/buffers.js +86 -0
- package/dist/process/registry.d.ts +98 -0
- package/dist/process/registry.js +282 -0
- package/dist/registry.d.ts +52 -0
- package/dist/registry.js +161 -0
- package/dist/run-code/broker.d.ts +68 -0
- package/dist/run-code/broker.js +465 -0
- package/dist/run-code/limits.d.ts +69 -0
- package/dist/run-code/limits.js +88 -0
- package/dist/run-code/lines.d.ts +69 -0
- package/dist/run-code/lines.js +199 -0
- package/dist/run-code/sdk-ts.d.ts +34 -0
- package/dist/run-code/sdk-ts.js +276 -0
- package/dist/run-code/sdk.d.ts +39 -0
- package/dist/run-code/sdk.js +294 -0
- package/dist/sandbox/async.d.ts +10 -0
- package/dist/sandbox/async.js +26 -0
- package/dist/sandbox/bwrap-argv.d.ts +62 -0
- package/dist/sandbox/bwrap-argv.js +113 -0
- package/dist/sandbox/bwrap.d.ts +94 -0
- package/dist/sandbox/bwrap.js +159 -0
- package/dist/sandbox/child.d.ts +38 -0
- package/dist/sandbox/child.js +98 -0
- package/dist/sandbox/config.d.ts +89 -0
- package/dist/sandbox/config.js +149 -0
- package/dist/sandbox/fake-sandbox.d.ts +57 -0
- package/dist/sandbox/fake-sandbox.js +110 -0
- package/dist/sandbox/launch.d.ts +51 -0
- package/dist/sandbox/launch.js +134 -0
- package/dist/sandbox/limits.d.ts +63 -0
- package/dist/sandbox/limits.js +113 -0
- package/dist/sandbox/probe.d.ts +46 -0
- package/dist/sandbox/probe.js +102 -0
- package/dist/sandbox/provider.d.ts +83 -0
- package/dist/sandbox/provider.js +126 -0
- package/dist/sandbox/rlimit.d.ts +60 -0
- package/dist/sandbox/rlimit.js +76 -0
- package/dist/sandbox/seccomp.d.ts +48 -0
- package/dist/sandbox/seccomp.js +115 -0
- package/dist/sandbox/userspace.d.ts +65 -0
- package/dist/sandbox/userspace.js +107 -0
- package/dist/sandbox/workdir.d.ts +13 -0
- package/dist/sandbox/workdir.js +44 -0
- package/dist/schema.d.ts +20 -0
- package/dist/schema.js +135 -0
- package/dist/testing/platform-gates.d.ts +54 -0
- package/dist/testing/platform-gates.js +62 -0
- package/dist/tool-failure.d.ts +13 -0
- package/dist/tool-failure.js +19 -0
- package/dist/tools/ask-user.d.ts +32 -0
- package/dist/tools/ask-user.js +145 -0
- package/dist/tools/browser.d.ts +24 -0
- package/dist/tools/browser.js +132 -0
- package/dist/tools/http-request.d.ts +31 -0
- package/dist/tools/http-request.js +117 -0
- package/dist/tools/list-dir.d.ts +9 -0
- package/dist/tools/list-dir.js +45 -0
- package/dist/tools/load-skill.d.ts +37 -0
- package/dist/tools/load-skill.js +76 -0
- package/dist/tools/memory.d.ts +46 -0
- package/dist/tools/memory.js +131 -0
- package/dist/tools/process-control.d.ts +14 -0
- package/dist/tools/process-control.js +49 -0
- package/dist/tools/read-file.d.ts +11 -0
- package/dist/tools/read-file.js +81 -0
- package/dist/tools/read-image.d.ts +33 -0
- package/dist/tools/read-image.js +144 -0
- package/dist/tools/run-code.d.ts +48 -0
- package/dist/tools/run-code.js +115 -0
- package/dist/tools/run-shell.d.ts +22 -0
- package/dist/tools/run-shell.js +81 -0
- package/dist/tools/write-file.d.ts +8 -0
- package/dist/tools/write-file.js +31 -0
- 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;
|
package/dist/exposure.js
ADDED
|
@@ -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;
|
package/dist/fn-tool.js
ADDED
|
@@ -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;
|