@guuey/host 0.7.0 → 0.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/dist/frameworks/claude-options.d.ts +74 -12
- package/dist/frameworks/claude-options.d.ts.map +1 -1
- package/dist/frameworks/claude-options.js +134 -53
- package/dist/frameworks/claude.d.ts +6 -0
- package/dist/frameworks/claude.d.ts.map +1 -1
- package/dist/frameworks/claude.js +1 -0
- package/package.json +3 -3
|
@@ -96,11 +96,27 @@ export interface BuildOptionsContext {
|
|
|
96
96
|
*/
|
|
97
97
|
authToken?: string;
|
|
98
98
|
/**
|
|
99
|
-
* Per-session
|
|
100
|
-
*
|
|
101
|
-
*
|
|
99
|
+
* Per-session layer mounts (the invoke's `fs`). When present, the invoke
|
|
100
|
+
* binds `cwd`=session, exposes home+app as `additionalDirectories`, and
|
|
101
|
+
* injects the `HOME`/`GUUEY_*`/`CLAUDE_CONFIG_DIR` env. NOTE: the wire
|
|
102
|
+
* carries a NON-NULL `fs` on every turn (spec §1.4 — the spec-default
|
|
103
|
+
* `/app`/`/home`/`/session` mounts when GuueyFS is off), so presence here
|
|
104
|
+
* says nothing about whether durable storage is bound. It is therefore NOT
|
|
105
|
+
* the tool-catalog gate — {@link fsBound} is (guuey#234).
|
|
102
106
|
*/
|
|
103
107
|
fs?: Fs;
|
|
108
|
+
/**
|
|
109
|
+
* Whether {@link fs} names REAL GuueyFS layers this turn — the pod has
|
|
110
|
+
* `GUUEY_FS_BASE` armed and the Router resolved per-session layer dirs under
|
|
111
|
+
* it — as opposed to the spec-default / federation-ephemeral mounts. THE gate
|
|
112
|
+
* for the built-in file tools + `Bash` (`tools`, and their `allowedTools`
|
|
113
|
+
* entries): absent/false → `tools: []`, purely MCP-driven. Threaded from the
|
|
114
|
+
* runtime on the invoke (`Invoke.fsBound`); mirrors {@link memoryAttached}
|
|
115
|
+
* (the memory-mcp T5 lesson: gate on the real signal, not on `fs` presence —
|
|
116
|
+
* which is why the pre-#234 `Boolean(ctx.fs)` gate put Bash in EVERY no-code
|
|
117
|
+
* agent's catalog on EVERY env).
|
|
118
|
+
*/
|
|
119
|
+
fsBound?: boolean;
|
|
104
120
|
/** Recent conversation window for the `<conversation_history>` preamble. */
|
|
105
121
|
history?: HistoryMessage[];
|
|
106
122
|
/** Thread-scoped memory for the `<thread_memory>` preamble (the §1.4 push). */
|
|
@@ -164,20 +180,34 @@ export interface BuildOptionsContext {
|
|
|
164
180
|
*/
|
|
165
181
|
export declare function buildOptions(snapshot: GuueyAgent, ctx: BuildOptionsContext): Options;
|
|
166
182
|
/**
|
|
167
|
-
* Auto-allow permission callback. Installed when
|
|
168
|
-
*
|
|
169
|
-
*
|
|
183
|
+
* Auto-allow permission callback. Installed when the operator did NOT pin
|
|
184
|
+
* `claude.permissions.mode` and the snapshot has NO explicit `tools.allowlist`,
|
|
185
|
+
* so the default no-code agent's MCP tools (and, on a GuueyFS-armed pod, its
|
|
186
|
+
* `Bash` + file tools) run prompt-free. Returns `{ behavior: 'allow' }` for
|
|
170
187
|
* every request, passing the input through unchanged.
|
|
171
188
|
*
|
|
172
189
|
* Safe because the model's tool surface is already locked down BEFORE the
|
|
173
|
-
* callback ever fires — `tools`/`allowedTools` cap which
|
|
174
|
-
* `settingSources:[]` blocks filesystem-loaded settings,
|
|
175
|
-
* pins the MCP catalog — and the real OS isolation is the
|
|
176
|
-
* jail this whole process runs inside. The callback only
|
|
177
|
-
* final interactive "ask" stage (which would otherwise
|
|
178
|
-
* the earlier hook/deny-rule stages
|
|
190
|
+
* callback ever fires — `tools`/`allowedTools`/`disallowedTools` cap which
|
|
191
|
+
* tools exist, `settingSources:[]` blocks filesystem-loaded settings,
|
|
192
|
+
* `strictMcpConfig` pins the MCP catalog — and the real OS isolation is the
|
|
193
|
+
* Router's bubblewrap jail this whole process runs inside. The callback only
|
|
194
|
+
* collapses the SDK's final interactive "ask" stage (which would otherwise
|
|
195
|
+
* hang a headless pod); the earlier hook/deny-rule stages still run.
|
|
179
196
|
*/
|
|
180
197
|
export declare const autoAllowTool: CanUseTool;
|
|
198
|
+
/**
|
|
199
|
+
* Deny-unlisted permission callback (guuey#234). Installed instead of
|
|
200
|
+
* {@link autoAllowTool} when the snapshot carries an explicit `tools.allowlist`
|
|
201
|
+
* (and no pinned mode). Tools that ARE listed never reach any callback — they
|
|
202
|
+
* are SDK allow rules (`allowedTools`) and are auto-allowed upstream. So every
|
|
203
|
+
* tool that DOES arrive here is, by construction, one the builder did not
|
|
204
|
+
* list: it is denied with a message the model can act on. Never `null`, never
|
|
205
|
+
* an ask — the invariant "an explicit allowlist can never hang the pod".
|
|
206
|
+
*
|
|
207
|
+
* `allowedSet` is consulted anyway (belt-and-braces against an SDK that
|
|
208
|
+
* routes an allow-listed name here after all): a listed name is allowed.
|
|
209
|
+
*/
|
|
210
|
+
export declare function denyUnlistedTool(allowedSet: ReadonlySet<string>): CanUseTool;
|
|
181
211
|
/**
|
|
182
212
|
* Map the Router-resolved cred files to the framework-neutral SdkMcpServer map.
|
|
183
213
|
* The Router (credential-broker) owns ALL resolution — default, federation, mint,
|
|
@@ -185,5 +215,37 @@ export declare const autoAllowTool: CanUseTool;
|
|
|
185
215
|
* (via ctx.listCredentials) and shapes each entry. Keyed by the server name.
|
|
186
216
|
*/
|
|
187
217
|
export declare function resolveMcpServers(ctx: BuildOptionsContext): Record<string, SdkMcpServer>;
|
|
218
|
+
/**
|
|
219
|
+
* The resolved tool surface for one invoke — what `buildOptions` hands the SDK.
|
|
220
|
+
* Exported for the invariant tests ("an explicit allowlist can never hang").
|
|
221
|
+
*/
|
|
222
|
+
export interface ResolvedToolGates {
|
|
223
|
+
/** The built-in catalog: `[...FS_TOOLS, Bash]` when fs is bound, else `[]`. */
|
|
224
|
+
tools: string[];
|
|
225
|
+
/** SDK allow rules — MCP names in the SDK's `mcp__<server>[__<tool>]` spelling plus the built-ins in `tools`. */
|
|
226
|
+
allowedTools: string[];
|
|
227
|
+
/** SDK `disallowedTools` — the translated `tools.denylist` (removed from the catalog). */
|
|
228
|
+
disallowedTools: string[];
|
|
229
|
+
/** Whether the snapshot carried a non-empty `tools.allowlist` (→ deny-unlisted posture). */
|
|
230
|
+
explicitAllowlist: boolean;
|
|
231
|
+
/** `allowedTools` as a set, for the deny-unlisted callback. */
|
|
232
|
+
allowedSet: ReadonlySet<string>;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Resolve the invoke's tool surface (guuey#234). Pure.
|
|
236
|
+
*
|
|
237
|
+
* - `tools` (the built-in catalog) is keyed ONLY on `fsBound` — the real
|
|
238
|
+
* "GuueyFS layers are bound this turn" signal from the runtime.
|
|
239
|
+
* - `allowedTools`: an explicit `tools.allowlist` is TRANSLATED from the config
|
|
240
|
+
* grammar (`<server>.<tool>` / `<server>.*` / bare) into SDK names — never
|
|
241
|
+
* passed verbatim; absent → every tool of every declared server
|
|
242
|
+
* (`mcp__<server>`). When fs is bound the built-ins join as allow rules so
|
|
243
|
+
* the platform's memory feature (file tools + `Bash` in the jail) is never
|
|
244
|
+
* silently switched off by a builder's MCP-only allowlist; a builder who
|
|
245
|
+
* wants them gone deny-lists them (`"Bash"`).
|
|
246
|
+
* - `disallowedTools`: the translated `tools.denylist` — the SDK removes those
|
|
247
|
+
* from the model's catalog outright.
|
|
248
|
+
*/
|
|
249
|
+
export declare function resolveToolGates(snapshot: GuueyAgent, declaredServerNames: readonly string[], fsBound: boolean): ResolvedToolGates;
|
|
188
250
|
export { withContextPreamble } from "../preamble.js";
|
|
189
251
|
//# sourceMappingURL=claude-options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-options.d.ts","sourceRoot":"","sources":["../../src/frameworks/claude-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAY,MAAM,gCAAgC,CAAC;AAChG,OAAO,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,
|
|
1
|
+
{"version":3,"file":"claude-options.d.ts","sourceRoot":"","sources":["../../src/frameworks/claude-options.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GUUEY_DEFAULT_SYSTEM_PROMPT, defaultModelFor, } from "@guuey/config";
|
|
1
|
+
import { GUUEY_DEFAULT_SYSTEM_PROMPT, defaultModelFor, parseToolGateEntry, } from "@guuey/config";
|
|
2
2
|
/**
|
|
3
3
|
* Default Claude model — only used when the snapshot omits `model`. Derived
|
|
4
4
|
* from the `@guuey/config` registry (single source of truth per the
|
|
@@ -20,8 +20,9 @@ const DEFAULT_MAX_TURNS = 25;
|
|
|
20
20
|
export const ENV_HOME_DIR = "GUUEY_HOME_DIR";
|
|
21
21
|
export const ENV_APP_DIR = "GUUEY_APP_DIR";
|
|
22
22
|
/**
|
|
23
|
-
* File tools enabled when GuueyFS layers are bound
|
|
24
|
-
* (
|
|
23
|
+
* File tools enabled when GuueyFS layers are bound
|
|
24
|
+
* ({@link BuildOptionsContext.fsBound}). `Bash` is added separately (see
|
|
25
|
+
* {@link BASH_TOOL}) so the two are independently testable.
|
|
25
26
|
*/
|
|
26
27
|
const FS_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep"];
|
|
27
28
|
/**
|
|
@@ -33,6 +34,12 @@ const FS_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep"];
|
|
|
33
34
|
* bubblewrap inside the Router's bwrap); the Router's bwrap IS the isolation.
|
|
34
35
|
*/
|
|
35
36
|
const BASH_TOOL = "Bash";
|
|
37
|
+
/**
|
|
38
|
+
* The built-in tool catalog this host can expose — exactly the file tools plus
|
|
39
|
+
* `Bash`, all keyed on {@link BuildOptionsContext.fsBound}. A bare tool-gate
|
|
40
|
+
* entry (`"Bash"`, `"Write"`) matches one of these by name.
|
|
41
|
+
*/
|
|
42
|
+
const BUILTIN_TOOLS = [...FS_TOOLS, BASH_TOOL];
|
|
36
43
|
/**
|
|
37
44
|
* Belt-and-braces (spec §4): guuey's memory mechanism is prompted file memory
|
|
38
45
|
* (a platform-owned system-prompt section, below), never the SDK's own
|
|
@@ -105,7 +112,8 @@ export function buildOptions(snapshot, ctx) {
|
|
|
105
112
|
throw new Error(`@guuey/host: snapshot.systemPrompt must be a resolved string (got ${JSON.stringify(snapshot.systemPrompt)}). The CLI inlines {file} references before upload; workers never read the filesystem.`);
|
|
106
113
|
}
|
|
107
114
|
const mcpServers = resolveMcpServers(ctx);
|
|
108
|
-
const
|
|
115
|
+
const fsBound = ctx.fsBound === true;
|
|
116
|
+
const gates = resolveToolGates(snapshot, Object.keys(mcpServers), fsBound);
|
|
109
117
|
const systemPrompt = withContextPreamble(snapshot.systemPrompt ?? GUUEY_DEFAULT_SYSTEM_PROMPT, ctx.history, ctx.priorMemory, ctx.priorState) +
|
|
110
118
|
buildMemorySection(ctx) +
|
|
111
119
|
// cross-app-profile T7: the profile section is a SIBLING of the memory
|
|
@@ -175,13 +183,17 @@ export function buildOptions(snapshot, ctx) {
|
|
|
175
183
|
}
|
|
176
184
|
// Whether the operator pinned a Claude permission mode in agent.json. When
|
|
177
185
|
// set we forward it verbatim and let the SDK's mode govern the posture; when
|
|
178
|
-
// unset we install
|
|
179
|
-
//
|
|
186
|
+
// unset we ALWAYS install a `canUseTool` below (guuey#234) so no tool pick can
|
|
187
|
+
// ever reach the SDK's interactive ask stage — a never-answered prompt would
|
|
188
|
+
// hang this headless pod.
|
|
180
189
|
const explicitMode = snapshot.claude?.permissions?.mode;
|
|
181
190
|
const options = {
|
|
182
191
|
model,
|
|
183
192
|
mcpServers,
|
|
184
|
-
allowedTools,
|
|
193
|
+
allowedTools: gates.allowedTools,
|
|
194
|
+
// Deny-listed tools (`tools.denylist`, translated) are removed from the
|
|
195
|
+
// model's catalog outright — the SDK's own mechanism, not a callback.
|
|
196
|
+
...(gates.disallowedTools.length > 0 ? { disallowedTools: gates.disallowedTools } : {}),
|
|
185
197
|
// Token streaming (guuey#91 consumer half): the SDK interleaves
|
|
186
198
|
// `stream_event` partials, which `@silverprotocol/claude-agent-sdk` ≥0.4
|
|
187
199
|
// maps to token-granular text/reasoning/tool.args deltas and dedupes
|
|
@@ -191,11 +203,12 @@ export function buildOptions(snapshot, ctx) {
|
|
|
191
203
|
// idempotent by tool_use id), so this flag's only observable effect is
|
|
192
204
|
// incremental frames on the SSE wire.
|
|
193
205
|
includePartialMessages: true,
|
|
194
|
-
// With GuueyFS layers bound
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
|
|
206
|
+
// With GuueyFS layers REALLY bound (`fsBound`, guuey#234 — never `fs`
|
|
207
|
+
// presence, which is unconditional on the wire), expose the file tools PLUS
|
|
208
|
+
// real `Bash`; otherwise purely MCP-driven. `Bash` is safe here because the
|
|
209
|
+
// host already runs inside the Router's bubblewrap jail — that bwrap, NOT
|
|
210
|
+
// the SDK's own `sandbox:{}` block, is the isolation.
|
|
211
|
+
tools: gates.tools,
|
|
199
212
|
// Settings isolation. Empty array = "no filesystem settings loaded" — guards
|
|
200
213
|
// against a future SDK change auto-pulling `~/.claude/settings.json` and
|
|
201
214
|
// leaking the operator's logged-in Claude Code MCPs into the tool catalog.
|
|
@@ -217,44 +230,68 @@ export function buildOptions(snapshot, ctx) {
|
|
|
217
230
|
//
|
|
218
231
|
// - Operator pinned `claude.permissions.mode` → forward it verbatim; the
|
|
219
232
|
// operator owns the posture (e.g. `acceptEdits`).
|
|
220
|
-
// - No explicit mode
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
// `
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
233
|
+
// - No explicit mode → ALWAYS install a `canUseTool` (guuey#234: it used
|
|
234
|
+
// to be installed only when fs was "bound", which was every turn — once
|
|
235
|
+
// the gate is real, an fs-off pod with an explicit allowlist would sit
|
|
236
|
+
// in `default` mode). In the SDK permission flow (hooks → deny → allow →
|
|
237
|
+
// ask → mode/canUseTool), `default` mode with no callback routes any
|
|
238
|
+
// tool not covered by an allow rule through an interactive permission
|
|
239
|
+
// prompt — which, in this headless ephemeral pod, no one answers, so
|
|
240
|
+
// the agent would HANG. The callback short-circuits that stage: with no
|
|
241
|
+
// explicit allowlist every pick is allowed (the tool surface is already
|
|
242
|
+
// capped by `tools`/`allowedTools` + `settingSources:[]` +
|
|
243
|
+
// `strictMcpConfig`, and the Router's bubblewrap jail is the real
|
|
244
|
+
// isolation boundary — NOT the SDK's own `sandbox:{}` block, which is
|
|
245
|
+
// intentionally absent to avoid a nested bwrap); with an explicit
|
|
246
|
+
// allowlist an UNLISTED pick is DENIED with a message the model can read
|
|
247
|
+
// — that is what makes the allowlist a real narrowing, and it can never
|
|
248
|
+
// hang. We do NOT use `bypassPermissions`: it requires
|
|
249
|
+
// `allowDangerouslySkipPermissions` and globally disables hooks/deny-rule
|
|
250
|
+
// evaluation, whereas the callback keeps those stages intact while only
|
|
251
|
+
// collapsing the final ask stage.
|
|
234
252
|
...(explicitMode
|
|
235
253
|
? { permissionMode: explicitMode }
|
|
236
|
-
:
|
|
237
|
-
? { canUseTool: autoAllowTool }
|
|
238
|
-
: {}),
|
|
254
|
+
: { canUseTool: gates.explicitAllowlist ? denyUnlistedTool(gates.allowedSet) : autoAllowTool }),
|
|
239
255
|
...(ctx.abortController ? { abortController: ctx.abortController } : {}),
|
|
240
256
|
};
|
|
241
257
|
return options;
|
|
242
258
|
}
|
|
243
259
|
/**
|
|
244
|
-
* Auto-allow permission callback. Installed when
|
|
245
|
-
*
|
|
246
|
-
*
|
|
260
|
+
* Auto-allow permission callback. Installed when the operator did NOT pin
|
|
261
|
+
* `claude.permissions.mode` and the snapshot has NO explicit `tools.allowlist`,
|
|
262
|
+
* so the default no-code agent's MCP tools (and, on a GuueyFS-armed pod, its
|
|
263
|
+
* `Bash` + file tools) run prompt-free. Returns `{ behavior: 'allow' }` for
|
|
247
264
|
* every request, passing the input through unchanged.
|
|
248
265
|
*
|
|
249
266
|
* Safe because the model's tool surface is already locked down BEFORE the
|
|
250
|
-
* callback ever fires — `tools`/`allowedTools` cap which
|
|
251
|
-
* `settingSources:[]` blocks filesystem-loaded settings,
|
|
252
|
-
* pins the MCP catalog — and the real OS isolation is the
|
|
253
|
-
* jail this whole process runs inside. The callback only
|
|
254
|
-
* final interactive "ask" stage (which would otherwise
|
|
255
|
-
* the earlier hook/deny-rule stages
|
|
267
|
+
* callback ever fires — `tools`/`allowedTools`/`disallowedTools` cap which
|
|
268
|
+
* tools exist, `settingSources:[]` blocks filesystem-loaded settings,
|
|
269
|
+
* `strictMcpConfig` pins the MCP catalog — and the real OS isolation is the
|
|
270
|
+
* Router's bubblewrap jail this whole process runs inside. The callback only
|
|
271
|
+
* collapses the SDK's final interactive "ask" stage (which would otherwise
|
|
272
|
+
* hang a headless pod); the earlier hook/deny-rule stages still run.
|
|
256
273
|
*/
|
|
257
274
|
export const autoAllowTool = (_toolName, input) => Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
275
|
+
/**
|
|
276
|
+
* Deny-unlisted permission callback (guuey#234). Installed instead of
|
|
277
|
+
* {@link autoAllowTool} when the snapshot carries an explicit `tools.allowlist`
|
|
278
|
+
* (and no pinned mode). Tools that ARE listed never reach any callback — they
|
|
279
|
+
* are SDK allow rules (`allowedTools`) and are auto-allowed upstream. So every
|
|
280
|
+
* tool that DOES arrive here is, by construction, one the builder did not
|
|
281
|
+
* list: it is denied with a message the model can act on. Never `null`, never
|
|
282
|
+
* an ask — the invariant "an explicit allowlist can never hang the pod".
|
|
283
|
+
*
|
|
284
|
+
* `allowedSet` is consulted anyway (belt-and-braces against an SDK that
|
|
285
|
+
* routes an allow-listed name here after all): a listed name is allowed.
|
|
286
|
+
*/
|
|
287
|
+
export function denyUnlistedTool(allowedSet) {
|
|
288
|
+
return (toolName, input) => Promise.resolve(allowedSet.has(toolName)
|
|
289
|
+
? { behavior: "allow", updatedInput: input }
|
|
290
|
+
: {
|
|
291
|
+
behavior: "deny",
|
|
292
|
+
message: `Tool "${toolName}" is not in this agent's tools.allowlist. Allowed: ${[...allowedSet].join(", ") || "(none)"}.`,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
258
295
|
/**
|
|
259
296
|
* Map the Router-resolved cred files to the framework-neutral SdkMcpServer map.
|
|
260
297
|
* The Router (credential-broker) owns ALL resolution — default, federation, mint,
|
|
@@ -279,24 +316,68 @@ export function resolveMcpServers(ctx) {
|
|
|
279
316
|
return out;
|
|
280
317
|
}
|
|
281
318
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* -
|
|
319
|
+
* Translate one parsed tool-gate entry (the `@guuey/config` grammar) into the
|
|
320
|
+
* SDK's tool-name spelling(s). MCP tools are auto-namespaced by the SDK as
|
|
321
|
+
* `mcp__<server>__<tool>`, and `mcp__<server>` is the SDK's own "every tool of
|
|
322
|
+
* that server" rule. A bare name fans out to EVERY declared server (we cannot
|
|
323
|
+
* enumerate a server's tools ahead of connecting) and, when it names one of the
|
|
324
|
+
* host's built-ins, to that built-in — but only while fs is bound, since an
|
|
325
|
+
* unbound turn has no built-in catalog for it to match.
|
|
326
|
+
*/
|
|
327
|
+
function toSdkToolNames(raw, declaredServerNames, fsBound) {
|
|
328
|
+
const parsed = parseToolGateEntry(raw);
|
|
329
|
+
// Deploy-time validation (`validateToolGates`) rejects malformed entries
|
|
330
|
+
// before a snapshot ever ships; a stale snapshot that slips one through is
|
|
331
|
+
// dropped here (NOT passed verbatim — an unknown allow rule is inert, and an
|
|
332
|
+
// unknown name reaching the ask stage is the hang this fix exists to close).
|
|
333
|
+
if ("error" in parsed)
|
|
334
|
+
return [];
|
|
335
|
+
switch (parsed.kind) {
|
|
336
|
+
case "server-tool":
|
|
337
|
+
return [`mcp__${parsed.server}__${parsed.tool}`];
|
|
338
|
+
case "server-all":
|
|
339
|
+
return [`mcp__${parsed.server}`];
|
|
340
|
+
case "bare":
|
|
341
|
+
return [
|
|
342
|
+
...declaredServerNames.map((s) => `mcp__${s}__${parsed.tool}`),
|
|
343
|
+
...(fsBound && BUILTIN_TOOLS.includes(parsed.tool) ? [parsed.tool] : []),
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Resolve the invoke's tool surface (guuey#234). Pure.
|
|
288
349
|
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
350
|
+
* - `tools` (the built-in catalog) is keyed ONLY on `fsBound` — the real
|
|
351
|
+
* "GuueyFS layers are bound this turn" signal from the runtime.
|
|
352
|
+
* - `allowedTools`: an explicit `tools.allowlist` is TRANSLATED from the config
|
|
353
|
+
* grammar (`<server>.<tool>` / `<server>.*` / bare) into SDK names — never
|
|
354
|
+
* passed verbatim; absent → every tool of every declared server
|
|
355
|
+
* (`mcp__<server>`). When fs is bound the built-ins join as allow rules so
|
|
356
|
+
* the platform's memory feature (file tools + `Bash` in the jail) is never
|
|
357
|
+
* silently switched off by a builder's MCP-only allowlist; a builder who
|
|
358
|
+
* wants them gone deny-lists them (`"Bash"`).
|
|
359
|
+
* - `disallowedTools`: the translated `tools.denylist` — the SDK removes those
|
|
360
|
+
* from the model's catalog outright.
|
|
293
361
|
*/
|
|
294
|
-
function
|
|
295
|
-
const explicit = snapshot.tools?.allowlist;
|
|
296
|
-
const
|
|
297
|
-
|
|
362
|
+
export function resolveToolGates(snapshot, declaredServerNames, fsBound) {
|
|
363
|
+
const explicit = snapshot.tools?.allowlist ?? [];
|
|
364
|
+
const explicitAllowlist = explicit.length > 0;
|
|
365
|
+
const builtins = fsBound ? [...BUILTIN_TOOLS] : [];
|
|
366
|
+
const mcpAllowed = explicitAllowlist
|
|
367
|
+
? explicit.flatMap((e) => toSdkToolNames(e, declaredServerNames, fsBound))
|
|
298
368
|
: declaredServerNames.map((s) => `mcp__${s}`);
|
|
299
|
-
|
|
369
|
+
const allowedTools = dedupe([...mcpAllowed, ...builtins]);
|
|
370
|
+
const disallowedTools = dedupe((snapshot.tools?.denylist ?? []).flatMap((e) => toSdkToolNames(e, declaredServerNames, fsBound)));
|
|
371
|
+
return {
|
|
372
|
+
tools: builtins,
|
|
373
|
+
allowedTools,
|
|
374
|
+
disallowedTools,
|
|
375
|
+
explicitAllowlist,
|
|
376
|
+
allowedSet: new Set(allowedTools),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function dedupe(names) {
|
|
380
|
+
return [...new Set(names)];
|
|
300
381
|
}
|
|
301
382
|
// withContextPreamble now lives in ../preamble.js (framework-neutral — the
|
|
302
383
|
// ADK runner renders the same preamble); re-exported for existing importers.
|
|
@@ -23,6 +23,12 @@ export interface HostInvoke {
|
|
|
23
23
|
input: string;
|
|
24
24
|
identity: Identity;
|
|
25
25
|
fs: Fs;
|
|
26
|
+
/**
|
|
27
|
+
* Whether `fs` names REAL GuueyFS layers this turn (guuey#234) — the gate
|
|
28
|
+
* for the built-in file tools + `Bash`. See `Invoke.fsBound` in
|
|
29
|
+
* `@guuey/worker`. Absent/false → purely MCP-driven.
|
|
30
|
+
*/
|
|
31
|
+
fsBound?: boolean;
|
|
26
32
|
history: HistoryMessage[];
|
|
27
33
|
priorMemory?: PriorMemoryRecord[];
|
|
28
34
|
priorState?: JsonValue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/frameworks/claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,KAAK,EACV,cAAc,EACd,OAAO,EACP,EAAE,EACF,cAAc,EACd,QAAQ,EACR,SAAS,EACT,cAAc,EAEf,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EAEZ,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAQ7B;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IACtC;gEAC4D;IAC5D,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;CACpC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,EAAE,MAAM,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,8EAA8E;AAC9E,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,KAAK,aAAa,CAAC,UAAU,CAAC,CAAC;AA0BhC;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACrE,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/frameworks/claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,KAAK,EACV,cAAc,EACd,OAAO,EACP,EAAE,EACF,cAAc,EACd,QAAQ,EACR,SAAS,EACT,cAAc,EAEf,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EAEZ,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAQ7B;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,EAAE,EAAE,EAAE,CAAC;IACP;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IACtC;gEAC4D;IAC5D,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;CACpC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,EAAE,MAAM,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,8EAA8E;AAC9E,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,KAAK,aAAa,CAAC,UAAU,CAAC,CAAC;AA0BhC;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACrE,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAqDf"}
|
|
@@ -42,6 +42,7 @@ export async function runInvoke(snapshot, invoke, runtime, emit, query) {
|
|
|
42
42
|
input: invoke.input,
|
|
43
43
|
identity: invoke.identity,
|
|
44
44
|
fs: invoke.fs,
|
|
45
|
+
...(invoke.fsBound !== undefined ? { fsBound: invoke.fsBound } : {}),
|
|
45
46
|
history: invoke.history,
|
|
46
47
|
listCredentials: runtime.listCredentials,
|
|
47
48
|
...(runtime.apiKey !== undefined ? { apiKey: runtime.apiKey } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/host",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "The universal config-driven Guuey worker. Reads the resolved agent.json snapshot, runs the Claude Agent SDK, and emits each native SDKMessage to fd-3 as a `native` WorkerEvent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@guuey/worker": "0.7.
|
|
26
|
-
"@guuey/config": "0.7.
|
|
25
|
+
"@guuey/worker": "0.7.1",
|
|
26
|
+
"@guuey/config": "0.7.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^24.0.0",
|