pi-roundtable-sandbox 0.7.12 → 0.7.13

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.13
4
+
5
+ - `precheckScriptRunner` forwards only the tools approved with the script (pi-roundtable 0.7.13's `PrecheckScriptContext.tools`) among those `grant` allows, and takes `toolName(server, tool)`, the name the host's hold rules know a tool by (default the tool's own name).
6
+
3
7
  ## 0.7.12
4
8
 
5
9
  - A host compactor gets at most half the time the turn has left (the runtime passes the broker its `deadline`, `PiHostContext.deadline`), so Pi's own summary still fits after it runs out; with under two seconds left the broker falls back to Pi's summary at once.
package/README.md CHANGED
@@ -391,7 +391,8 @@ export const precheckScripts = definePlugin({
391
391
  });
392
392
  ```
393
393
 
394
- `grant({ channel, target })` returns the `PrecheckMcpServer`s a script for that schedule may call: `{ name, url, tools, token? }`.
394
+ `grant({ channel, target, tier })` returns the `PrecheckMcpServer`s a script for that schedule may call: `{ name, url, tools, token? }`.
395
+ Each run reaches only the granted tools that were approved with the script: the core reads a script's calls when it is saved, and saving one that calls a tool the host's hold rules hold waits for the owner's approval. `toolName(server, tool)` gives the name those rules know a tool by, the name the host's own agent calls it by; the default is the tool's own name, so pass it when your agent sees MCP tools under another name, such as `${server}_${tool}`.
395
396
  The script calls a server by `name`, only the listed `tools`, and only with single `tools/call` requests; every other server, tool, method, query, and extra credential is refused, and refused calls count against the run's budget (`maxCalls`, default 16).
396
397
  `url` is a fixed HTTPS Streamable HTTP endpoint answering with JSON or one SSE event; `token` is read on the host for each call and inserted by the broker. An answer that carries it, plainly, inside JSON text, or base64-encoded, is refused; this catches an echo, not an upstream set on leaking it, so grant only upstreams you trust.
397
398
  Calls run one at a time; a second call while one is pending is refused.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-roundtable-sandbox",
3
- "version": "0.7.12",
3
+ "version": "0.7.13",
4
4
  "description": "Sealed guest channels and an allow-listed credential broker for pi-roundtable",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -52,7 +52,7 @@
52
52
  "@biomejs/biome": "2.5.15",
53
53
  "@types/bun": "1.4.2",
54
54
  "discord.js": "14.27.0",
55
- "pi-roundtable": "0.7.12",
55
+ "pi-roundtable": "0.7.13",
56
56
  "typebox": "1.3.34",
57
57
  "typescript": "7.0.2"
58
58
  }
@@ -51,6 +51,12 @@ export interface PrecheckScriptRunnerOptions {
51
51
  grant(
52
52
  scope: PrecheckScope,
53
53
  ): readonly PrecheckMcpServer[] | Promise<readonly PrecheckMcpServer[]>;
54
+ /**
55
+ * The name the host's hold rules know `mcp.call(server, tool)` by, which is the name its own
56
+ * agent calls that tool by; default `tool` itself. A script calling a tool the rules hold needs
57
+ * the owner's approval to be saved.
58
+ */
59
+ toolName?: (server: string, tool: string) => string;
54
60
  /** The container's command; default the package's worker in the Pi image's layout. */
55
61
  entrypoint?: readonly string[];
56
62
  /** How long a script may run; default 60 seconds. */
@@ -286,6 +292,7 @@ function guide(servers: readonly PrecheckMcpServer[]): string {
286
292
  "`result` is `{ wake: false, note? }` to skip the turn (the note, if any, is posted in small text) or `{ wake: true, context }` to wake you with `context`. Anything else, a throw, or running too long wakes you with the error.",
287
293
  "`today` is the date in the host's time zone (YYYY-MM-DD); pass it to tools instead of computing dates in UTC. `firedAt` is a Date and `timeZone` an IANA zone.",
288
294
  "Await each call before making the next; calls do not run in parallel. console output is discarded; only the returned result counts.",
295
+ 'Write each call as mcp.call("server", "tool", args) or mcp.json(...), with server and tool as strings: the host reads them when the script is saved, and each run may call only those. A script that calls a tool needing the owner\'s approval waits for it when saved, once.',
289
296
  "`await mcp.call(server, tool, args)` returns the MCP tool result; `await mcp.json(server, tool, args)` returns its structured content, or its first text content parsed as JSON. A tool error throws.",
290
297
  "The script runs in a container with no network, no files of the host, and no credentials; it reaches only the tools below.",
291
298
  reach,
@@ -315,6 +322,7 @@ export function precheckScriptRunner(
315
322
  ? {}
316
323
  : { timeoutMs: options.timeoutMs }),
317
324
  describe: async (scope) => guide(await grant(scope)),
325
+ toolName: options.toolName ?? ((_server, tool) => tool),
318
326
  run: async (script, context) => runOne(script, context),
319
327
  };
320
328
 
@@ -323,10 +331,21 @@ export function precheckScriptRunner(
323
331
  context: PrecheckScriptContext,
324
332
  ): Promise<PrecheckResult> {
325
333
  const { schedule, signal } = context;
326
- const servers = await grant({
327
- channel: schedule.channel,
328
- target: schedule.target,
329
- tier: schedule.createdTier,
334
+ // Only what the grant allows and the owner approved with the script: the broker refuses the rest.
335
+ const approved = new Set(
336
+ context.tools.map(({ server, tool }) => `${server}\u0000${tool}`),
337
+ );
338
+ const servers = (
339
+ await grant({
340
+ channel: schedule.channel,
341
+ target: schedule.target,
342
+ tier: schedule.createdTier,
343
+ })
344
+ ).flatMap((server) => {
345
+ const tools = server.tools.filter((tool) =>
346
+ approved.has(`${server.name}\u0000${tool}`),
347
+ );
348
+ return tools.length > 0 ? [{ ...server, tools }] : [];
330
349
  });
331
350
  const dirs: string[] = [];
332
351
  let listener: Awaited<ReturnType<typeof listenBroker>> | undefined;