@mono-agent/agent-runtime 0.15.1 → 0.15.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/MIGRATION.md CHANGED
@@ -11,8 +11,30 @@ exports map, a five-bridge lazy registry, typed policy objects, stricter sandbox
11
11
  behavior, and revised provider-session semantics even when Pi is not your
12
12
  primary route.
13
13
 
14
+ Migration policy: every newly introduced fail-closed validation belongs in the
15
+ first affected version section, even when it tightens behavior without changing
16
+ the configuration schema.
17
+
14
18
  ---
15
19
 
20
+ ## 0.15.2
21
+
22
+ - **Tool-policy capability discovery:** built-in bridge capabilities now report
23
+ `tool_policy: "projected" | "allow_all_only"`. Pi, Claude SDK, and Claude Code
24
+ report `projected`; direct Codex and direct OpenCode report
25
+ `allow_all_only`. Custom structural bridges that omit the field have unknown
26
+ capability.
27
+ - **Wildcard normalization:** any `allowedTools` list containing `"*"` is
28
+ semantically allow-all. Direct Codex, direct OpenCode, and the public legacy
29
+ Codex CLI export now accept forms such as `["*", "Read"]` when
30
+ `disallowedTools` is empty. Named-only lists, `[]`, and every non-empty
31
+ denylist still fail closed on those non-projecting routes. The guided Codex
32
+ readiness probe retains its exact no-tool contract.
33
+ - **Telemetry compatibility:** the route-safety value
34
+ `tools: "exact-allow-all"` is unchanged. It now explicitly denotes the
35
+ effective unrestricted contract rather than requiring a literal one-element
36
+ `["*"]` array.
37
+
16
38
  ## 0.15.1
17
39
 
18
40
  - **Runtime-owned Pi interoperability:** consumers that directly import
@@ -62,6 +84,12 @@ public-surface cleanup described in this guide.
62
84
 
63
85
  ## 0.10.x
64
86
 
87
+ - Direct Codex normal runs introduced a fail-closed tool-policy gate: omitted
88
+ `allowedTools` or the explicit `["*"]` sentinel was accepted with no denied
89
+ tools, while named-only allowlists, `[]`, and deny lists were rejected before
90
+ provider startup. Version 0.15.2 preserves that safety boundary while
91
+ normalizing every wildcard-containing allowlist to the same effective
92
+ allow-all meaning.
65
93
  - `ReadSkill` returns complete skill instructions by default, including content
66
94
  beyond the former 12,000-character boundary. Programmatic callers of
67
95
  `formatSkillBodyWithPathNote()` opt into truncation by passing a positive
package/README.md CHANGED
@@ -664,6 +664,16 @@ Per-call options (a non-exhaustive selection):
664
664
  | `codexAppServerCommand` | `string` | Override the Codex CLI binary. |
665
665
  | `codexAppServerArgs` | `string[]` | Override the Codex CLI arguments. |
666
666
 
667
+ `runtimeCapabilities()` and each descriptor returned by
668
+ `listRuntimeBridges()` expose `tool_policy`. `"projected"` means the bridge can
669
+ project restrictive `allowedTools` / `disallowedTools`; `"allow_all_only"`
670
+ means it accepts only an effective unrestricted policy—`allowedTools` omitted
671
+ or containing `"*"`, with no denied tools. A wildcard dominates named entries,
672
+ so `["*", "Read"]` is allow-all. Named-only lists, `[]`, and any denylist remain
673
+ unsupported on the direct Codex and direct OpenCode bridges and fail before
674
+ provider startup. Built-in bridges always report this field; omission by a
675
+ custom structural bridge means the capability is unknown.
676
+
667
677
  Live input is native on the Claude SDK, Codex app-server, and Pi bridges. The
668
678
  one-shot Claude CLI and direct OpenCode bridges advertise it as unsupported so
669
679
  routers skip them when a direct runtime call requires steering.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mono-agent/agent-runtime",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Agent runtime supporting Claude SDK/CLI, Codex, OpenCode, and Pi SDK bridges out of the box",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-only",
@@ -11,6 +11,10 @@ import { createStderrTail } from "../failure.js";
11
11
  import { modelWithContextWindow } from "../runtime/context-windows.js";
12
12
  import { readRuntimeBrand } from "../../agent/tools/shared/runtime-context.js";
13
13
  import { buildCapabilitiesUsed } from "../runtime/capabilities-used.js";
14
+ import {
15
+ isAllowAllToolPolicy,
16
+ TOOL_POLICY_PROJECTED,
17
+ } from "../runtime/tool-policy.js";
14
18
  import {
15
19
  claudeNativeAgentDefinitions,
16
20
  resolveClaudeAllowedTools,
@@ -28,13 +32,9 @@ const CLAUDE_CLI_EMPTY_TOOL_POLICY_UNSUPPORTED =
28
32
  "Claude Code CLI cannot enforce an explicit empty allowedTools list: omitting --tools would restore Claude Code's default toolset. Use a specific non-empty allowlist, a denylist, or the Claude SDK for a no-tools run.";
29
33
 
30
34
  function codexCliToolPolicyProblem(options) {
31
- const allowedTools = Array.isArray(options.allowedTools) ? options.allowedTools : null;
32
- const disallowedTools = Array.isArray(options.disallowedTools) ? options.disallowedTools : [];
33
- const exactAllowAll = allowedTools === null
34
- || (allowedTools.length === 1 && allowedTools[0] === "*");
35
- return exactAllowAll && disallowedTools.length === 0
35
+ return isAllowAllToolPolicy(options.allowedTools, options.disallowedTools)
36
36
  ? null
37
- : "Direct Codex CLI cannot enforce allowedTools/disallowedTools. Use exact allow-all ([\"*\"] with no disallowedTools) or another runtime.";
37
+ : "Direct Codex CLI cannot enforce allowedTools/disallowedTools. Use allow-all-only (omit allowedTools or include \"*\", with no disallowedTools) or another runtime.";
38
38
  }
39
39
 
40
40
  function codexCliCapabilityMismatchResult(options, error, codexErrorCode, start) {
@@ -86,6 +86,7 @@ const DORMANT_CLI_CAPABILITIES = {
86
86
  // The one-shot CLI bridge cannot add stdin messages after process launch.
87
87
  supports_live_input: false,
88
88
  supports_native_subagents: true,
89
+ tool_policy: TOOL_POLICY_PROJECTED,
89
90
  };
90
91
 
91
92
  function promptFromMessages(messages) {
@@ -465,7 +466,7 @@ export function buildCliCommand({
465
466
  args.push("--tools", cliAllowedTools.join(","));
466
467
  }
467
468
  const autoAllowed = [
468
- ...(Array.isArray(cliAllowedTools) ? cliAllowedTools : []),
469
+ ...(!cliAllowAll && Array.isArray(cliAllowedTools) ? cliAllowedTools : []),
469
470
  ...Object.keys(mcpServers || {}).map((name) => `mcp__${name}__*`),
470
471
  ];
471
472
  if (autoAllowed.length) args.push("--allowedTools", shellList(autoAllowed));
@@ -10,6 +10,10 @@ import { buildCapabilitiesUsed } from "../runtime/capabilities-used.js";
10
10
  import { resolveSandboxPolicy } from "../../agent/tools/shared/tool-context.js";
11
11
  import { createSessionRegistry } from "../runtime/sessions.js";
12
12
  import { createSessionLiveness } from "../runtime/session-liveness.js";
13
+ import {
14
+ isAllowAllToolPolicy,
15
+ TOOL_POLICY_ALLOW_ALL_ONLY,
16
+ } from "../runtime/tool-policy.js";
13
17
 
14
18
  const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
15
19
  const DEFAULT_THREAD_START_ATTEMPTS = 2;
@@ -448,6 +452,7 @@ const CODEX_APP_CAPABILITIES = {
448
452
  supports_live_input: true,
449
453
  supports_native_subagents: true,
450
454
  supports_fast_mode: true,
455
+ tool_policy: TOOL_POLICY_ALLOW_ALL_ONLY,
451
456
  };
452
457
 
453
458
  function promptFromMessages(messages) {
@@ -558,14 +563,9 @@ function codexToolPolicyProblem(options) {
558
563
  }
559
564
  return "Codex no-tool probe mode requires an empty tool policy, no MCP servers, and a disposable session.";
560
565
  }
561
- // `undefined` retains the public runtime's documented allow-all default.
562
- // Once a caller specifies a policy, require the exact wildcard contract.
563
- // Extra entries can conceal a caller's mistaken belief that Codex enforces a
564
- // mixed allowlist, which the app-server cannot project.
565
- const effectiveAllowAll = allowedTools === null || (allowedTools.length === 1 && allowedTools[0] === "*");
566
- return effectiveAllowAll && disallowedTools.length === 0
566
+ return isAllowAllToolPolicy(allowedTools, disallowedTools)
567
567
  ? null
568
- : "Direct Codex cannot enforce allowedTools/disallowedTools. Use exact allow-all ([\"*\"] with no disallowedTools) or another runtime.";
568
+ : "Direct Codex cannot enforce allowedTools/disallowedTools. Use allow-all-only (omit allowedTools or include \"*\", with no disallowedTools) or another runtime.";
569
569
  }
570
570
 
571
571
  const CODEX_NO_TOOL_ACTION_ITEMS = new Set([
@@ -3,6 +3,10 @@ import { estimateCost } from "../cost.js";
3
3
  import { buildCapabilitiesUsed } from "../runtime/capabilities-used.js";
4
4
  import { createApprovalManager, RISK_TIERS } from "../../agent/approval.js";
5
5
  import { resolveSandboxPolicy } from "../../agent/tools/shared/tool-context.js";
6
+ import {
7
+ isAllowAllToolPolicy,
8
+ TOOL_POLICY_ALLOW_ALL_ONLY,
9
+ } from "../runtime/tool-policy.js";
6
10
  import { createIsolatedOpencode } from "./opencode-server.js";
7
11
  import {
8
12
  toolUseEvent,
@@ -37,6 +41,7 @@ const OPENCODE_APP_CAPABILITIES = {
37
41
  supports_live_input: false,
38
42
  supports_native_subagents: false,
39
43
  supports_fast_mode: false,
44
+ tool_policy: TOOL_POLICY_ALLOW_ALL_ONLY,
40
45
  };
41
46
 
42
47
  // How long to keep draining the event stream after session.prompt resolves, in
@@ -428,13 +433,9 @@ export function mapSpawnFailureKind(err) {
428
433
  }
429
434
 
430
435
  function opencodeToolPolicyProblem(options) {
431
- const allowedTools = Array.isArray(options.allowedTools) ? options.allowedTools : null;
432
- const disallowedTools = Array.isArray(options.disallowedTools) ? options.disallowedTools : [];
433
- const exactAllowAll = allowedTools === null
434
- || (allowedTools.length === 1 && allowedTools[0] === "*");
435
- return exactAllowAll && disallowedTools.length === 0
436
+ return isAllowAllToolPolicy(options.allowedTools, options.disallowedTools)
436
437
  ? null
437
- : "Direct OpenCode cannot enforce allowedTools/disallowedTools. Use exact allow-all ([\"*\"] with no disallowedTools) or a Pi runtime (including pi:opencode-go:*).";
438
+ : "Direct OpenCode cannot enforce allowedTools/disallowedTools. Use allow-all-only (omit allowedTools or include \"*\", with no disallowedTools) or a Pi runtime (including pi:opencode-go:*).";
438
439
  }
439
440
 
440
441
  function opencodeCapabilityMismatchResult({
@@ -1,3 +1,8 @@
1
+ import {
2
+ TOOL_POLICY_ALLOW_ALL_ONLY,
3
+ TOOL_POLICY_PROJECTED,
4
+ } from "./tool-policy.js";
5
+
1
6
  export const COMMON_CAPABILITIES = {
2
7
  streaming: true,
3
8
  structured_output: true,
@@ -9,6 +14,7 @@ export const COMMON_CAPABILITIES = {
9
14
  supports_live_input: true,
10
15
  supports_native_subagents: true,
11
16
  supports_fast_mode: false,
17
+ tool_policy: TOOL_POLICY_PROJECTED,
12
18
  };
13
19
 
14
20
  export const RUNTIME_CAPABILITIES = {
@@ -36,6 +42,7 @@ export const RUNTIME_CAPABILITIES = {
36
42
  // when options.sessionKeepAlive is set; resumed turns reuse the thread.
37
43
  supports_session_resume: true,
38
44
  supports_fast_mode: true,
45
+ tool_policy: TOOL_POLICY_ALLOW_ALL_ONLY,
39
46
  },
40
47
  opencode: {
41
48
  runtime: "cli",
@@ -46,6 +53,7 @@ export const RUNTIME_CAPABILITIES = {
46
53
  supports_skills: false,
47
54
  supports_live_input: false,
48
55
  supports_native_subagents: false,
56
+ tool_policy: TOOL_POLICY_ALLOW_ALL_ONLY,
49
57
  },
50
58
  };
51
59
 
@@ -0,0 +1,22 @@
1
+ // @ts-check
2
+
3
+ /** @type {"projected"} */
4
+ export const TOOL_POLICY_PROJECTED = "projected";
5
+ /** @type {"allow_all_only"} */
6
+ export const TOOL_POLICY_ALLOW_ALL_ONLY = "allow_all_only";
7
+
8
+ /**
9
+ * True when a tool policy is semantically unrestricted.
10
+ *
11
+ * An omitted allowlist and any allowlist containing the global `"*"` sentinel
12
+ * both mean allow-all. A denylist still makes the policy restrictive.
13
+ *
14
+ * @param {*} allowedTools
15
+ * @param {*} disallowedTools
16
+ * @returns {boolean}
17
+ */
18
+ export function isAllowAllToolPolicy(allowedTools, disallowedTools) {
19
+ const allowAll = !Array.isArray(allowedTools) || allowedTools.includes("*");
20
+ const hasDeniedTools = Array.isArray(disallowedTools) && disallowedTools.length > 0;
21
+ return allowAll && !hasDeniedTools;
22
+ }
package/src/ai/types.js CHANGED
@@ -56,7 +56,12 @@
56
56
  * branch ran for a particular command.
57
57
  */
58
58
 
59
- /** @typedef {"mono-agent-monotonic"|"mono-agent-policy"|"provider-representable"|"exact-allow-all"|"unsupported"} RuntimeRouteToolsContract */
59
+ /**
60
+ * @typedef {"mono-agent-monotonic"|"mono-agent-policy"|"provider-representable"|"exact-allow-all"|"unsupported"} RuntimeRouteToolsContract
61
+ * `exact-allow-all` is a stable telemetry token. It describes an effective
62
+ * unrestricted contract, including mixed allowlists that contain `"*"`;
63
+ * it does not require the literal one-element array `["*"]`.
64
+ */
60
65
 
61
66
  /**
62
67
  * @typedef {Object} RuntimeRouteSafetyContract
@@ -223,6 +228,10 @@
223
228
  * @property {boolean} [supports_live_input]
224
229
  * @property {boolean} [supports_native_subagents]
225
230
  * @property {boolean} [supports_fast_mode]
231
+ * @property {"projected"|"allow_all_only"} [tool_policy] Whether the bridge can
232
+ * project named allow/deny policies or accepts only a semantically unrestricted
233
+ * policy. Built-in bridges always report this field; omission means unknown
234
+ * for custom structural capability objects.
226
235
  */
227
236
 
228
237
  /**
@@ -168,6 +168,7 @@ export namespace claudeCodeRuntimeBridge {
168
168
  export let supports_builtin_tools: boolean;
169
169
  export let supports_live_input: boolean;
170
170
  export let supports_native_subagents: boolean;
171
+ export { TOOL_POLICY_PROJECTED as tool_policy };
171
172
  let kind_1: string;
172
173
  export { kind_1 as kind };
173
174
  export let runtime: string;
@@ -301,3 +302,4 @@ export namespace claudeCodeRuntimeBridge {
301
302
  };
302
303
  }>;
303
304
  }
305
+ import { TOOL_POLICY_PROJECTED } from "../runtime/tool-policy.js";
@@ -145,5 +145,7 @@ declare namespace CODEX_APP_CAPABILITIES {
145
145
  export let supports_live_input: boolean;
146
146
  export let supports_native_subagents: boolean;
147
147
  export let supports_fast_mode: boolean;
148
+ export { TOOL_POLICY_ALLOW_ALL_ONLY as tool_policy };
148
149
  }
150
+ import { TOOL_POLICY_ALLOW_ALL_ONLY } from "../runtime/tool-policy.js";
149
151
  export {};
@@ -27,6 +27,7 @@ declare namespace OPENCODE_APP_CAPABILITIES {
27
27
  export let supports_live_input: boolean;
28
28
  export let supports_native_subagents: boolean;
29
29
  export let supports_fast_mode: boolean;
30
+ export { TOOL_POLICY_ALLOW_ALL_ONLY as tool_policy };
30
31
  }
31
32
  declare function generateOpencodeAppResponse(systemPrompt: any, options?: {}): Promise<{
32
33
  text: any;
@@ -91,4 +92,5 @@ declare function generateOpencodeAppResponse(systemPrompt: any, options?: {}): P
91
92
  context_compaction_applied: any;
92
93
  };
93
94
  }>;
95
+ import { TOOL_POLICY_ALLOW_ALL_ONLY } from "../runtime/tool-policy.js";
94
96
  export {};
@@ -1,15 +1,16 @@
1
1
  export function runtimeCapabilities(sdkOrModel: any): any;
2
2
  export namespace COMMON_CAPABILITIES {
3
- let streaming: boolean;
4
- let structured_output: boolean;
5
- let supports_session_resume: boolean;
6
- let native_runtime_config: any;
7
- let supports_mcp: boolean;
8
- let supports_skills: boolean;
9
- let supports_builtin_tools: boolean;
10
- let supports_live_input: boolean;
11
- let supports_native_subagents: boolean;
12
- let supports_fast_mode: boolean;
3
+ export let streaming: boolean;
4
+ export let structured_output: boolean;
5
+ export let supports_session_resume: boolean;
6
+ export let native_runtime_config: any;
7
+ export let supports_mcp: boolean;
8
+ export let supports_skills: boolean;
9
+ export let supports_builtin_tools: boolean;
10
+ export let supports_live_input: boolean;
11
+ export let supports_native_subagents: boolean;
12
+ export let supports_fast_mode: boolean;
13
+ export { TOOL_POLICY_PROJECTED as tool_policy };
13
14
  }
14
15
  export namespace RUNTIME_CAPABILITIES {
15
16
  namespace claude {
@@ -30,6 +31,7 @@ export namespace RUNTIME_CAPABILITIES {
30
31
  export { supports_session_resume_3 as supports_session_resume };
31
32
  let supports_fast_mode_1: boolean;
32
33
  export { supports_fast_mode_1 as supports_fast_mode };
34
+ export { TOOL_POLICY_ALLOW_ALL_ONLY as tool_policy };
33
35
  let runtime_2: string;
34
36
  export { runtime_2 as runtime };
35
37
  }
@@ -46,7 +48,10 @@ export namespace RUNTIME_CAPABILITIES {
46
48
  export { supports_live_input_1 as supports_live_input };
47
49
  let supports_native_subagents_2: boolean;
48
50
  export { supports_native_subagents_2 as supports_native_subagents };
51
+ export { TOOL_POLICY_ALLOW_ALL_ONLY as tool_policy };
49
52
  let runtime_3: string;
50
53
  export { runtime_3 as runtime };
51
54
  }
52
55
  }
56
+ import { TOOL_POLICY_PROJECTED } from "./tool-policy.js";
57
+ import { TOOL_POLICY_ALLOW_ALL_ONLY } from "./tool-policy.js";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * True when a tool policy is semantically unrestricted.
3
+ *
4
+ * An omitted allowlist and any allowlist containing the global `"*"` sentinel
5
+ * both mean allow-all. A denylist still makes the policy restrictive.
6
+ *
7
+ * @param {*} allowedTools
8
+ * @param {*} disallowedTools
9
+ * @returns {boolean}
10
+ */
11
+ export function isAllowAllToolPolicy(allowedTools: any, disallowedTools: any): boolean;
12
+ /** @type {"projected"} */
13
+ export const TOOL_POLICY_PROJECTED: "projected";
14
+ /** @type {"allow_all_only"} */
15
+ export const TOOL_POLICY_ALLOW_ALL_ONLY: "allow_all_only";
@@ -33,7 +33,12 @@
33
33
  * explicitly permits host execution if unavailable; it does not claim which
34
34
  * branch ran for a particular command.
35
35
  */
36
- /** @typedef {"mono-agent-monotonic"|"mono-agent-policy"|"provider-representable"|"exact-allow-all"|"unsupported"} RuntimeRouteToolsContract */
36
+ /**
37
+ * @typedef {"mono-agent-monotonic"|"mono-agent-policy"|"provider-representable"|"exact-allow-all"|"unsupported"} RuntimeRouteToolsContract
38
+ * `exact-allow-all` is a stable telemetry token. It describes an effective
39
+ * unrestricted contract, including mixed allowlists that contain `"*"`;
40
+ * it does not require the literal one-element array `["*"]`.
41
+ */
37
42
  /**
38
43
  * @typedef {Object} RuntimeRouteSafetyContract
39
44
  * Bounded, credential-free description of the sandbox/tool contract applied
@@ -191,6 +196,10 @@
191
196
  * @property {boolean} [supports_live_input]
192
197
  * @property {boolean} [supports_native_subagents]
193
198
  * @property {boolean} [supports_fast_mode]
199
+ * @property {"projected"|"allow_all_only"} [tool_policy] Whether the bridge can
200
+ * project named allow/deny policies or accepts only a semantically unrestricted
201
+ * policy. Built-in bridges always report this field; omission means unknown
202
+ * for custom structural capability objects.
194
203
  */
195
204
  /**
196
205
  * @typedef {Object} RuntimeBridge
@@ -341,6 +350,11 @@ export type RuntimeRouteSafetyMode = "uniform" | "per-route-native";
341
350
  * branch ran for a particular command.
342
351
  */
343
352
  export type RuntimeRouteSandboxContract = "mono-agent-monotonic" | "disabled" | "mono-agent-srt" | "mono-agent-srt-unsafe-host-fallback" | "provider-native" | "codex-native" | "unsupported";
353
+ /**
354
+ * `exact-allow-all` is a stable telemetry token. It describes an effective
355
+ * unrestricted contract, including mixed allowlists that contain `"*"`;
356
+ * it does not require the literal one-element array `["*"]`.
357
+ */
344
358
  export type RuntimeRouteToolsContract = "mono-agent-monotonic" | "mono-agent-policy" | "provider-representable" | "exact-allow-all" | "unsupported";
345
359
  /**
346
360
  * Bounded, credential-free description of the sandbox/tool contract applied
@@ -654,6 +668,13 @@ export type RuntimeCapabilities = {
654
668
  supports_live_input?: boolean;
655
669
  supports_native_subagents?: boolean;
656
670
  supports_fast_mode?: boolean;
671
+ /**
672
+ * Whether the bridge can
673
+ * project named allow/deny policies or accepts only a semantically unrestricted
674
+ * policy. Built-in bridges always report this field; omission means unknown
675
+ * for custom structural capability objects.
676
+ */
677
+ tool_policy?: "projected" | "allow_all_only";
657
678
  };
658
679
  /**
659
680
  * A fully loaded bridge (registry.js's resolveRuntimeBridge result): the