@sagentlab/navarch-runtime 0.1.41 → 0.1.43

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/README.md CHANGED
@@ -326,7 +326,7 @@ unchanged across the deployment.
326
326
  | `NAVARCH_ACP_BIN` | `dsh` | Path/name of an Agent Client Protocol v1 stdio server. DeepSeek Harness is the default implementation. |
327
327
  | `NAVARCH_ACP_EXTRA_ARGS` | `--profile,acp` | Comma list of arguments used to start the ACP server. Override this together with `NAVARCH_ACP_BIN` for another ACP-compatible coding agent. |
328
328
  | `NAVARCH_MCP_CONFIG_PATH` | — | Path to the platform MCP config passed as `--mcp-config`. |
329
- | `NAVARCH_WORKTREE_GUARD` | on | Host-mode sessions get an adapter-native per-session worktree boundary guard (see below). Set `off` to disable. |
329
+ | `NAVARCH_WORKTREE_GUARD` | on | Host-mode sessions get an adapter-native per-session worktree boundary guard (see below). Set `off` to disable it for every runtime on the machine — required to run OpenCode on the host. |
330
330
  | `NAVARCH_GUARD_EXTRA_ROOTS` | — | `path.delimiter`-separated (`:` on POSIX) extra directories the worktree guard allows beyond the session worktree, shared bare repo, and temp dirs. |
331
331
 
332
332
  ## Worktree boundary guard (host mode)
@@ -378,10 +378,13 @@ as an explicit read-only mount.
378
378
  Docker-mode sessions disable Gemini's implicit YOLO sandbox to avoid nesting
379
379
  it inside Navarch's already isolated session container.
380
380
  - **OpenCode:** the CLI cannot currently express a host-side boundary for all
381
- shell side effects. With the guard enabled, host launches therefore fail
382
- closed before the CLI starts. Run OpenCode in Docker with an operator-owned
383
- image that contains an authenticated `opencode` binary, or set
384
- `NAVARCH_WORKTREE_GUARD=off` only when the whole machine is already isolated.
381
+ shell side effects, so with the guard enabled it is resolved out of the
382
+ machine's advertised runtimes at startup — and a machine that offers only
383
+ OpenCode there refuses to start rather than claim tasks it can only fail.
384
+ Run OpenCode in Docker with an operator-owned image that contains an
385
+ authenticated `opencode` binary, or set `NAVARCH_WORKTREE_GUARD=off` only
386
+ when the whole machine is already isolated (the opt-out is machine-wide:
387
+ every runtime on it loses the boundary).
385
388
  OpenCode receives a private, per-session config, project config discovery is
386
389
  disabled, and lease MCP headers are referenced through child-only environment
387
390
  variables instead of being copied into its config file or argv.
@@ -445,15 +448,18 @@ export NAVARCH_AGENT=codex
445
448
  export NAVARCH_AGENT=gemini
446
449
 
447
450
  # OpenCode — requires an authenticated `opencode` CLI (or
448
- # NAVARCH_OPENCODE_BIN pointing at it). The guarded host path fails closed;
449
- # use a Docker image containing OpenCode for the normal isolated path.
451
+ # NAVARCH_OPENCODE_BIN pointing at it). A guarded host refuses to start with
452
+ # only this runtime; use a Docker image containing OpenCode for the normal
453
+ # isolated path, or NAVARCH_WORKTREE_GUARD=off on an isolated machine.
450
454
  export NAVARCH_AGENT=opencode
451
455
 
452
456
  # Agent Client Protocol — defaults to DeepSeek Harness `dsh --profile acp`.
453
457
  # Override NAVARCH_ACP_BIN / NAVARCH_ACP_EXTRA_ARGS for another ACP v1 server.
454
458
  export NAVARCH_AGENT=acp
455
459
 
456
- # Advanced compatibility mode: advertise every installed adapter.
460
+ # Advanced compatibility mode: advertise every installed adapter. On a guarded
461
+ # host, `opencode` is dropped from this list (see the boundary notes above);
462
+ # the rest are advertised as written.
457
463
  export NAVARCH_RUNTIMES=claude-code,codex,gemini,opencode,acp
458
464
  ```
459
465
 
@@ -4,6 +4,7 @@ exports.openCodeAdapter = void 0;
4
4
  exports.runOpenCodeAdapter = runOpenCodeAdapter;
5
5
  const node_child_process_1 = require("node:child_process");
6
6
  const node_fs_1 = require("node:fs");
7
+ const config_cjs_1 = require("../config.cjs");
7
8
  /**
8
9
  * Headless OpenCode adapter, fixture-pinned to the OpenCode 1.18 JSON/config
9
10
  * contract:
@@ -24,9 +25,7 @@ async function runOpenCodeAdapter(options) {
24
25
  timedOut: false,
25
26
  killedByLeaseLoss: false,
26
27
  stdout: "",
27
- stderr: "OpenCode cannot enforce Navarch's host worktree boundary for shell side effects. " +
28
- "Use NAVARCH_SANDBOX_MODE=docker, or explicitly set NAVARCH_WORKTREE_GUARD=off " +
29
- "only on an otherwise isolated machine.",
28
+ stderr: config_cjs_1.OPENCODE_HOST_GUARD_MESSAGE,
30
29
  };
31
30
  }
32
31
  const env = { ...options.env };
package/dist/cli.cjs CHANGED
@@ -274,7 +274,19 @@ async function superviseCommand(flags) {
274
274
  process.exitCode = exitCode;
275
275
  }
276
276
  async function doctorCommand(flags) {
277
- const config = configFromFlags(flags);
277
+ // doctor is the command an operator reaches for precisely when the config
278
+ // will not load — an OpenCode-only machine on a guarded host refuses to
279
+ // start (config.cts). Report that as the diagnosis instead of rethrowing it
280
+ // as a bare CLI error.
281
+ let config;
282
+ try {
283
+ config = configFromFlags(flags);
284
+ }
285
+ catch (err) {
286
+ console.log(`config: UNUSABLE — ${err instanceof Error ? err.message : String(err)}`);
287
+ process.exitCode = 1;
288
+ return;
289
+ }
278
290
  const dockerOk = await (0, sandbox_cjs_1.isDockerAvailable)();
279
291
  console.log(`api_base: ${config.apiBase}`);
280
292
  console.log(`config_dir: ${config.configDir}`);
@@ -282,6 +294,8 @@ async function doctorCommand(flags) {
282
294
  console.log(`max_sessions: ${config.maxSessions}`);
283
295
  console.log(`capabilities: ${config.capabilities.join(", ")}`);
284
296
  console.log(`sandbox_mode: ${config.sandboxMode}`);
297
+ console.log(`worktree_guard: ${config.worktreeGuard ? "on" : "off"}`);
298
+ console.log(`runtimes: ${config.runtimes?.join(", ") ?? config.agentType}`);
285
299
  console.log(`docker: ${dockerOk ? "available" : "NOT AVAILABLE (docker-backed sessions will fail)"}`);
286
300
  let identity;
287
301
  try {
package/dist/config.cjs CHANGED
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_SANDBOX_IMAGE = void 0;
6
+ exports.OPENCODE_HOST_GUARD_MESSAGE = exports.DEFAULT_SANDBOX_IMAGE = void 0;
7
7
  exports.isRuntimeAgentType = isRuntimeAgentType;
8
+ exports.opencodeBlockedOnHost = opencodeBlockedOnHost;
8
9
  exports.loadRuntimeConfig = loadRuntimeConfig;
9
10
  const node_path_1 = __importDefault(require("node:path"));
10
11
  const node_os_1 = __importDefault(require("node:os"));
@@ -44,6 +45,18 @@ function envList(env, name, fallback) {
44
45
  .map((s) => s.trim())
45
46
  .filter(Boolean);
46
47
  }
48
+ /**
49
+ * OpenCode is the one runtime Navarch cannot fence into a per-session worktree
50
+ * on the host: its CLI has no equivalent of Claude's PreToolUse hook or
51
+ * Codex's native permission profile. Docker mode supplies the boundary
52
+ * instead, and NAVARCH_WORKTREE_GUARD=off is the explicit opt-out.
53
+ */
54
+ function opencodeBlockedOnHost(sandboxMode, worktreeGuard) {
55
+ return sandboxMode === "host" && worktreeGuard;
56
+ }
57
+ exports.OPENCODE_HOST_GUARD_MESSAGE = "OpenCode cannot enforce Navarch's host worktree boundary for shell side effects. " +
58
+ "Use NAVARCH_SANDBOX_MODE=docker, or explicitly set NAVARCH_WORKTREE_GUARD=off " +
59
+ "only on an otherwise isolated machine.";
47
60
  /**
48
61
  * Loads runtime config from NAVARCH_* env vars, with sane defaults for a
49
62
  * fresh machine. Accepts an explicit env map (defaulting to process.env) so
@@ -57,8 +70,23 @@ function loadRuntimeConfig(env = process.env) {
57
70
  // prerequisite for claiming ordinary shell work.
58
71
  const sandboxMode = env.NAVARCH_SANDBOX_MODE === "docker" ? "docker" : "host";
59
72
  const defaultCapabilities = sandboxMode === "docker" ? ["docker-sandbox", "shell"] : ["shell", "browser-use"];
60
- const agentType = isRuntimeAgentType(env.NAVARCH_AGENT) ? env.NAVARCH_AGENT : "claude-code";
61
- const runtimes = envList(env, "NAVARCH_RUNTIMES", [agentType]).filter(isRuntimeAgentType);
73
+ const requestedAgentType = isRuntimeAgentType(env.NAVARCH_AGENT) ? env.NAVARCH_AGENT : "claude-code";
74
+ // Multiple sessions share one machine; keeping each agent inside its own
75
+ // worktree is the safe default, so disabling is the explicit opt-out.
76
+ const worktreeGuard = !["off", "false", "0"].includes(env.NAVARCH_WORKTREE_GUARD ?? "");
77
+ const requestedRuntimes = envList(env, "NAVARCH_RUNTIMES", [requestedAgentType]).filter(isRuntimeAgentType);
78
+ // OpenCode's CLI cannot express the host worktree boundary, so its adapter
79
+ // fails closed there (adapters/opencode.cts). Advertising it anyway meant
80
+ // the machine claimed OpenCode tasks it could only fail, one lease at a
81
+ // time; keep it out of the claim instead of discovering this per session.
82
+ const runnableRuntimes = requestedRuntimes.filter((runtime) => runtime !== "opencode" || !opencodeBlockedOnHost(sandboxMode, worktreeGuard));
83
+ if (requestedRuntimes.includes("opencode") && runnableRuntimes.length === 0) {
84
+ throw new Error(exports.OPENCODE_HOST_GUARD_MESSAGE);
85
+ }
86
+ const agentType = runnableRuntimes.includes(requestedAgentType) || runnableRuntimes.length === 0
87
+ ? requestedAgentType
88
+ : (runnableRuntimes[0] ?? requestedAgentType);
89
+ const runtimes = runnableRuntimes;
62
90
  return {
63
91
  apiBase: env.NAVARCH_API_BASE ?? "http://localhost:3000",
64
92
  workspaceRoot: env.NAVARCH_WORKSPACE_ROOT ?? node_path_1.default.join(configDir, "sandboxes"),
@@ -98,9 +126,7 @@ function loadRuntimeConfig(env = process.env) {
98
126
  : sandbox_profile_cjs_1.DEFAULT_SANDBOX_PROFILE_ID,
99
127
  sandboxEgressNetwork: env.NAVARCH_SANDBOX_EGRESS_NETWORK?.trim() || null,
100
128
  dockerImage: env.NAVARCH_DOCKER_IMAGE ?? exports.DEFAULT_SANDBOX_IMAGE,
101
- // Multiple sessions share one machine; keeping each agent inside its own
102
- // worktree is the safe default, so disabling is the explicit opt-out.
103
- worktreeGuard: !["off", "false", "0"].includes(env.NAVARCH_WORKTREE_GUARD ?? ""),
129
+ worktreeGuard,
104
130
  guardExtraRoots: (env.NAVARCH_GUARD_EXTRA_ROOTS ?? "")
105
131
  .split(node_path_1.default.delimiter)
106
132
  .map((s) => s.trim())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagentlab/navarch-runtime",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "Navarch machine-side session manager: claims delivery tasks and runs them through local coding agents.",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",