@sagentlab/navarch-runtime 0.1.34 → 0.1.36
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 +9 -0
- package/dist/adapters/pi.cjs +61 -15
- package/dist/cli.cjs +6 -1
- package/dist/config.cjs +1 -0
- package/dist/heartbeat-loop.cjs +3 -0
- package/dist/supervisor.cjs +6 -0
- package/dist/update-coordinator.cjs +31 -1
- package/dist/usage-limits.cjs +12 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -233,6 +233,15 @@ accepted. Rollouts are assigned by the machine's server-managed `stable` or
|
|
|
233
233
|
`canary` channel and deterministic rollout percentage. Set
|
|
234
234
|
`NAVARCH_AUTO_UPDATE=off` to keep telemetry but disable automatic activation.
|
|
235
235
|
|
|
236
|
+
Project owners can also request a same-version restart from the project Agents
|
|
237
|
+
page. Only a worker running under `navarch-runtime supervise` advertises this
|
|
238
|
+
capability. The restart command arrives on the normal machine heartbeat; the
|
|
239
|
+
worker stops claiming, reports zero capacity, drains active sessions, and exits
|
|
240
|
+
with a restart-only handoff code. The supervisor respawns the current verified
|
|
241
|
+
binary without staging an update. The control plane acknowledges the one-shot
|
|
242
|
+
command only after a heartbeat reports a different boot ID, preventing a stale
|
|
243
|
+
command from causing a restart loop.
|
|
244
|
+
|
|
236
245
|
Plain `navarch-runtime start` continues to work and reports available releases,
|
|
237
246
|
but deliberately does not activate them because no parent process would exist
|
|
238
247
|
to perform a safe handoff or rollback.
|
package/dist/adapters/pi.cjs
CHANGED
|
@@ -3,7 +3,8 @@ 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.piAdapter = exports.DEFAULT_HOSTED_PI_MODEL = exports.HOSTED_PI_MODELS = exports.DEEPSEEK_API_KEY_ENV = exports.PI_PROVIDER_NAME = void 0;
|
|
6
|
+
exports.piAdapter = exports.HOSTED_PI_MAX_PROVIDER_TURNS = exports.DEFAULT_HOSTED_PI_MODEL = exports.HOSTED_PI_MODELS = exports.DEEPSEEK_API_KEY_ENV = exports.PI_PROVIDER_NAME = void 0;
|
|
7
|
+
exports.buildHostedPiBudgetExtension = buildHostedPiBudgetExtension;
|
|
7
8
|
exports.resolveHostedPiModel = resolveHostedPiModel;
|
|
8
9
|
exports.buildPiProviderConfig = buildPiProviderConfig;
|
|
9
10
|
exports.parsePiJsonEvents = parsePiJsonEvents;
|
|
@@ -27,9 +28,9 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
27
28
|
* shared parsing/provider-config contract.
|
|
28
29
|
*
|
|
29
30
|
* Secret hygiene: the generated models.json references the DeepSeek key ONLY
|
|
30
|
-
* as
|
|
31
|
-
* child environment at
|
|
32
|
-
* the config file, or in any file that survives cleanup. A missing
|
|
31
|
+
* as the `DEEPSEEK_API_KEY` environment-variable name (Pi resolves it from
|
|
32
|
+
* the child environment at request time). The literal key never appears in
|
|
33
|
+
* argv, in the config file, or in any file that survives cleanup. A missing
|
|
33
34
|
* DEEPSEEK_API_KEY in the session environment is a hard configuration
|
|
34
35
|
* failure — the adapter refuses to launch rather than fall back to an
|
|
35
36
|
* ambient credential.
|
|
@@ -47,6 +48,17 @@ exports.PI_PROVIDER_NAME = "deepseek";
|
|
|
47
48
|
exports.DEEPSEEK_API_KEY_ENV = "DEEPSEEK_API_KEY";
|
|
48
49
|
exports.HOSTED_PI_MODELS = ["deepseek-v4-flash", "deepseek-v4-pro"];
|
|
49
50
|
exports.DEFAULT_HOSTED_PI_MODEL = "deepseek-v4-flash";
|
|
51
|
+
exports.HOSTED_PI_MAX_PROVIDER_TURNS = 3;
|
|
52
|
+
function buildHostedPiBudgetExtension() {
|
|
53
|
+
return `export default function navarchHostedPiBudget(pi) {
|
|
54
|
+
let providerTurns = 0;
|
|
55
|
+
pi.on("turn_start", (_event, ctx) => {
|
|
56
|
+
providerTurns += 1;
|
|
57
|
+
if (providerTurns > ${exports.HOSTED_PI_MAX_PROVIDER_TURNS}) ctx.abort();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
50
62
|
function resolveHostedPiModel(configured) {
|
|
51
63
|
const trimmed = (configured ?? "").trim();
|
|
52
64
|
const bare = trimmed.startsWith(`${exports.PI_PROVIDER_NAME}/`)
|
|
@@ -88,7 +100,7 @@ function buildPiProviderConfig() {
|
|
|
88
100
|
[exports.PI_PROVIDER_NAME]: {
|
|
89
101
|
baseUrl: "https://api.deepseek.com",
|
|
90
102
|
api: "openai-completions",
|
|
91
|
-
apiKey:
|
|
103
|
+
apiKey: exports.DEEPSEEK_API_KEY_ENV,
|
|
92
104
|
models,
|
|
93
105
|
},
|
|
94
106
|
},
|
|
@@ -195,6 +207,13 @@ function attachPiOutput(result) {
|
|
|
195
207
|
// Invocation
|
|
196
208
|
// ---------------------------------------------------------------------------
|
|
197
209
|
const CONTAINER_PI_DIR = "/tmp/navarch-pi-agent";
|
|
210
|
+
const BUDGET_EXTENSION_NAME = "navarch-budget.js";
|
|
211
|
+
function withBudgetExtension(args, extensionPath) {
|
|
212
|
+
const promptIndex = args.indexOf("-p");
|
|
213
|
+
if (promptIndex < 0)
|
|
214
|
+
return [...args, "--extension", extensionPath];
|
|
215
|
+
return [...args.slice(0, promptIndex), "--extension", extensionPath, ...args.slice(promptIndex)];
|
|
216
|
+
}
|
|
198
217
|
function hasArg(args, ...flags) {
|
|
199
218
|
return args.some((arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
200
219
|
}
|
|
@@ -205,7 +224,7 @@ async function runPiAdapter(options) {
|
|
|
205
224
|
throw new Error(`pi adapter: ${exports.DEEPSEEK_API_KEY_ENV} is missing from the session environment; ` +
|
|
206
225
|
"the managed provider credential must be injected by the hosted runner");
|
|
207
226
|
}
|
|
208
|
-
const args = ["--mode", "json"];
|
|
227
|
+
const args = ["--mode", "json", "--no-extensions"];
|
|
209
228
|
if (!hasArg(options.extraArgs, "--no-session")) {
|
|
210
229
|
// Ephemeral by default: session persistence would leave conversation
|
|
211
230
|
// files (which may quote repository content) behind after cleanup.
|
|
@@ -215,9 +234,9 @@ async function runPiAdapter(options) {
|
|
|
215
234
|
const model = resolveHostedPiModel(options.model);
|
|
216
235
|
args.push("--model", `${exports.PI_PROVIDER_NAME}/${model}`);
|
|
217
236
|
if (options.reasoningEffort) {
|
|
218
|
-
// Pi
|
|
219
|
-
//
|
|
220
|
-
args.push("--thinking", options.reasoningEffort);
|
|
237
|
+
// Pi 0.73.1 tops out at xhigh. Navarch's max effort must clamp rather
|
|
238
|
+
// than passing an invalid value that Pi warns about and ignores.
|
|
239
|
+
args.push("--thinking", options.reasoningEffort === "max" ? "xhigh" : options.reasoningEffort);
|
|
221
240
|
}
|
|
222
241
|
args.push("-p", options.prompt);
|
|
223
242
|
const raw = options.dockerExec
|
|
@@ -230,14 +249,19 @@ async function runOnHost(options, args) {
|
|
|
230
249
|
// reads) an operator's ~/.pi configuration/credentials.
|
|
231
250
|
const agentDir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), "navarch-pi-agent-"));
|
|
232
251
|
await node_fs_1.promises.writeFile(node_path_1.default.join(agentDir, "models.json"), buildPiProviderConfig(), { mode: 0o600 });
|
|
252
|
+
const budgetExtensionPath = node_path_1.default.join(agentDir, BUDGET_EXTENSION_NAME);
|
|
253
|
+
await node_fs_1.promises.writeFile(budgetExtensionPath, buildHostedPiBudgetExtension(), {
|
|
254
|
+
mode: 0o600,
|
|
255
|
+
});
|
|
233
256
|
const env = { ...options.env, PI_CODING_AGENT_DIR: agentDir };
|
|
257
|
+
const boundedArgs = withBudgetExtension(args, budgetExtensionPath);
|
|
234
258
|
try {
|
|
235
259
|
return await new Promise((resolve) => {
|
|
236
260
|
let stdout = "";
|
|
237
261
|
let stderr = "";
|
|
238
262
|
let timedOut = false;
|
|
239
263
|
let killedByLeaseLoss = false;
|
|
240
|
-
const child = (0, node_child_process_1.spawn)(options.bin,
|
|
264
|
+
const child = (0, node_child_process_1.spawn)(options.bin, boundedArgs, {
|
|
241
265
|
cwd: options.cwd,
|
|
242
266
|
env: { ...process.env, ...env },
|
|
243
267
|
});
|
|
@@ -261,12 +285,24 @@ async function runOnHost(options, args) {
|
|
|
261
285
|
clearTimeout(timer);
|
|
262
286
|
options.signal?.removeEventListener("abort", onAbort);
|
|
263
287
|
stderr += `\n${String(err)}`;
|
|
264
|
-
resolve({
|
|
288
|
+
resolve({
|
|
289
|
+
exitCode: null,
|
|
290
|
+
timedOut,
|
|
291
|
+
killedByLeaseLoss,
|
|
292
|
+
stdout,
|
|
293
|
+
stderr,
|
|
294
|
+
});
|
|
265
295
|
});
|
|
266
296
|
child.on("close", (code) => {
|
|
267
297
|
clearTimeout(timer);
|
|
268
298
|
options.signal?.removeEventListener("abort", onAbort);
|
|
269
|
-
resolve({
|
|
299
|
+
resolve({
|
|
300
|
+
exitCode: code,
|
|
301
|
+
timedOut,
|
|
302
|
+
killedByLeaseLoss,
|
|
303
|
+
stdout,
|
|
304
|
+
stderr,
|
|
305
|
+
});
|
|
270
306
|
});
|
|
271
307
|
});
|
|
272
308
|
}
|
|
@@ -276,12 +312,19 @@ async function runOnHost(options, args) {
|
|
|
276
312
|
}
|
|
277
313
|
async function runViaDocker(options, args) {
|
|
278
314
|
const { containerName, runner } = options.dockerExec;
|
|
279
|
-
// The provider config carries no secret values (only the
|
|
315
|
+
// The provider config carries no secret values (only the DEEPSEEK_API_KEY
|
|
280
316
|
// env reference), so materializing it through the container shell is safe.
|
|
281
317
|
const command = `[ -f /tmp/session.env ] && . /tmp/session.env; cd repo 2>/dev/null; ` +
|
|
282
318
|
`mkdir -p ${CONTAINER_PI_DIR} && cat > ${CONTAINER_PI_DIR}/models.json <<'NAVARCH_PI_EOF'\n` +
|
|
283
319
|
`${buildPiProviderConfig()}NAVARCH_PI_EOF\n` +
|
|
284
|
-
`
|
|
320
|
+
`cat > ${CONTAINER_PI_DIR}/${BUDGET_EXTENSION_NAME} <<'NAVARCH_PI_BUDGET_EOF'\n` +
|
|
321
|
+
`${buildHostedPiBudgetExtension()}NAVARCH_PI_BUDGET_EOF\n` +
|
|
322
|
+
`PI_CODING_AGENT_DIR=${CONTAINER_PI_DIR} ${[
|
|
323
|
+
options.bin,
|
|
324
|
+
...withBudgetExtension(args, `${CONTAINER_PI_DIR}/${BUDGET_EXTENSION_NAME}`),
|
|
325
|
+
]
|
|
326
|
+
.map(shellQuote)
|
|
327
|
+
.join(" ")}`;
|
|
285
328
|
let killedByLeaseLoss = false;
|
|
286
329
|
const onAbort = () => {
|
|
287
330
|
killedByLeaseLoss = true;
|
|
@@ -290,7 +333,10 @@ async function runViaDocker(options, args) {
|
|
|
290
333
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
291
334
|
try {
|
|
292
335
|
const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
|
|
293
|
-
const result = await runner.run("docker", ["exec", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
336
|
+
const result = await runner.run("docker", ["exec", ...forwardedEnv, containerName, "sh", "-c", command], {
|
|
337
|
+
timeoutMs: options.timeoutMs,
|
|
338
|
+
env: { ...process.env, ...options.env },
|
|
339
|
+
});
|
|
294
340
|
return {
|
|
295
341
|
exitCode: result.code,
|
|
296
342
|
timedOut: false,
|
package/dist/cli.cjs
CHANGED
|
@@ -207,7 +207,12 @@ async function startCommand(flags) {
|
|
|
207
207
|
const bootId = (0, node_crypto_1.randomUUID)();
|
|
208
208
|
const updateCoordinatorRef = {};
|
|
209
209
|
let readySent = false;
|
|
210
|
-
const heartbeat = new heartbeat_loop_cjs_1.MachineHeartbeatLoop(api, identity.machine_id, config, capacity, bootId, (result) =>
|
|
210
|
+
const heartbeat = new heartbeat_loop_cjs_1.MachineHeartbeatLoop(api, identity.machine_id, config, capacity, bootId, (result) => {
|
|
211
|
+
if (result.restart)
|
|
212
|
+
updateCoordinatorRef.current?.considerRestart(result.restart);
|
|
213
|
+
else
|
|
214
|
+
updateCoordinatorRef.current?.consider(result.update);
|
|
215
|
+
}, () => {
|
|
211
216
|
if (!readySent && process.send) {
|
|
212
217
|
readySent = true;
|
|
213
218
|
process.send({ type: "navarch-ready", boot_id: bootId });
|
package/dist/config.cjs
CHANGED
|
@@ -103,6 +103,7 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
103
103
|
.map((s) => s.trim())
|
|
104
104
|
.filter(Boolean),
|
|
105
105
|
updateChannel: env.NAVARCH_UPDATE_CHANNEL === "canary" ? "canary" : "stable",
|
|
106
|
+
supervised: env.NAVARCH_SUPERVISED === "1",
|
|
106
107
|
autoUpdate: env.NAVARCH_SUPERVISED === "1" &&
|
|
107
108
|
!["off", "false", "0"].includes(env.NAVARCH_AUTO_UPDATE ?? ""),
|
|
108
109
|
// Reads only the quota numbers providers already write to disk, so it is on
|
package/dist/heartbeat-loop.cjs
CHANGED
|
@@ -70,6 +70,8 @@ class MachineHeartbeatLoop {
|
|
|
70
70
|
? await (0, usage_limits_cjs_1.collectAgentUsage)({
|
|
71
71
|
codexHome: this.config.codexHome,
|
|
72
72
|
claudeConfigPath: this.config.claudeConfigPath,
|
|
73
|
+
providers: [...new Set([this.config.agentType, ...(this.config.runtimes ?? [])])]
|
|
74
|
+
.filter((runtime) => runtime === "codex" || runtime === "claude-code"),
|
|
73
75
|
})
|
|
74
76
|
: null;
|
|
75
77
|
const result = await this.api.machineHeartbeat(this.machineId, {
|
|
@@ -82,6 +84,7 @@ class MachineHeartbeatLoop {
|
|
|
82
84
|
channel: this.config.updateChannel,
|
|
83
85
|
state: this.updateState,
|
|
84
86
|
boot_id: this.bootId,
|
|
87
|
+
supervised: this.config.supervised,
|
|
85
88
|
...(this.lastUpdateError ? { last_update_error: this.lastUpdateError } : {}),
|
|
86
89
|
},
|
|
87
90
|
});
|
package/dist/supervisor.cjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REMOTE_RESTART_EXIT_CODE = void 0;
|
|
3
4
|
exports.superviseRuntime = superviseRuntime;
|
|
4
5
|
const node_child_process_1 = require("node:child_process");
|
|
5
6
|
const logger_cjs_1 = require("./logger.cjs");
|
|
6
7
|
const update_installer_cjs_1 = require("./update-installer.cjs");
|
|
7
8
|
const log = (0, logger_cjs_1.createLogger)("supervisor");
|
|
8
9
|
const UPDATE_RESTART_EXIT_CODE = 75;
|
|
10
|
+
exports.REMOTE_RESTART_EXIT_CODE = 76;
|
|
9
11
|
function runWorker(binPath, args, healthTimeoutMs, workerEnv, setCurrentChild, onHealthy) {
|
|
10
12
|
return new Promise((resolve) => {
|
|
11
13
|
const child = (0, node_child_process_1.spawn)(process.execPath, [binPath, "start", ...args], {
|
|
@@ -126,6 +128,10 @@ async function superviseRuntime(configDir, initialBinPath, workerArgs = [], heal
|
|
|
126
128
|
await (0, update_installer_cjs_1.clearPendingUpdate)(configDir);
|
|
127
129
|
continue;
|
|
128
130
|
}
|
|
131
|
+
if (result.code === exports.REMOTE_RESTART_EXIT_CODE) {
|
|
132
|
+
log.info("remote restart requested; respawning current runtime");
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
129
135
|
if (result.code !== UPDATE_RESTART_EXIT_CODE)
|
|
130
136
|
return result.code;
|
|
131
137
|
const pending = await (0, update_installer_cjs_1.readPendingUpdate)(configDir);
|
|
@@ -4,16 +4,18 @@ exports.RuntimeUpdateCoordinator = void 0;
|
|
|
4
4
|
const logger_cjs_1 = require("./logger.cjs");
|
|
5
5
|
const update_installer_cjs_1 = require("./update-installer.cjs");
|
|
6
6
|
const version_cjs_1 = require("./version.cjs");
|
|
7
|
+
const supervisor_cjs_1 = require("./supervisor.cjs");
|
|
7
8
|
const log = (0, logger_cjs_1.createLogger)("update");
|
|
8
9
|
class RuntimeUpdateCoordinator {
|
|
9
10
|
options;
|
|
10
11
|
targetInProgress = null;
|
|
12
|
+
restartInProgress = null;
|
|
11
13
|
warnedUnsupervised = false;
|
|
12
14
|
constructor(options) {
|
|
13
15
|
this.options = options;
|
|
14
16
|
}
|
|
15
17
|
consider(directive) {
|
|
16
|
-
if (!directive || directive.target_version === version_cjs_1.RUNTIME_VERSION)
|
|
18
|
+
if (!directive || directive.target_version === version_cjs_1.RUNTIME_VERSION || this.restartInProgress)
|
|
17
19
|
return;
|
|
18
20
|
if (!this.options.config.autoUpdate) {
|
|
19
21
|
if (!this.warnedUnsupervised) {
|
|
@@ -27,6 +29,34 @@ class RuntimeUpdateCoordinator {
|
|
|
27
29
|
this.targetInProgress = directive.target_version;
|
|
28
30
|
void this.apply(directive);
|
|
29
31
|
}
|
|
32
|
+
considerRestart(directive) {
|
|
33
|
+
if (!directive || this.targetInProgress || this.restartInProgress)
|
|
34
|
+
return;
|
|
35
|
+
if (!this.options.config.supervised) {
|
|
36
|
+
log.warn(`ignoring remote restart ${directive.command_id}: runtime is not supervised`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.restartInProgress = directive.command_id;
|
|
40
|
+
void this.applyRestart(directive);
|
|
41
|
+
}
|
|
42
|
+
async applyRestart(directive) {
|
|
43
|
+
const { heartbeat, claimLoop, capacity } = this.options;
|
|
44
|
+
try {
|
|
45
|
+
heartbeat.setUpdateState("draining");
|
|
46
|
+
log.info(`remote restart ${directive.command_id} received; draining active sessions`);
|
|
47
|
+
await claimLoop.drain();
|
|
48
|
+
await capacity.waitForIdle();
|
|
49
|
+
log.info(`remote restart ${directive.command_id} ready; handing off to supervisor`);
|
|
50
|
+
(this.options.requestRestart ?? ((code) => process.exit(code)))(supervisor_cjs_1.REMOTE_RESTART_EXIT_CODE);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
+
log.error(`remote restart failed: ${message}`);
|
|
55
|
+
heartbeat.setUpdateState("failed", message.slice(0, 500));
|
|
56
|
+
claimLoop.start();
|
|
57
|
+
this.restartInProgress = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
30
60
|
async apply(directive) {
|
|
31
61
|
const { config, heartbeat, claimLoop, capacity } = this.options;
|
|
32
62
|
try {
|
package/dist/usage-limits.cjs
CHANGED
|
@@ -359,7 +359,9 @@ async function collectClaudeUsage(options) {
|
|
|
359
359
|
}
|
|
360
360
|
const DEFAULT_COLLECT_TIMEOUT_MS = 5_000;
|
|
361
361
|
/**
|
|
362
|
-
* Gathers
|
|
362
|
+
* Gathers reports for the providers this runtime actually advertises. Provider
|
|
363
|
+
* CLIs can share a host, so scanning every state directory would attribute a
|
|
364
|
+
* different agent's quota to this machine. Selected providers are still read
|
|
363
365
|
* independently so one unreadable or malformed source cannot suppress another,
|
|
364
366
|
* and any thrown error degrades to "no report" rather than failing the caller.
|
|
365
367
|
*/
|
|
@@ -372,10 +374,15 @@ async function collectAgentUsage(options) {
|
|
|
372
374
|
timer.unref?.();
|
|
373
375
|
});
|
|
374
376
|
try {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
const providers = new Set(options.providers ?? ["codex", "claude-code"]);
|
|
378
|
+
const collectors = [];
|
|
379
|
+
if (providers.has("codex")) {
|
|
380
|
+
collectors.push(collectCodexUsage({ codexHome: options.codexHome, now: options.now }));
|
|
381
|
+
}
|
|
382
|
+
if (providers.has("claude-code")) {
|
|
383
|
+
collectors.push(collectClaudeUsage({ configPath: options.claudeConfigPath, now: options.now }));
|
|
384
|
+
}
|
|
385
|
+
const collection = Promise.allSettled(collectors);
|
|
379
386
|
const settled = await Promise.race([collection, deadline]);
|
|
380
387
|
if (!settled) {
|
|
381
388
|
log.warn(`usage collection timed out after ${timeoutMs}ms`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sagentlab/navarch-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "Navarch machine-side session manager: claims delivery tasks and runs them through Claude Code, Codex, Gemini, or OpenCode.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|