@sagentlab/navarch-runtime 0.1.0 → 0.1.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/README.md CHANGED
@@ -7,13 +7,13 @@ it. Plain Node/TypeScript, zero production dependencies, no Next.js coupling
7
7
  machine.
8
8
 
9
9
  The agent CLI a session runs is either **Claude Code** or **OpenAI Codex**,
10
- selected per machine via `FLOTILLA_AGENT` (default `claude-code`) — see
10
+ selected per machine via `NAVARCH_AGENT` (default `claude-code`) — see
11
11
  "Choosing an agent (Claude Code vs. Codex)" below.
12
12
 
13
13
  See [`docs/agent-platform-project-plan.md`](../docs/agent-platform-project-plan.md)
14
- §3.8/§3.9/§3.11 and [`docs/flotilla/implementation-plan.md`](../docs/flotilla/implementation-plan.md)
14
+ §3.8/§3.9/§3.11 and [`docs/navarch/implementation-plan.md`](../docs/navarch/implementation-plan.md)
15
15
  WP-07 for the design this implements, and
16
- [`docs/flotilla/schema-design.md`](../docs/flotilla/schema-design.md) §7 for
16
+ [`docs/navarch/schema-design.md`](../docs/navarch/schema-design.md) §7 for
17
17
  the API contract.
18
18
 
19
19
  ## Quick start on a fresh machine
@@ -21,7 +21,7 @@ the API contract.
21
21
  ```sh
22
22
  git clone <this repo> && cd sagentlab/runtime
23
23
  ./install.sh # checks node/docker, npm install, npm run build
24
- export FLOTILLA_API_BASE=https://flotilla.example.com
24
+ export NAVARCH_API_BASE=https://navarch.example.com
25
25
  node bin/navarch.cjs register --token <enrollment-token> --name my-machine-1
26
26
  node bin/navarch.cjs start
27
27
  ```
@@ -30,15 +30,15 @@ Or, once dependencies are installed:
30
30
 
31
31
  ```sh
32
32
  npm run build
33
- FLOTILLA_API_BASE=https://flotilla.example.com npm run register -- --token <enrollment-token> --name my-machine-1
33
+ NAVARCH_API_BASE=https://navarch.example.com npm run register -- --token <enrollment-token> --name my-machine-1
34
34
  npm start
35
35
  ```
36
36
 
37
37
  `register` is the one command that prints the machine's auth token — exactly
38
- once, to stdout. It is stored at `$FLOTILLA_CONFIG_DIR/machine.json` (default
39
- `~/.flotilla/machine.json`, mode `0600`) and never echoed again. For
38
+ once, to stdout. It is stored at `$NAVARCH_CONFIG_DIR/machine.json` (default
39
+ `~/.navarch/machine.json`, mode `0600`) and never echoed again. For
40
40
  twelve-factor deployments (systemd `EnvironmentFile`, container secrets),
41
- skip `register` and set `FLOTILLA_MACHINE_TOKEN` + `FLOTILLA_MACHINE_ID`
41
+ skip `register` and set `NAVARCH_MACHINE_TOKEN` + `NAVARCH_MACHINE_ID`
42
42
  directly.
43
43
 
44
44
  Run `node bin/navarch.cjs doctor` any time to print resolved config, Docker
@@ -52,21 +52,21 @@ runner token): a project **owner** mints a short-lived (60 minute),
52
52
  single-use token from the platform UI (the project settings page's "Connect
53
53
  an agent" panel, or the onboarding wizard's Agent step),
54
54
  `POST /api/projects/:id/enrollment-tokens`, and pastes you the ready-to-run
55
- command. Unlike `register`, no `FLOTILLA_ENROLLMENT_SECRET` or
55
+ command. Unlike `register`, no `NAVARCH_ENROLLMENT_SECRET` or
56
56
  `--owner-zone` is needed — the token is already scoped to exactly one
57
57
  project, and the resulting machine's `project_bindings` is set to that
58
58
  project only (it will never be dispatched work from any other project).
59
59
 
60
60
  ```sh
61
61
  npx @sagentlab/navarch-runtime connect --token flmt_<...> --project <project-id> \
62
- --name my-agent-1 --api-base https://flotilla.example.com
62
+ --name my-agent-1 --api-base https://navarch.example.com
63
63
  node bin/navarch.cjs start
64
64
  ```
65
65
 
66
- (`@sagentlab/navarch-runtime` is not yet published to npm see "What needs
67
- live verification" below for the from-source equivalent, i.e. the
68
- `git clone` + `./install.sh` + `node bin/navarch.cjs connect ...` flow
69
- above.)
66
+ (`@sagentlab/navarch-runtime` is published to npm, so `npx
67
+ @sagentlab/navarch-runtime <cmd>` works on a fresh machine with no clone.
68
+ The from-source flow — `git clone` + `./install.sh` + `node bin/navarch.cjs
69
+ <cmd>` — remains available for local development.)
70
70
 
71
71
  ## Commands
72
72
 
@@ -74,50 +74,50 @@ above.)
74
74
  |---|---|
75
75
  | `register --token <t> --name <n> [--capabilities a,b] [--max-sessions N] [--owner-zone z] [--api-base url]` | Registers this machine (global enrollment secret), prints the token once. |
76
76
  | `connect --token <t> --name <n> [--project <id>] [--capabilities a,b] [--max-sessions N] [--api-base url]` | Connects this machine to exactly one project (project-scoped enrollment token), prints the token once. |
77
- | `start` | Runs the daemon: machine heartbeat loop + claim loop, `FLOTILLA_MAX_SESSIONS` concurrent task sessions. |
77
+ | `start` | Runs the daemon: machine heartbeat loop + claim loop, `NAVARCH_MAX_SESSIONS` concurrent task sessions. |
78
78
  | `doctor` | Prints resolved config + Docker/registration status; no side effects. |
79
79
 
80
- ## Configuration (`FLOTILLA_*` env vars)
80
+ ## Configuration (`NAVARCH_*` env vars)
81
81
 
82
82
  | Var | Default | Meaning |
83
83
  |---|---|---|
84
- | `FLOTILLA_API_BASE` | `http://localhost:3000` | Control-plane base URL. |
85
- | `FLOTILLA_MACHINE_TOKEN` / `FLOTILLA_MACHINE_ID` | — | Skip `register`/the config file; twelve-factor auth. |
86
- | `FLOTILLA_MACHINE_NAME` | — | Used by `register`. |
87
- | `FLOTILLA_ENROLLMENT_TOKEN` | — | Alternative to `register --token` / `connect --token`. |
88
- | `FLOTILLA_PROJECT_ID` | — | Alternative to `connect --project`. |
89
- | `FLOTILLA_CONFIG_DIR` | `~/.flotilla` | Where `machine.json` lives. |
90
- | `FLOTILLA_WORKSPACE_ROOT` | `<config dir>/sandboxes` | Per-session working directories (host side; mounted into each container). |
91
- | `FLOTILLA_MAX_SESSIONS` | `3` | Capacity cap — see `src/capacity.cts`. |
92
- | `FLOTILLA_CAPABILITIES` | `docker-sandbox,shell` | Comma list reported at heartbeat/claim time. |
93
- | `FLOTILLA_OWNER_ZONE` | `sagentlab` | `sagentlab` or `customer-<slug>-premises` (project-plan.md §3.11). |
94
- | `FLOTILLA_POLL_INTERVAL_MS` | `5000` | Claim-loop poll interval. |
95
- | `FLOTILLA_HEARTBEAT_INTERVAL_MS` | `60000` | Machine-level heartbeat interval. |
96
- | `FLOTILLA_LEASE_HEARTBEAT_INTERVAL_MS` | `300000` | Per-lease heartbeat interval; must stay well under the 15-minute lease TTL (schema-design.md §4). |
97
- | `FLOTILLA_SESSION_TIMEOUT_MS` | `2700000` (45 min) | Hard kill timeout for a single session. |
98
- | `FLOTILLA_SANDBOX_MODE` | `docker` | `docker` or `host` (host mode skips the container — dev/debug only, no isolation). |
99
- | `FLOTILLA_DOCKER_IMAGE` | `node:20-slim` | Image used for the per-session container. |
100
- | `FLOTILLA_AGENT` | `claude-code` | Which agent CLI runs sessions: `claude-code` or `codex`. Any other value falls back to `claude-code`. Recorded as `agent_type` on the `complete()` call so the sessions row shows which one ran. |
101
- | `FLOTILLA_CLAUDE_BIN` | `claude` | Path/name of the Claude Code CLI binary. |
102
- | `FLOTILLA_CLAUDE_EXTRA_ARGS` | — | Comma list of extra CLI args appended after `--mcp-config` (Claude Code). |
103
- | `FLOTILLA_CODEX_BIN` | `codex` | Path/name of the Codex CLI binary. |
104
- | `FLOTILLA_CODEX_EXTRA_ARGS` | — | Comma list of extra CLI args appended after `--mcp-config`/`--json` (Codex). |
105
- | `FLOTILLA_MCP_CONFIG_PATH` | — | Path to the platform MCP config passed as `--mcp-config`. |
84
+ | `NAVARCH_API_BASE` | `http://localhost:3000` | Control-plane base URL. |
85
+ | `NAVARCH_MACHINE_TOKEN` / `NAVARCH_MACHINE_ID` | — | Skip `register`/the config file; twelve-factor auth. |
86
+ | `NAVARCH_MACHINE_NAME` | — | Used by `register`. |
87
+ | `NAVARCH_ENROLLMENT_TOKEN` | — | Alternative to `register --token` / `connect --token`. |
88
+ | `NAVARCH_PROJECT_ID` | — | Alternative to `connect --project`. |
89
+ | `NAVARCH_CONFIG_DIR` | `~/.navarch` | Where `machine.json` lives. |
90
+ | `NAVARCH_WORKSPACE_ROOT` | `<config dir>/sandboxes` | Per-session working directories (host side; mounted into each container). |
91
+ | `NAVARCH_MAX_SESSIONS` | `3` | Capacity cap — see `src/capacity.cts`. |
92
+ | `NAVARCH_CAPABILITIES` | `docker-sandbox,shell` | Comma list reported at heartbeat/claim time. |
93
+ | `NAVARCH_OWNER_ZONE` | `sagentlab` | `sagentlab` or `customer-<slug>-premises` (project-plan.md §3.11). |
94
+ | `NAVARCH_POLL_INTERVAL_MS` | `5000` | Claim-loop poll interval. |
95
+ | `NAVARCH_HEARTBEAT_INTERVAL_MS` | `60000` | Machine-level heartbeat interval. |
96
+ | `NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS` | `300000` | Per-lease heartbeat interval; must stay well under the 15-minute lease TTL (schema-design.md §4). |
97
+ | `NAVARCH_SESSION_TIMEOUT_MS` | `2700000` (45 min) | Hard kill timeout for a single session. |
98
+ | `NAVARCH_SANDBOX_MODE` | `docker` | `docker` or `host` (host mode skips the container — dev/debug only, no isolation). |
99
+ | `NAVARCH_DOCKER_IMAGE` | `node:20-slim` | Image used for the per-session container. |
100
+ | `NAVARCH_AGENT` | `claude-code` | Which agent CLI runs sessions: `claude-code` or `codex`. Any other value falls back to `claude-code`. Recorded as `agent_type` on the `complete()` call so the sessions row shows which one ran. |
101
+ | `NAVARCH_CLAUDE_BIN` | `claude` | Path/name of the Claude Code CLI binary. |
102
+ | `NAVARCH_CLAUDE_EXTRA_ARGS` | — | Comma list of extra CLI args appended after `--mcp-config` (Claude Code). |
103
+ | `NAVARCH_CODEX_BIN` | `codex` | Path/name of the Codex CLI binary. |
104
+ | `NAVARCH_CODEX_EXTRA_ARGS` | — | Comma list of extra CLI args appended after `--mcp-config`/`--json` (Codex). |
105
+ | `NAVARCH_MCP_CONFIG_PATH` | — | Path to the platform MCP config passed as `--mcp-config`. |
106
106
 
107
107
  ## Choosing an agent (Claude Code vs. Codex)
108
108
 
109
109
  Each machine runs sessions with exactly one agent CLI, set once via
110
- `FLOTILLA_AGENT`:
110
+ `NAVARCH_AGENT`:
111
111
 
112
112
  ```sh
113
113
  # Claude Code (default) — requires the `claude` CLI installed and
114
- # authenticated on this machine (or FLOTILLA_CLAUDE_BIN pointing at it).
115
- export FLOTILLA_AGENT=claude-code
114
+ # authenticated on this machine (or NAVARCH_CLAUDE_BIN pointing at it).
115
+ export NAVARCH_AGENT=claude-code
116
116
 
117
117
  # OpenAI Codex — requires the `codex` CLI installed and authenticated on
118
- # this machine (or FLOTILLA_CODEX_BIN pointing at it), analogous to the
118
+ # this machine (or NAVARCH_CODEX_BIN pointing at it), analogous to the
119
119
  # Claude Code prerequisite above.
120
- export FLOTILLA_AGENT=codex
120
+ export NAVARCH_AGENT=codex
121
121
  ```
122
122
 
123
123
  Both adapters implement the same `AgentAdapter` interface
@@ -144,17 +144,17 @@ cli.cts
144
144
  ├─ register → api.registerMachine() → machine-store.cts (writes machine.json once)
145
145
  ├─ connect → api.connectMachine() → machine-store.cts (writes machine.json once)
146
146
  └─ start
147
- ├─ MachineHeartbeatLoop (heartbeat-loop.cts) → api.machineHeartbeat() [every FLOTILLA_HEARTBEAT_INTERVAL_MS]
148
- └─ ClaimLoop (claim-loop.cts) → api.claim() [every FLOTILLA_POLL_INTERVAL_MS, gated by CapacityTracker]
149
- └─ runSession (session.cts), one per claimed lease, run concurrently up to FLOTILLA_MAX_SESSIONS:
147
+ ├─ MachineHeartbeatLoop (heartbeat-loop.cts) → api.machineHeartbeat() [every NAVARCH_HEARTBEAT_INTERVAL_MS]
148
+ └─ ClaimLoop (claim-loop.cts) → api.claim() [every NAVARCH_POLL_INTERVAL_MS, gated by CapacityTracker]
149
+ └─ runSession (session.cts), one per claimed lease, run concurrently up to NAVARCH_MAX_SESSIONS:
150
150
  1. write prompt.md (prompt.cts renders the 4-layer context bundle)
151
151
  2. api.issueSecrets() → held in memory only
152
152
  3. DockerSandbox.create/injectEnv/cloneRepo (sandbox.cts)
153
153
  4. selectAdapter(config.agentType) (adapters/index.cts) picks one AgentAdapter
154
- (adapters/types.cts) by FLOTILLA_AGENT, then .run(...):
154
+ (adapters/types.cts) by NAVARCH_AGENT, then .run(...):
155
155
  - claudeCodeAdapter (adapters/claude.cts) — `claude -p <prompt> --mcp-config <path>`
156
156
  - codexAdapter (adapters/codex.cts) — `codex exec <prompt> --json --mcp-config <path>` (ASSUMED)
157
- heartbeating the lease every FLOTILLA_LEASE_HEARTBEAT_INTERVAL_MS throughout either;
157
+ heartbeating the lease every NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS throughout either;
158
158
  a failed heartbeat aborts the run (kills the process) and marks the outcome as lease-lost
159
159
  5. mapExitCondition (exit-conditions.cts) → redact.cts scrubs the transcript → upload.cts PUTs it
160
160
  6. api.completeLease(), reporting agent_type: config.agentType
@@ -229,13 +229,13 @@ everything that can be verified without those:
229
229
  plus Claude's and Codex's usage/report parsing (`tests/exit-conditions.test.cts`).
230
230
  - `redact.cts` — exact-value and pattern-based redaction (`tests/redact.test.cts`).
231
231
  - `capacity.cts` — capacity math and acquire/release bookkeeping (`tests/capacity.test.cts`).
232
- - `config.cts` — env var parsing and defaults, including `FLOTILLA_AGENT`/
233
- `FLOTILLA_CODEX_BIN`/`FLOTILLA_CODEX_EXTRA_ARGS` (`tests/config.test.cts`).
232
+ - `config.cts` — env var parsing and defaults, including `NAVARCH_AGENT`/
233
+ `NAVARCH_CODEX_BIN`/`NAVARCH_CODEX_EXTRA_ARGS` (`tests/config.test.cts`).
234
234
  - `sandbox.cts` — command construction (flags, env-via-stdin, credential-helper
235
235
  argv hygiene) against an injected fake `CommandRunner`, plus `isDockerAvailable()`
236
236
  degrading to `false` instead of throwing when Docker is absent (`tests/sandbox.test.cts`).
237
237
  - `adapters/index.cts#selectAdapter` — picks the right `AgentAdapter` for every
238
- `FLOTILLA_AGENT` value, including the claude-code fallback for an unrecognized
238
+ `NAVARCH_AGENT` value, including the claude-code fallback for an unrecognized
239
239
  one (`tests/adapters/index.test.cts`).
240
240
  - `adapters/codex.cts` — arg construction on both the host path (mocked `spawn`)
241
241
  and the docker-exec path (fake `CommandRunner`), and usage/report-text
@@ -287,7 +287,7 @@ secrets absent from disk after exit"):
287
287
  `--dangerously-bypass-approvals-and-sandbox` per published Codex CLI
288
288
  docs) to avoid blocking on an approval prompt inside the already-isolated
289
289
  Docker sandbox — deliberately **not** hardcoded, left to
290
- `FLOTILLA_CODEX_EXTRA_ARGS` until confirmed, since guessing the wrong
290
+ `NAVARCH_CODEX_EXTRA_ARGS` until confirmed, since guessing the wrong
291
291
  flag here could silently disable sandboxing rather than just fail loudly.
292
292
  - Whether the Codex CLI even authenticates/runs non-interactively the same
293
293
  way `claude` does (API key vs. ChatGPT-account OAuth device flow) — this
@@ -315,13 +315,14 @@ secrets absent from disk after exit"):
315
315
  that one project. Unit-tested here: `connectMachine()`'s request shape
316
316
  (`tests/api.test.cts`) and the `connect` command's flag/env parsing
317
317
  (`tests/cli.test.cts`); not tested here: the real Postgres round trip
318
- (`lib/flotilla/__tests__/enrollment.test.ts` and the
318
+ (`lib/navarch/__tests__/enrollment.test.ts` and the
319
319
  `app/api/machines/connect` / `app/api/projects/[id]/enrollment-tokens`
320
320
  route tests cover that with a mocked admin client, not a live database).
321
- - `@sagentlab/navarch-runtime` is not yet published to npm — every `npx
321
+ - `@sagentlab/navarch-runtime` is published to npm — every `npx
322
322
  @sagentlab/navarch-runtime ...` command shown above (and in
323
- `ConnectAgentPanel`) currently requires the from-source flow instead
324
- (`git clone` + `./install.sh` + `node bin/navarch.cjs connect ...`).
323
+ `ConnectAgentPanel`) resolves the published package directly; the
324
+ from-source flow (`git clone` + `./install.sh` + `node bin/navarch.cjs
325
+ connect ...`) is an equivalent local-development alternative.
325
326
 
326
327
  ## A note on `.cts` instead of `.ts`
327
328
 
package/dist/adapter.cjs CHANGED
@@ -7,7 +7,7 @@ exports.claudeCodeAdapter = exports.runClaudeCodeAdapter = void 0;
7
7
  * The Claude Code adapter that used to live entirely in this file now lives
8
8
  * in adapters/claude.cts, implementing the generalized `AgentAdapter`
9
9
  * interface (adapters/types.cts) alongside adapters/codex.cts's Codex CLI
10
- * sibling — session.cts picks between the two via FLOTILLA_AGENT
10
+ * sibling — session.cts picks between the two via NAVARCH_AGENT
11
11
  * (config.cts's `agentType`) through adapters/index.cts#selectAdapter.
12
12
  *
13
13
  * This file is kept, unchanged in its exported names, so any existing
@@ -14,7 +14,7 @@ const exit_conditions_cjs_1 = require("../exit-conditions.cjs");
14
14
  *
15
15
  * This is one of two implementations of the AgentAdapter interface
16
16
  * (adapters/types.cts) — see adapters/codex.cts for the Codex CLI sibling
17
- * session.cts picks between via FLOTILLA_AGENT (config.cts's `agentType`).
17
+ * session.cts picks between via NAVARCH_AGENT (config.cts's `agentType`).
18
18
  *
19
19
  * NEEDS LIVE VERIFICATION (WP-13): `--output-format json` is assumed to make
20
20
  * `claude -p` print a single JSON result object with a `usage` block, per
@@ -128,7 +128,7 @@ async function runViaDocker(options, args) {
128
128
  function shellQuote(value) {
129
129
  return `'${value.replace(/'/g, `'\\''`)}'`;
130
130
  }
131
- /** The AgentAdapter (adapters/types.cts) wrapper session.cts selects via FLOTILLA_AGENT=claude-code (the default). */
131
+ /** The AgentAdapter (adapters/types.cts) wrapper session.cts selects via NAVARCH_AGENT=claude-code (the default). */
132
132
  exports.claudeCodeAdapter = {
133
133
  agentType: "claude-code",
134
134
  run: runClaudeCodeAdapter,
@@ -8,7 +8,7 @@ const exit_conditions_cjs_1 = require("../exit-conditions.cjs");
8
8
  * Headless OpenAI Codex CLI adapter — the Codex sibling of claude.cts's
9
9
  * `runClaudeCodeAdapter`, implementing the same AgentAdapter interface
10
10
  * (adapters/types.cts) so session.cts can pick either one at runtime via
11
- * FLOTILLA_AGENT (config.cts's `agentType`). Structurally this mirrors
11
+ * NAVARCH_AGENT (config.cts's `agentType`). Structurally this mirrors
12
12
  * claude.cts exactly: same host-vs-docker-exec split, same
13
13
  * timeout/AbortSignal handling, same "attach best-effort usage onto the raw
14
14
  * result" shape — only the CLI invocation and output parsing differ.
@@ -41,14 +41,14 @@ const exit_conditions_cjs_1 = require("../exit-conditions.cjs");
41
41
  * - Sandboxing/approvals: a real `codex exec` may prompt for
42
42
  * approval/sandbox-escalation on some actions by default; because this
43
43
  * runtime already isolates the session in its own Docker container (or,
44
- * in `FLOTILLA_SANDBOX_MODE=host`, trusts the host), the intent is to pass
44
+ * in `NAVARCH_SANDBOX_MODE=host`, trusts the host), the intent is to pass
45
45
  * whatever flag disables Codex's own approval gate for a fully
46
46
  * non-interactive run (something like `--full-auto` or
47
47
  * `--dangerously-bypass-approvals-and-sandbox` in published Codex CLI
48
48
  * documentation) — deliberately NOT hardcoded here since getting an
49
49
  * unverified flag wrong could silently change sandboxing behavior; left to
50
- * be supplied via FLOTILLA_CODEX_EXTRA_ARGS until confirmed.
51
- * - `FLOTILLA_CODEX_EXTRA_ARGS` (`extraArgs`) wins over the default `--json`
50
+ * be supplied via NAVARCH_CODEX_EXTRA_ARGS until confirmed.
51
+ * - `NAVARCH_CODEX_EXTRA_ARGS` (`extraArgs`) wins over the default `--json`
52
52
  * exactly like the Claude adapter's `--output-format` opt-out, so an
53
53
  * operator can fall back to plain-text output (or add the real
54
54
  * approval-bypass flag) without an adapter code change.
@@ -160,7 +160,7 @@ async function runViaDocker(options, args) {
160
160
  function shellQuote(value) {
161
161
  return `'${value.replace(/'/g, `'\\''`)}'`;
162
162
  }
163
- /** The AgentAdapter (adapters/types.cts) wrapper session.cts selects via FLOTILLA_AGENT=codex. */
163
+ /** The AgentAdapter (adapters/types.cts) wrapper session.cts selects via NAVARCH_AGENT=codex. */
164
164
  exports.codexAdapter = {
165
165
  agentType: "codex",
166
166
  run: runCodexAdapter,
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "codexAdapter", { enumerable: true, get: function
10
10
  Object.defineProperty(exports, "runCodexAdapter", { enumerable: true, get: function () { return codex_cjs_1.runCodexAdapter; } });
11
11
  /**
12
12
  * Picks the AgentAdapter (adapters/types.cts) session.cts should run a
13
- * session with, keyed off config.cts's `agentType` (FLOTILLA_AGENT). This is
13
+ * session with, keyed off config.cts's `agentType` (NAVARCH_AGENT). This is
14
14
  * the one place agent-type branching happens outside config loading itself —
15
15
  * session.cts calls the returned adapter's `run()` uniformly regardless of
16
16
  * which one it got.
package/dist/api.cjs CHANGED
@@ -74,11 +74,11 @@ class NavarchApiClient {
74
74
  }
75
75
  /**
76
76
  * `POST /api/machines/connect` — "Connect an agent to a project"
77
- * (docs/flotilla/schema-design.md §7 "Agent connect"). CONFIRMED endpoint,
77
+ * (docs/navarch/schema-design.md §7 "Agent connect"). CONFIRMED endpoint,
78
78
  * the project-scoped sibling of {@link registerMachine}: redeems a
79
79
  * single-use, project-scoped enrollment token a project owner minted
80
80
  * (POST /api/projects/:id/enrollment-tokens) instead of the global
81
- * FLOTILLA_ENROLLMENT_SECRET. Unauthenticated like registerMachine — the
81
+ * NAVARCH_ENROLLMENT_SECRET. Unauthenticated like registerMachine — the
82
82
  * enrollment token in the body is the auth.
83
83
  */
84
84
  async connectMachine(req) {
package/dist/capacity.cjs CHANGED
@@ -9,7 +9,7 @@ function computeAvailableCapacity(maxSessions, activeSessions) {
9
9
  /**
10
10
  * Tracks which session (lease) ids currently occupy a slot on this machine so
11
11
  * the heartbeat loop reports accurate available_capacity and the claim loop
12
- * never over-claims beyond FLOTILLA_MAX_SESSIONS.
12
+ * never over-claims beyond NAVARCH_MAX_SESSIONS.
13
13
  */
14
14
  class CapacityTracker {
15
15
  maxSessions;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ClaimLoop = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
+ const api_cjs_1 = require("./api.cjs");
5
6
  const logger_cjs_1 = require("./logger.cjs");
6
7
  const log = (0, logger_cjs_1.createLogger)("claim");
7
8
  /**
@@ -57,7 +58,17 @@ class ClaimLoop {
57
58
  .finally(() => this.capacity.release(claimed.lease_id));
58
59
  }
59
60
  catch (err) {
60
- log.warn(`claim failed: ${String(err)}`);
61
+ // NavarchApiError's message is only the status line ("… failed with
62
+ // 500"); the control plane's actual error text lives in `.body`. Log it
63
+ // so a server-side claim failure is diagnosable from the runtime alone
64
+ // instead of an opaque bare status.
65
+ if (err instanceof api_cjs_1.NavarchApiError) {
66
+ const detail = typeof err.body === "string" ? err.body : JSON.stringify(err.body);
67
+ log.warn(`claim failed: ${err.message}${detail ? ` — ${detail}` : ""}`);
68
+ }
69
+ else {
70
+ log.warn(`claim failed: ${String(err)}`);
71
+ }
61
72
  }
62
73
  }
63
74
  }
package/dist/cli.cjs CHANGED
@@ -11,6 +11,24 @@ const session_cjs_1 = require("./session.cjs");
11
11
  const sandbox_cjs_1 = require("./sandbox.cjs");
12
12
  const logger_cjs_1 = require("./logger.cjs");
13
13
  const log = (0, logger_cjs_1.createLogger)("cli");
14
+ const PACKAGE_NAME = "@sagentlab/navarch-runtime";
15
+ /**
16
+ * How to tell the user to re-invoke this CLI, matching however THEY launched
17
+ * it. The bare `navarch-runtime` bin only exists on PATH after a global install
18
+ * (`npm i -g`) or `npm link`; the documented onboarding path is `npx
19
+ * @sagentlab/navarch-runtime …`, where the bare name is NOT on PATH. Printing
20
+ * `navarch-runtime start` to someone who ran us via npx sends them straight
21
+ * into `command not found`, so detect that case and echo the form that works.
22
+ */
23
+ function invocation(subcommand) {
24
+ const scriptPath = process.argv[1] ?? "";
25
+ const viaNpx = scriptPath.includes("/_npx/") ||
26
+ scriptPath.includes("\\_npx\\") ||
27
+ process.env.npm_command === "exec" ||
28
+ process.env.npm_lifecycle_event === "npx";
29
+ const prefix = viaNpx ? `npx ${PACKAGE_NAME}` : "navarch-runtime";
30
+ return `${prefix} ${subcommand}`;
31
+ }
14
32
  function parseArgs(argv) {
15
33
  const [command, ...rest] = argv;
16
34
  const flags = {};
@@ -38,13 +56,13 @@ function parseArgs(argv) {
38
56
  async function registerCommand(flags) {
39
57
  const config = (0, config_cjs_1.loadRuntimeConfig)();
40
58
  const apiBase = flags["api-base"] ?? config.apiBase;
41
- const enrollmentToken = flags.token ?? process.env.FLOTILLA_ENROLLMENT_TOKEN;
42
- const name = flags.name ?? process.env.FLOTILLA_MACHINE_NAME;
59
+ const enrollmentToken = flags.token ?? process.env.NAVARCH_ENROLLMENT_TOKEN;
60
+ const name = flags.name ?? process.env.NAVARCH_MACHINE_NAME;
43
61
  if (!enrollmentToken) {
44
- throw new Error("Missing --token (or FLOTILLA_ENROLLMENT_TOKEN) — get one from the admin console.");
62
+ throw new Error("Missing --token (or NAVARCH_ENROLLMENT_TOKEN) — get one from the admin console.");
45
63
  }
46
64
  if (!name) {
47
- throw new Error("Missing --name (or FLOTILLA_MACHINE_NAME) for this machine.");
65
+ throw new Error("Missing --name (or NAVARCH_MACHINE_NAME) for this machine.");
48
66
  }
49
67
  const maxSessions = Number(flags["max-sessions"] ?? config.maxSessions);
50
68
  const capabilities = (flags.capabilities ?? config.capabilities.join(","))
@@ -70,28 +88,28 @@ async function registerCommand(flags) {
70
88
  console.log("Machine registered.");
71
89
  console.log(` machine_id: ${result.machine_id}`);
72
90
  console.log(` token: ${result.token}`);
73
- console.log(`\nStored in ${config.configDir}/machine.json (mode 0600). Run \`navarch-runtime start\` to begin serving tasks.`);
91
+ console.log(`\nStored in ${config.configDir}/machine.json (mode 0600). Run \`${invocation("start")}\` to begin serving tasks.`);
74
92
  }
75
93
  /**
76
94
  * `navarch-runtime connect` — "Connect an agent to a project"
77
- * (docs/flotilla/schema-design.md §7 "Agent connect"; self-hosted-runner
95
+ * (docs/navarch/schema-design.md §7 "Agent connect"; self-hosted-runner
78
96
  * style). The project-scoped sibling of `register`: redeems a short-lived,
79
97
  * single-use enrollment token a project owner minted from the platform UI
80
98
  * (ConnectAgentPanel → POST /api/projects/:id/enrollment-tokens) instead of
81
- * the global FLOTILLA_ENROLLMENT_SECRET `register` needs. Prints the
99
+ * the global NAVARCH_ENROLLMENT_SECRET `register` needs. Prints the
82
100
  * machine token exactly once, same discipline as `register`.
83
101
  */
84
102
  async function connectCommand(flags) {
85
103
  const config = (0, config_cjs_1.loadRuntimeConfig)();
86
104
  const apiBase = flags["api-base"] ?? config.apiBase;
87
- const enrollmentToken = flags.token ?? process.env.FLOTILLA_ENROLLMENT_TOKEN;
88
- const name = flags.name ?? process.env.FLOTILLA_MACHINE_NAME;
89
- const projectId = flags.project ?? process.env.FLOTILLA_PROJECT_ID;
105
+ const enrollmentToken = flags.token ?? process.env.NAVARCH_ENROLLMENT_TOKEN;
106
+ const name = flags.name ?? process.env.NAVARCH_MACHINE_NAME;
107
+ const projectId = flags.project ?? process.env.NAVARCH_PROJECT_ID;
90
108
  if (!enrollmentToken) {
91
- throw new Error("Missing --token (or FLOTILLA_ENROLLMENT_TOKEN) — get one from a project owner's \"Connect an agent\" panel.");
109
+ throw new Error("Missing --token (or NAVARCH_ENROLLMENT_TOKEN) — get one from a project owner's \"Connect an agent\" panel.");
92
110
  }
93
111
  if (!name) {
94
- throw new Error("Missing --name (or FLOTILLA_MACHINE_NAME) for this machine.");
112
+ throw new Error("Missing --name (or NAVARCH_MACHINE_NAME) for this machine.");
95
113
  }
96
114
  const maxSessions = Number(flags["max-sessions"] ?? config.maxSessions);
97
115
  const capabilities = (flags.capabilities ?? config.capabilities.join(","))
@@ -116,7 +134,7 @@ async function connectCommand(flags) {
116
134
  console.log("Machine connected.");
117
135
  console.log(` machine_id: ${result.machine_id}`);
118
136
  console.log(` token: ${result.token}`);
119
- console.log(`\nStored in ${config.configDir}/machine.json (mode 0600). Run \`navarch-runtime start\` to begin serving tasks.`);
137
+ console.log(`\nStored in ${config.configDir}/machine.json (mode 0600). Run \`${invocation("start")}\` to begin serving tasks.`);
120
138
  }
121
139
  async function startCommand() {
122
140
  const config = (0, config_cjs_1.loadRuntimeConfig)();
@@ -152,7 +170,7 @@ async function doctorCommand() {
152
170
  console.log(`machine: ${identity.name} (${identity.machine_id})`);
153
171
  }
154
172
  catch {
155
- console.log("machine: not registered — run `navarch-runtime register`");
173
+ console.log(`machine: not registered — run \`${invocation("register")}\``);
156
174
  }
157
175
  }
158
176
  function helpText() {
@@ -166,7 +184,7 @@ Usage:
166
184
  navarch-runtime start
167
185
  navarch-runtime doctor
168
186
 
169
- Configuration is via FLOTILLA_* environment variables; see runtime/README.md.
187
+ Configuration is via NAVARCH_* environment variables; see runtime/README.md.
170
188
  `;
171
189
  }
172
190
  async function main(argv = process.argv.slice(2)) {
package/dist/config.cjs CHANGED
@@ -23,33 +23,33 @@ function envList(env, name, fallback) {
23
23
  .filter(Boolean);
24
24
  }
25
25
  /**
26
- * Loads runtime config from FLOTILLA_* env vars, with sane defaults for a
26
+ * Loads runtime config from NAVARCH_* env vars, with sane defaults for a
27
27
  * fresh machine. Accepts an explicit env map (defaulting to process.env) so
28
28
  * it is trivially unit-testable without mutating global state.
29
29
  */
30
30
  function loadRuntimeConfig(env = process.env) {
31
- const configDir = env.FLOTILLA_CONFIG_DIR ?? node_path_1.default.join(node_os_1.default.homedir(), ".flotilla");
32
- const leaseHeartbeatIntervalMs = envInt(env, "FLOTILLA_LEASE_HEARTBEAT_INTERVAL_MS", 5 * 60 * 1000);
31
+ const configDir = env.NAVARCH_CONFIG_DIR ?? node_path_1.default.join(node_os_1.default.homedir(), ".navarch");
32
+ const leaseHeartbeatIntervalMs = envInt(env, "NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS", 5 * 60 * 1000);
33
33
  return {
34
- apiBase: env.FLOTILLA_API_BASE ?? "http://localhost:3000",
35
- workspaceRoot: env.FLOTILLA_WORKSPACE_ROOT ?? node_path_1.default.join(configDir, "sandboxes"),
34
+ apiBase: env.NAVARCH_API_BASE ?? "http://localhost:3000",
35
+ workspaceRoot: env.NAVARCH_WORKSPACE_ROOT ?? node_path_1.default.join(configDir, "sandboxes"),
36
36
  configDir,
37
- maxSessions: envInt(env, "FLOTILLA_MAX_SESSIONS", 3),
38
- capabilities: envList(env, "FLOTILLA_CAPABILITIES", ["docker-sandbox", "shell"]),
39
- ownerZone: env.FLOTILLA_OWNER_ZONE ?? "sagentlab",
40
- pollIntervalMs: envInt(env, "FLOTILLA_POLL_INTERVAL_MS", 5000),
41
- machineHeartbeatIntervalMs: envInt(env, "FLOTILLA_HEARTBEAT_INTERVAL_MS", 60_000),
37
+ maxSessions: envInt(env, "NAVARCH_MAX_SESSIONS", 3),
38
+ capabilities: envList(env, "NAVARCH_CAPABILITIES", ["docker-sandbox", "shell"]),
39
+ ownerZone: env.NAVARCH_OWNER_ZONE ?? "sagentlab",
40
+ pollIntervalMs: envInt(env, "NAVARCH_POLL_INTERVAL_MS", 5000),
41
+ machineHeartbeatIntervalMs: envInt(env, "NAVARCH_HEARTBEAT_INTERVAL_MS", 60_000),
42
42
  // leases.expires_at = claimed_at + 15 min (schema-design.md §4) — default renewal
43
43
  // interval must stay comfortably under that TTL.
44
44
  leaseHeartbeatIntervalMs,
45
- sessionTimeoutMs: envInt(env, "FLOTILLA_SESSION_TIMEOUT_MS", 45 * 60 * 1000),
46
- agentType: env.FLOTILLA_AGENT === "codex" ? "codex" : "claude-code",
47
- claudeBin: env.FLOTILLA_CLAUDE_BIN ?? "claude",
48
- claudeExtraArgs: envList(env, "FLOTILLA_CLAUDE_EXTRA_ARGS", []),
49
- codexBin: env.FLOTILLA_CODEX_BIN ?? "codex",
50
- codexExtraArgs: envList(env, "FLOTILLA_CODEX_EXTRA_ARGS", []),
51
- mcpConfigPath: env.FLOTILLA_MCP_CONFIG_PATH ?? null,
52
- sandboxMode: env.FLOTILLA_SANDBOX_MODE === "host" ? "host" : "docker",
53
- dockerImage: env.FLOTILLA_DOCKER_IMAGE ?? "node:20-slim",
45
+ sessionTimeoutMs: envInt(env, "NAVARCH_SESSION_TIMEOUT_MS", 45 * 60 * 1000),
46
+ agentType: env.NAVARCH_AGENT === "codex" ? "codex" : "claude-code",
47
+ claudeBin: env.NAVARCH_CLAUDE_BIN ?? "claude",
48
+ claudeExtraArgs: envList(env, "NAVARCH_CLAUDE_EXTRA_ARGS", []),
49
+ codexBin: env.NAVARCH_CODEX_BIN ?? "codex",
50
+ codexExtraArgs: envList(env, "NAVARCH_CODEX_EXTRA_ARGS", []),
51
+ mcpConfigPath: env.NAVARCH_MCP_CONFIG_PATH ?? null,
52
+ sandboxMode: env.NAVARCH_SANDBOX_MODE === "host" ? "host" : "docker",
53
+ dockerImage: env.NAVARCH_DOCKER_IMAGE ?? "node:20-slim",
54
54
  };
55
55
  }
@@ -40,19 +40,19 @@ async function loadMachineIdentity(configDir) {
40
40
  * per-task broker-issued secrets, handled entirely in session.cts/sandbox.cts.
41
41
  */
42
42
  async function resolveMachineIdentity(configDir, apiBaseFallback) {
43
- const envToken = process.env.FLOTILLA_MACHINE_TOKEN;
44
- const envId = process.env.FLOTILLA_MACHINE_ID;
43
+ const envToken = process.env.NAVARCH_MACHINE_TOKEN;
44
+ const envId = process.env.NAVARCH_MACHINE_ID;
45
45
  if (envToken && envId) {
46
46
  return {
47
47
  machine_id: envId,
48
48
  token: envToken,
49
- name: process.env.FLOTILLA_MACHINE_NAME ?? envId,
50
- api_base: process.env.FLOTILLA_API_BASE ?? apiBaseFallback,
49
+ name: process.env.NAVARCH_MACHINE_NAME ?? envId,
50
+ api_base: process.env.NAVARCH_API_BASE ?? apiBaseFallback,
51
51
  };
52
52
  }
53
53
  const stored = await loadMachineIdentity(configDir);
54
54
  if (stored)
55
55
  return stored;
56
56
  throw new Error("No machine identity found. Run `navarch-runtime register` first, or set " +
57
- "FLOTILLA_MACHINE_TOKEN + FLOTILLA_MACHINE_ID for a twelve-factor deployment.");
57
+ "NAVARCH_MACHINE_TOKEN + NAVARCH_MACHINE_ID for a twelve-factor deployment.");
58
58
  }
@@ -5,27 +5,27 @@
5
5
  // platform MCP server".
6
6
  //
7
7
  // Previously `mcpConfigPath` was just a static, operator-supplied path
8
- // (config.cts's `FLOTILLA_MCP_CONFIG_PATH`) with nothing that ever wrote a
8
+ // (config.cts's `NAVARCH_MCP_CONFIG_PATH`) with nothing that ever wrote a
9
9
  // real config file -- there was no way for a session to actually reach the
10
10
  // platform MCP server with the auth it needs. This module is the fix: one
11
11
  // config, generated fresh per session, carrying the machine's bearer token
12
12
  // and this session's lease id (the same two-layer auth app/api/mcp/route.ts
13
- // requires -- see lib/flotilla/mcp/context.ts).
13
+ // requires -- see lib/navarch/mcp/context.ts).
14
14
  //
15
15
  // Pure and side-effect free (no filesystem access) so it's trivially unit
16
16
  // testable; session.cts is the only caller that writes the result to disk.
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.FLOTILLA_LEASE_HEADER = void 0;
18
+ exports.NAVARCH_LEASE_HEADER = void 0;
19
19
  exports.buildNavarchMcpConfig = buildNavarchMcpConfig;
20
- /** The header app/api/mcp/route.ts's lib/flotilla/mcp/context.ts reads to identify which lease is calling. */
21
- exports.FLOTILLA_LEASE_HEADER = "X-Navarch-Lease-Id";
20
+ /** The header app/api/mcp/route.ts's lib/navarch/mcp/context.ts reads to identify which lease is calling. */
21
+ exports.NAVARCH_LEASE_HEADER = "X-Navarch-Lease-Id";
22
22
  /**
23
23
  * Builds the `.mcp.json`-shaped config object Claude Code's `--mcp-config`
24
24
  * flag expects: a remote "http" (streamable HTTP) server entry with the
25
25
  * bearer token and lease id as request headers.
26
26
  */
27
27
  function buildNavarchMcpConfig(opts) {
28
- const serverName = opts.serverName ?? "flotilla";
28
+ const serverName = opts.serverName ?? "navarch";
29
29
  return {
30
30
  mcpServers: {
31
31
  [serverName]: {
@@ -33,7 +33,7 @@ function buildNavarchMcpConfig(opts) {
33
33
  url: `${opts.apiBase.replace(/\/+$/, "")}/api/mcp`,
34
34
  headers: {
35
35
  Authorization: `Bearer ${opts.machineToken}`,
36
- [exports.FLOTILLA_LEASE_HEADER]: opts.leaseId,
36
+ [exports.NAVARCH_LEASE_HEADER]: opts.leaseId,
37
37
  },
38
38
  },
39
39
  },
package/dist/prompt.cjs CHANGED
@@ -9,6 +9,11 @@ exports.renderPrompt = renderPrompt;
9
9
  * WP-07 "write prompt file".
10
10
  */
11
11
  function renderPrompt(task, bundle) {
12
+ if (bundle.task_context) {
13
+ return [bundle.task_context.trim(), bundle.retry_context?.trim()]
14
+ .filter((section) => Boolean(section))
15
+ .join("\n\n") + "\n";
16
+ }
12
17
  const sections = [];
13
18
  sections.push(`# Task: ${task.summary}`);
14
19
  sections.push(`Type: ${task.task_type} | Repo: ${task.repo} | Environment: ${task.environment_scope}`);
package/dist/sandbox.cjs CHANGED
@@ -69,7 +69,7 @@ async function isDockerAvailable(runner = exports.nodeCommandRunner) {
69
69
  }
70
70
  }
71
71
  function containerName(sessionId) {
72
- return `flotilla-${sessionId}`;
72
+ return `navarch-${sessionId}`;
73
73
  }
74
74
  /**
75
75
  * One Docker container per session (implementation-plan.md WP-07 "Sandbox:
package/dist/session.cjs CHANGED
@@ -23,7 +23,7 @@ const log = (0, logger_cjs_1.createLogger)("session");
23
23
  * 2. fetch secrets from the broker once, at session start
24
24
  * 3. stand up a Docker sandbox, clone the repo, inject env vars
25
25
  * 4. run the configured agent adapter (Claude Code or Codex, per
26
- * FLOTILLA_AGENT — adapters/index.cts#selectAdapter), heartbeating the
26
+ * NAVARCH_AGENT — adapters/index.cts#selectAdapter), heartbeating the
27
27
  * lease throughout
28
28
  * 5. redact + upload the transcript, map the exit condition, complete the
29
29
  * lease (recording which agent_type ran it)
@@ -87,8 +87,8 @@ async function runSession(deps, claimed, sessionId) {
87
87
  // Platform MCP config (implementation-plan.md WP-07: "--mcp-config
88
88
  // pointing at the platform MCP server"): generated fresh per session,
89
89
  // carrying this machine's bearer token and this session's lease id (the
90
- // auth app/api/mcp/route.ts requires -- see lib/flotilla/mcp/context.ts),
91
- // unless the operator pinned a static override via FLOTILLA_MCP_CONFIG_PATH
90
+ // auth app/api/mcp/route.ts requires -- see lib/navarch/mcp/context.ts),
91
+ // unless the operator pinned a static override via NAVARCH_MCP_CONFIG_PATH
92
92
  // (e.g. pointing at a fake MCP server in local testing). Written to
93
93
  // workDir (host path) so it also lands at /workspace/mcp-config.json
94
94
  // inside the Docker sandbox (sandbox.cts binds workDir at /workspace) --
@@ -110,7 +110,7 @@ async function runSession(deps, claimed, sessionId) {
110
110
  await sandbox.injectEnv(toEnvMap(secrets));
111
111
  await sandbox.cloneRepo(task.repo, Boolean(secrets["github-pat"]));
112
112
  }
113
- // Picks the Claude Code or Codex adapter per FLOTILLA_AGENT
113
+ // Picks the Claude Code or Codex adapter per NAVARCH_AGENT
114
114
  // (config.cts's `agentType`) — see adapters/index.cts#selectAdapter.
115
115
  // Both adapters implement the same AgentAdapter.run() shape
116
116
  // (adapters/types.cts), so nothing else in this function branches on
package/dist/types.cjs CHANGED
@@ -2,8 +2,8 @@
2
2
  // Navarch runtime — shared wire types.
3
3
  //
4
4
  // These interfaces mirror the JSON shapes described in:
5
- // - docs/flotilla/schema-design.md (table columns => API field names, esp. §4, §5, §7)
6
- // - docs/flotilla/implementation-plan.md (WP-07 behavioral contract)
5
+ // - docs/navarch/schema-design.md (table columns => API field names, esp. §4, §5, §7)
6
+ // - docs/navarch/implementation-plan.md (WP-07 behavioral contract)
7
7
  // - docs/agent-platform-project-plan.md (§3.8 dispatch/§3.9 adapter contract)
8
8
  //
9
9
  // The control-plane API is being built in parallel (WP-01/WP-04/WP-05) in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagentlab/navarch-runtime",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Navarch machine-side session manager: registers a machine, claims tasks from the control-plane dispatcher, runs them in a Docker sandbox via the Claude Code or Codex adapter, and reports results back.",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
@@ -45,6 +45,6 @@
45
45
  "devDependencies": {
46
46
  "@types/node": "^20.14.0",
47
47
  "typescript": "^5.7.2",
48
- "vitest": "^2.1.8"
48
+ "vitest": "^4.1.10"
49
49
  }
50
50
  }