@guuey/config 0.7.0 → 0.7.2

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/agent.d.ts CHANGED
@@ -134,9 +134,33 @@ export declare const McpServerEntrySchema: z.ZodUnion<readonly [z.ZodDiscriminat
134
134
  }, z.core.$strict>], "kind">, z.ZodLiteral<false>]>;
135
135
  export type DeclaredMcpServers = Record<string, GuueyAgentMcpServer | false>;
136
136
  /**
137
- * Tool-gate block — allowlist applied first (intersect with what the MCP
138
- * server advertises), then denylist subtracts. Tool names are MCP-namespaced
139
- * (`"<server>.<tool>"`). Bare names match across all connected servers.
137
+ * Tool-gate block — `allowlist` first (the model may call ONLY these), then
138
+ * `denylist` subtracts (removed from the model's catalog outright). Both
139
+ * optional; both empty/absent every tool of every connected server.
140
+ *
141
+ * Entry shapes (guuey#234 — the ONE grammar, parsed by
142
+ * {@link parseToolGateEntry}, validated at deploy time by
143
+ * {@link validateToolGates}, and translated to the framework's own tool
144
+ * names by the host at turn time):
145
+ *
146
+ * - `"<server>.<tool>"` — one tool of one declared MCP server
147
+ * (`"todoist.create_task"`).
148
+ * - `"<server>.*"` — every tool of one declared server (`"ggui.*"`).
149
+ * - `"<tool>"` — a bare name: that tool on EVERY connected server
150
+ * (`"search"`), and — for a Claude agent on a GuueyFS-armed pod — the
151
+ * built-in file tool of that name (`"Bash"`, `"Write"`, …).
152
+ *
153
+ * `<server>` must name a server this agent connects: a declared
154
+ * `mcpServers` key, the platform default `ggui` (unless opted out with
155
+ * `ggui: false`), or a platform-injected reserved server
156
+ * ({@link RESERVED_MCP_SERVER_NAMES}). The framework-internal spellings
157
+ * (`mcp__server__tool`) are NOT accepted — write the config grammar and
158
+ * let the host translate.
159
+ *
160
+ * Runtime posture (Claude): a tool the model picks that is NOT in an explicit
161
+ * allowlist is DENIED with a message the model can read — never routed to an
162
+ * interactive prompt (a headless pod has no one to answer one). Deny-listed
163
+ * tools never reach the model's catalog at all.
140
164
  */
141
165
  declare const ToolGatesSchema: z.ZodObject<{
142
166
  allowlist: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -195,6 +219,17 @@ declare const DeploySchema: z.ZodObject<{
195
219
  }>>;
196
220
  region: z.ZodOptional<z.ZodString>;
197
221
  }, z.core.$strict>;
222
+ /**
223
+ * Whether the agent wants VFS layers at all (guuey#234). `storage` absent →
224
+ * the platform default (`['user','app']`) → true; `storage: []` → false; any
225
+ * non-empty list → true. The runtime ANDs this with the pod's own GuueyFS
226
+ * arming to decide `Invoke.fsBound` (→ built-in file tools + `Bash`), so an
227
+ * agent whose definition says "no VFS" never carries a catalog wider than its
228
+ * definition, whatever the pod is capable of.
229
+ */
230
+ export declare function agentDeclaresVfs(agent: {
231
+ storage?: ReadonlyArray<'user' | 'app'>;
232
+ } | undefined): boolean;
198
233
  /**
199
234
  * The agent section — composes runtime + platform features + deploy.
200
235
  *
@@ -409,6 +444,51 @@ export declare const RESERVED_MCP_SERVER_NAMES: readonly string[];
409
444
  * {@link validateNoProxiedServers}'s shape.
410
445
  */
411
446
  export declare function validateReservedServerNames(agent: GuueyAgent | undefined): string[];
447
+ /** One parsed `tools.allowlist` / `tools.denylist` entry. */
448
+ export type ToolGateEntry =
449
+ /** `"<server>.<tool>"` — one tool of one server. */
450
+ {
451
+ kind: 'server-tool';
452
+ server: string;
453
+ tool: string;
454
+ }
455
+ /** `"<server>.*"` — every tool of one server. */
456
+ | {
457
+ kind: 'server-all';
458
+ server: string;
459
+ }
460
+ /** `"<tool>"` — that tool on every connected server (and the built-in of that name). */
461
+ | {
462
+ kind: 'bare';
463
+ tool: string;
464
+ };
465
+ /**
466
+ * Parse one tool-gate entry per the {@link ToolGatesSchema} grammar. Returns
467
+ * the parsed shape, or a human-readable reason string when the entry is
468
+ * malformed (never throws — callers decide whether to reject or report).
469
+ */
470
+ export declare function parseToolGateEntry(raw: string): ToolGateEntry | {
471
+ error: string;
472
+ };
473
+ /**
474
+ * The server names a tool-gate entry may qualify with: every EFFECTIVE server
475
+ * (declared map with the platform default seeded and `ggui: false` honoured —
476
+ * {@link effectiveMcpServers}) plus the platform-injected reserved servers
477
+ * ({@link RESERVED_MCP_SERVER_NAMES}: memory / profile, spliced at invoke time
478
+ * for authenticated callers).
479
+ */
480
+ export declare function toolGateServerNames(agent: GuueyAgent | undefined): string[];
481
+ /**
482
+ * Validate `agent.tools.allowlist` / `agent.tools.denylist` at deploy time
483
+ * (guuey#234). Every entry must parse per {@link parseToolGateEntry}, and a
484
+ * server-qualified entry must name a server this agent connects
485
+ * ({@link toolGateServerNames}). Returns a list of human-readable violation
486
+ * messages (empty = clean). Tool-level existence against a server's live
487
+ * manifest is NOT checked here (a hosted server's tool set is only known once
488
+ * it is connected) — that is why the host DENIES an unlisted pick at turn time
489
+ * instead of prompting.
490
+ */
491
+ export declare function validateToolGates(agent: GuueyAgent | undefined): string[];
412
492
  /**
413
493
  * Platform default MCP server map. Seeded under every declared map by
414
494
  * {@link effectiveMcpServers} (opt out with `ggui: false`). Exposed here so
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
package/dist/agent.js CHANGED
@@ -193,9 +193,33 @@ const McpServerSchema = z.discriminatedUnion('kind', [
193
193
  */
194
194
  export const McpServerEntrySchema = z.union([McpServerSchema, z.literal(false)]);
195
195
  /**
196
- * Tool-gate block — allowlist applied first (intersect with what the MCP
197
- * server advertises), then denylist subtracts. Tool names are MCP-namespaced
198
- * (`"<server>.<tool>"`). Bare names match across all connected servers.
196
+ * Tool-gate block — `allowlist` first (the model may call ONLY these), then
197
+ * `denylist` subtracts (removed from the model's catalog outright). Both
198
+ * optional; both empty/absent every tool of every connected server.
199
+ *
200
+ * Entry shapes (guuey#234 — the ONE grammar, parsed by
201
+ * {@link parseToolGateEntry}, validated at deploy time by
202
+ * {@link validateToolGates}, and translated to the framework's own tool
203
+ * names by the host at turn time):
204
+ *
205
+ * - `"<server>.<tool>"` — one tool of one declared MCP server
206
+ * (`"todoist.create_task"`).
207
+ * - `"<server>.*"` — every tool of one declared server (`"ggui.*"`).
208
+ * - `"<tool>"` — a bare name: that tool on EVERY connected server
209
+ * (`"search"`), and — for a Claude agent on a GuueyFS-armed pod — the
210
+ * built-in file tool of that name (`"Bash"`, `"Write"`, …).
211
+ *
212
+ * `<server>` must name a server this agent connects: a declared
213
+ * `mcpServers` key, the platform default `ggui` (unless opted out with
214
+ * `ggui: false`), or a platform-injected reserved server
215
+ * ({@link RESERVED_MCP_SERVER_NAMES}). The framework-internal spellings
216
+ * (`mcp__server__tool`) are NOT accepted — write the config grammar and
217
+ * let the host translate.
218
+ *
219
+ * Runtime posture (Claude): a tool the model picks that is NOT in an explicit
220
+ * allowlist is DENIED with a message the model can read — never routed to an
221
+ * interactive prompt (a headless pod has no one to answer one). Deny-listed
222
+ * tools never reach the model's catalog at all.
199
223
  */
200
224
  const ToolGatesSchema = z.strictObject({
201
225
  allowlist: z.array(z.string().min(1)).optional(),
@@ -280,8 +304,24 @@ const AuthSchema = z.enum(['anonymous', 'required', 'optional']);
280
304
  const MemorySchema = z.enum(['thread', 'none']);
281
305
  /**
282
306
  * VFS scopes to mount into the pod. Empty array = no VFS (still uses thread + state).
307
+ * Absent = the platform default `['user', 'app']`. Consumed by
308
+ * {@link agentDeclaresVfs} — the per-AGENT half of the runtime's `fsBound`
309
+ * signal (guuey#234): a `storage: []` agent gets NO built-in file tools even on
310
+ * a GuueyFS-armed pod.
283
311
  */
284
312
  const StorageScopeSchema = z.array(z.enum(['user', 'app']));
313
+ /**
314
+ * Whether the agent wants VFS layers at all (guuey#234). `storage` absent →
315
+ * the platform default (`['user','app']`) → true; `storage: []` → false; any
316
+ * non-empty list → true. The runtime ANDs this with the pod's own GuueyFS
317
+ * arming to decide `Invoke.fsBound` (→ built-in file tools + `Bash`), so an
318
+ * agent whose definition says "no VFS" never carries a catalog wider than its
319
+ * definition, whatever the pod is capable of.
320
+ */
321
+ export function agentDeclaresVfs(agent) {
322
+ const storage = agent?.storage;
323
+ return storage === undefined || storage.length > 0;
324
+ }
285
325
  /**
286
326
  * The agent section — composes runtime + platform features + deploy.
287
327
  *
@@ -607,6 +647,85 @@ export function validateReservedServerNames(agent) {
607
647
  }
608
648
  return violations;
609
649
  }
650
+ /**
651
+ * The framework-internal MCP tool-name prefix (Claude Agent SDK:
652
+ * `mcp__<server>__<tool>`). Rejected in config — builders write the
653
+ * `<server>.<tool>` grammar; the host translates.
654
+ */
655
+ const SDK_INTERNAL_TOOL_PREFIX = /^mcp__/;
656
+ /**
657
+ * Parse one tool-gate entry per the {@link ToolGatesSchema} grammar. Returns
658
+ * the parsed shape, or a human-readable reason string when the entry is
659
+ * malformed (never throws — callers decide whether to reject or report).
660
+ */
661
+ export function parseToolGateEntry(raw) {
662
+ const entry = raw.trim();
663
+ if (entry.length === 0)
664
+ return { error: 'empty entry' };
665
+ if (SDK_INTERNAL_TOOL_PREFIX.test(entry)) {
666
+ return {
667
+ error: `"${entry}" uses the framework-internal spelling — write "<server>.<tool>" (or "<server>.*", or a bare tool name) instead`,
668
+ };
669
+ }
670
+ const dot = entry.indexOf('.');
671
+ if (dot === -1)
672
+ return { kind: 'bare', tool: entry };
673
+ const server = entry.slice(0, dot);
674
+ const tool = entry.slice(dot + 1);
675
+ if (server.length === 0)
676
+ return { error: `"${entry}" has an empty server name before the dot` };
677
+ if (tool.length === 0)
678
+ return { error: `"${entry}" has an empty tool name after the dot` };
679
+ if (tool === '*')
680
+ return { kind: 'server-all', server };
681
+ if (tool.includes('*')) {
682
+ return { error: `"${entry}": wildcards are only valid as the whole tool part ("${server}.*")` };
683
+ }
684
+ return { kind: 'server-tool', server, tool };
685
+ }
686
+ /**
687
+ * The server names a tool-gate entry may qualify with: every EFFECTIVE server
688
+ * (declared map with the platform default seeded and `ggui: false` honoured —
689
+ * {@link effectiveMcpServers}) plus the platform-injected reserved servers
690
+ * ({@link RESERVED_MCP_SERVER_NAMES}: memory / profile, spliced at invoke time
691
+ * for authenticated callers).
692
+ */
693
+ export function toolGateServerNames(agent) {
694
+ return [
695
+ ...Object.keys(effectiveMcpServers(agent?.mcpServers)),
696
+ ...RESERVED_MCP_SERVER_NAMES,
697
+ ];
698
+ }
699
+ /**
700
+ * Validate `agent.tools.allowlist` / `agent.tools.denylist` at deploy time
701
+ * (guuey#234). Every entry must parse per {@link parseToolGateEntry}, and a
702
+ * server-qualified entry must name a server this agent connects
703
+ * ({@link toolGateServerNames}). Returns a list of human-readable violation
704
+ * messages (empty = clean). Tool-level existence against a server's live
705
+ * manifest is NOT checked here (a hosted server's tool set is only known once
706
+ * it is connected) — that is why the host DENIES an unlisted pick at turn time
707
+ * instead of prompting.
708
+ */
709
+ export function validateToolGates(agent) {
710
+ const violations = [];
711
+ const gates = agent?.tools;
712
+ if (!gates)
713
+ return violations;
714
+ const known = new Set(toolGateServerNames(agent));
715
+ for (const list of ['allowlist', 'denylist']) {
716
+ for (const raw of gates[list] ?? []) {
717
+ const parsed = parseToolGateEntry(raw);
718
+ if ('error' in parsed) {
719
+ violations.push(`tools.${list}: ${parsed.error}`);
720
+ continue;
721
+ }
722
+ if (parsed.kind !== 'bare' && !known.has(parsed.server)) {
723
+ violations.push(`tools.${list}: "${raw}" names MCP server "${parsed.server}", which this agent does not connect — declare it under mcpServers or use one of: ${[...known].join(', ')}`);
724
+ }
725
+ }
726
+ }
727
+ return violations;
728
+ }
610
729
  /**
611
730
  * Platform default MCP server map. Seeded under every declared map by
612
731
  * {@link effectiveMcpServers} (opt out with `ggui: false`). Exposed here so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/config",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Open-source schemas + loaders for the three guuey config files: agent.json (declarative agent definition), guuey.json (hosted-deploy overlay), and the helper types around them. Consumed by @guuey/cli, the guuey backend, and devs writing their own agent or MCP server.",
5
5
  "license": "MIT",
6
6
  "type": "module",