@mono-agent/agent-runtime 0.5.1 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mono-agent/agent-runtime",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "Agent runtime supporting Claude SDK, Claude CLI, Codex CLI, and PI SDK out of the box",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-only",
@@ -309,7 +309,7 @@ function createBuiltinTool(name, label, description, parameters, execute, { cwd,
309
309
  }
310
310
 
311
311
  /**
312
- * Progressive skill disclosure: exposes a `read_skill` tool so the agent can pull
312
+ * Progressive skill disclosure: exposes a `ReadSkill` tool so the agent can pull
313
313
  * a named skill's FULL body on demand (skills are otherwise injected index-only).
314
314
  *
315
315
  * Two input shapes are accepted, matching what hosts pass:
@@ -318,7 +318,7 @@ function createBuiltinTool(name, label, description, parameters, execute, { cwd,
318
318
  * back-compat `dataDir` (skills under `<dataDir>/skills`). This path is UNCHANGED.
319
319
  * - pi's neutral `Skill` shape (`{name, description, content, filePath, ...}`,
320
320
  * what worklab passes): each skill carries an absolute `filePath`. When NO shared
321
- * `skillsRoot`/`dataDir` is configured, read_skill derives each skill's root from
321
+ * `skillsRoot`/`dataDir` is configured, ReadSkill derives each skill's root from
322
322
  * its own `filePath` — pi has no lazy-body equivalent, so the body is still read
323
323
  * from disk on demand rather than injected up front.
324
324
  *
@@ -361,7 +361,7 @@ function readSkillTool(skillNames = [], { skillsRoot, dataDir, skills = [] } = {
361
361
  if (!enumNames.length) return null;
362
362
 
363
363
  return {
364
- name: "read_skill",
364
+ name: "ReadSkill",
365
365
  label: "Read Skill",
366
366
  description: "Load the full instructions for a named skill.",
367
367
  parameters: objectSchema({ name: { type: "string", enum: enumNames } }, ["name"]),
@@ -408,9 +408,10 @@ export function createStructuredOutputTool(outputSchema, onStructuredOutput) {
408
408
 
409
409
  /**
410
410
  * @param {any} allowedTools
411
- * @param {{skillNames?: any[], skills?: any[], skillsRoot?: any, dataDir?: any, cwd?: any, onEvent?: (event: any) => void, toolLimits?: any, persistArtifact?: any, onTruncate?: any, toolPayloadMaxBytes?: number, imageInlineMaxBytes?: any, toolPolicy?: any, sandboxPolicy?: any, sandboxEngine?: any, approvalManager?: any, approvalModel?: any, ctx?: any}} [options]
411
+ * @param {{disallowedTools?: any[], skillNames?: any[], skills?: any[], skillsRoot?: any, dataDir?: any, cwd?: any, onEvent?: (event: any) => void, toolLimits?: any, persistArtifact?: any, onTruncate?: any, toolPayloadMaxBytes?: number, imageInlineMaxBytes?: any, toolPolicy?: any, sandboxPolicy?: any, sandboxEngine?: any, approvalManager?: any, approvalModel?: any, ctx?: any}} [options]
412
412
  */
413
413
  export function getPiBuiltinTools(allowedTools, {
414
+ disallowedTools = [],
414
415
  skillNames = [],
415
416
  skills = [],
416
417
  skillsRoot,
@@ -495,10 +496,17 @@ export function getPiBuiltinTools(allowedTools, {
495
496
  limit: { type: "integer" },
496
497
  }, ["query"]), webSearchToolImpl, toolContext),
497
498
  };
498
- const names = Array.isArray(allowedTools) ? allowedTools : Object.keys(all);
499
+ // allowedTools honors the `"*"` allow-all sentinel (and undefined) as "every
500
+ // built-in"; disallowedTools is the deny-wins filter applied to the final set.
501
+ const allowAll = !Array.isArray(allowedTools) || allowedTools.includes("*");
502
+ const selected = allowAll ? Object.keys(all) : allowedTools;
503
+ const denied = new Set(Array.isArray(disallowedTools) ? disallowedTools : []);
504
+ const names = selected.filter((name) => !denied.has(name));
499
505
  const tools = names.map((name) => all[name]).filter(Boolean);
500
506
  const skillTool = readSkillTool(skillNames, { skillsRoot, dataDir, skills });
501
- if (skillTool) tools.push(skillTool);
507
+ // Deny-check the canonical PascalCase name AND the legacy snake_case alias so
508
+ // an old denylist keeps disabling the tool after the rename.
509
+ if (skillTool && !denied.has("ReadSkill") && !denied.has("read_skill" /* legacy alias */)) tools.push(skillTool);
502
510
  const gated = approvalManager
503
511
  ? wrapToolsWithApprovalGate(tools, approvalManager, { model: approvalModel })
504
512
  : tools;
@@ -13,7 +13,7 @@ import { readRuntimeBrand } from "../../agent/tools/shared/runtime-context.js";
13
13
  import { buildCapabilitiesUsed } from "../runtime/capabilities-used.js";
14
14
  import {
15
15
  claudeNativeAgentDefinitions,
16
- claudeToolsWithNativeSubagents,
16
+ resolveClaudeAllowedTools,
17
17
  } from "./claude-subagents.js";
18
18
 
19
19
  const DORMANT_CLI_CAPABILITIES = {
@@ -377,7 +377,10 @@ export function buildCliCommand({
377
377
  const normalizedEffort = typeof effort === "string" && effort.trim() ? effort : null;
378
378
  if (sdk === "claude-code") {
379
379
  const nativeAgents = claudeNativeAgentDefinitions(nativeSubagents);
380
- const cliAllowedTools = claudeToolsWithNativeSubagents(allowedTools, nativeSubagents);
380
+ // `"*"` allow-all → defer to Claude Code's full default toolset (omit
381
+ // --tools); `cliAllowedTools` is the "*"-stripped explicit list so a bare
382
+ // "*" never reaches a flag value.
383
+ const { allowAll: cliAllowAll, tools: cliAllowedTools } = resolveClaudeAllowedTools(allowedTools, nativeSubagents);
381
384
  // intelligence-ramp Phase 5.1: when the coordinator hands us a parent
382
385
  // session id (recovery continuation, R12), pass --resume so the host
383
386
  // CLI can keep its own conversation cache warm. Otherwise stay
@@ -402,7 +405,7 @@ export function buildCliCommand({
402
405
  args.push("--add-dir", ...skillDirs);
403
406
  }
404
407
  if (nativeAgents) args.push("--agents", JSON.stringify(nativeAgents));
405
- if (Array.isArray(cliAllowedTools) && cliAllowedTools.length) {
408
+ if (!cliAllowAll && Array.isArray(cliAllowedTools) && cliAllowedTools.length) {
406
409
  args.push("--tools", cliAllowedTools.join(","));
407
410
  }
408
411
  const autoAllowed = [
@@ -20,7 +20,7 @@ import { readRuntimeBrand } from "../../agent/tools/shared/runtime-context.js";
20
20
  import { createApprovalManager } from "../../agent/approval.js";
21
21
  import {
22
22
  claudeNativeAgentDefinitions,
23
- claudeToolsWithNativeSubagents,
23
+ resolveClaudeAllowedTools,
24
24
  } from "./claude-subagents.js";
25
25
 
26
26
  function thinkingForEffort(effort) {
@@ -588,6 +588,10 @@ export async function generateClaudeResponse(systemPrompt, options) {
588
588
  ? "default"
589
589
  : permissionMode;
590
590
  const nativeAgents = claudeNativeAgentDefinitions(options.nativeSubagents);
591
+ // `"*"` allow-all → pass `allowedTools: undefined` so the SDK uses its default
592
+ // toolset (every tool, incl. Task — not double-added). disallowedTools still
593
+ // flows through, so deny-wins holds under allow-all.
594
+ const { allowAll: allowAllTools, tools: resolvedAllowedTools } = resolveClaudeAllowedTools(allowedTools, options.nativeSubagents);
591
595
  // Assembled incrementally, then handed across the SDK `query` boundary
592
596
  // (outputFormat/resume/maxTurns are attached conditionally below).
593
597
  /** @type {any} */
@@ -597,7 +601,7 @@ export async function generateClaudeResponse(systemPrompt, options) {
597
601
  cwd,
598
602
  permissionMode: effectivePermissionMode,
599
603
  ...(effectivePermissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
600
- allowedTools: claudeToolsWithNativeSubagents(allowedTools, options.nativeSubagents),
604
+ allowedTools: allowAllTools ? undefined : resolvedAllowedTools,
601
605
  disallowedTools,
602
606
  mcpServers,
603
607
  ...(approvalManager ? { canUseTool: createClaudeCanUseTool(approvalManager, model.model) } : {}),
@@ -65,3 +65,24 @@ export function claudeToolsWithNativeSubagents(allowedTools, nativeSubagents) {
65
65
  ? withTaskTool(allowedTools)
66
66
  : allowedTools;
67
67
  }
68
+
69
+ /**
70
+ * Resolve the effective allowed-tools list for the Claude bridges, honoring the
71
+ * `"*"` allow-all sentinel. Returns `{ allowAll, tools }` where `tools` is the
72
+ * native-subagent-composed list with any bare `"*"` stripped, so a literal `"*"`
73
+ * never reaches a CLI flag value or the SDK `query()`. When `allowAll` is true
74
+ * the caller MUST defer to Claude's default toolset (omit `--tools` / pass
75
+ * `allowedTools: undefined`) rather than pass `tools` as the tool set — the
76
+ * default already carries every tool, incl. `Task`, so it is not double-added.
77
+ * @param {any} allowedTools
78
+ * @param {any} nativeSubagents
79
+ * @returns {{allowAll: boolean, tools: any}}
80
+ */
81
+ export function resolveClaudeAllowedTools(allowedTools, nativeSubagents) {
82
+ const composed = claudeToolsWithNativeSubagents(allowedTools, nativeSubagents);
83
+ const allowAll = Array.isArray(composed) && composed.includes("*");
84
+ const tools = Array.isArray(composed)
85
+ ? composed.filter((tool) => tool !== "*")
86
+ : composed;
87
+ return { allowAll, tools };
88
+ }
@@ -116,8 +116,7 @@ function sandboxForPermissionMode(permissionMode) {
116
116
 
117
117
  function approvalPolicyForPermissionMode(permissionMode) {
118
118
  if (permissionMode === "bypassPermissions") return "never";
119
- if (permissionMode === "plan") return "on-request";
120
- return "on-failure";
119
+ return "on-request";
121
120
  }
122
121
 
123
122
  function codexMcpConfig(mcpServers = {}) {
@@ -72,13 +72,15 @@ export async function buildTurnTools(runState, {
72
72
  const builtIns = capabilities.tool_use === false
73
73
  ? []
74
74
  : getPiBuiltinTools(options.allowedTools, /** @type {any} */ ({
75
+ // deny-wins filter applied over the built-ins + ReadSkill inside pi-bridge.
76
+ disallowedTools: options.disallowedTools,
75
77
  skillNames: (options.skills || []).map((/** @type {{name: string}} */ skill) => skill.name),
76
- // Full skill objects so read_skill can honor pi's neutral Skill shape
78
+ // Full skill objects so ReadSkill can honor pi's neutral Skill shape
77
79
  // ({name, filePath, ...}) and derive each skill's root from its own
78
80
  // filePath when no shared skillsRoot is threaded.
79
81
  skills: options.skills || [],
80
82
  // Progressive skill disclosure: when the harness threads the skills root
81
- // (the directory holding `<name>/SKILL.md`) the read_skill tool resolves
83
+ // (the directory holding `<name>/SKILL.md`) the ReadSkill tool resolves
82
84
  // bodies directly from there. `dataDir` (skills under `<dataDir>/skills`)
83
85
  // remains the back-compat fallback; a per-skill filePath is the third path.
84
86
  skillsRoot: options.skillsRoot,
@@ -35,9 +35,10 @@ export function createStructuredOutputTool(outputSchema: any, onStructuredOutput
35
35
  };
36
36
  /**
37
37
  * @param {any} allowedTools
38
- * @param {{skillNames?: any[], skills?: any[], skillsRoot?: any, dataDir?: any, cwd?: any, onEvent?: (event: any) => void, toolLimits?: any, persistArtifact?: any, onTruncate?: any, toolPayloadMaxBytes?: number, imageInlineMaxBytes?: any, toolPolicy?: any, sandboxPolicy?: any, sandboxEngine?: any, approvalManager?: any, approvalModel?: any, ctx?: any}} [options]
38
+ * @param {{disallowedTools?: any[], skillNames?: any[], skills?: any[], skillsRoot?: any, dataDir?: any, cwd?: any, onEvent?: (event: any) => void, toolLimits?: any, persistArtifact?: any, onTruncate?: any, toolPayloadMaxBytes?: number, imageInlineMaxBytes?: any, toolPolicy?: any, sandboxPolicy?: any, sandboxEngine?: any, approvalManager?: any, approvalModel?: any, ctx?: any}} [options]
39
39
  */
40
- export function getPiBuiltinTools(allowedTools: any, { skillNames, skills, skillsRoot, dataDir, cwd, onEvent, toolLimits, persistArtifact, onTruncate, toolPayloadMaxBytes, imageInlineMaxBytes, toolPolicy, sandboxPolicy, sandboxEngine, approvalManager, approvalModel, ctx, }?: {
40
+ export function getPiBuiltinTools(allowedTools: any, { disallowedTools, skillNames, skills, skillsRoot, dataDir, cwd, onEvent, toolLimits, persistArtifact, onTruncate, toolPayloadMaxBytes, imageInlineMaxBytes, toolPolicy, sandboxPolicy, sandboxEngine, approvalManager, approvalModel, ctx, }?: {
41
+ disallowedTools?: any[];
41
42
  skillNames?: any[];
42
43
  skills?: any[];
43
44
  skillsRoot?: any;
@@ -1,2 +1,18 @@
1
1
  export function claudeNativeAgentDefinitions(nativeSubagents: any): {};
2
2
  export function claudeToolsWithNativeSubagents(allowedTools: any, nativeSubagents: any): any;
3
+ /**
4
+ * Resolve the effective allowed-tools list for the Claude bridges, honoring the
5
+ * `"*"` allow-all sentinel. Returns `{ allowAll, tools }` where `tools` is the
6
+ * native-subagent-composed list with any bare `"*"` stripped, so a literal `"*"`
7
+ * never reaches a CLI flag value or the SDK `query()`. When `allowAll` is true
8
+ * the caller MUST defer to Claude's default toolset (omit `--tools` / pass
9
+ * `allowedTools: undefined`) rather than pass `tools` as the tool set — the
10
+ * default already carries every tool, incl. `Task`, so it is not double-added.
11
+ * @param {any} allowedTools
12
+ * @param {any} nativeSubagents
13
+ * @returns {{allowAll: boolean, tools: any}}
14
+ */
15
+ export function resolveClaudeAllowedTools(allowedTools: any, nativeSubagents: any): {
16
+ allowAll: boolean;
17
+ tools: any;
18
+ };