@sagentlab/navarch-runtime 0.1.10 → 0.1.11
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/dist/api.cjs +49 -6
- package/dist/claim-loop.cjs +20 -3
- package/dist/worktree-guard.cjs +31 -0
- package/package.json +2 -2
package/dist/api.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NavarchApiClient = exports.NavarchApiError = void 0;
|
|
3
|
+
exports.NavarchApiClient = exports.NavarchTransportError = exports.NavarchApiError = void 0;
|
|
4
4
|
class NavarchApiError extends Error {
|
|
5
5
|
status;
|
|
6
6
|
body;
|
|
@@ -12,6 +12,18 @@ class NavarchApiError extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
exports.NavarchApiError = NavarchApiError;
|
|
15
|
+
/** A request that failed before the control plane returned an HTTP response. */
|
|
16
|
+
class NavarchTransportError extends Error {
|
|
17
|
+
method;
|
|
18
|
+
endpoint;
|
|
19
|
+
constructor(method, endpoint, cause) {
|
|
20
|
+
super(`Navarch API ${method} ${endpoint} transport failed: ${describeError(cause)}`, { cause });
|
|
21
|
+
this.method = method;
|
|
22
|
+
this.endpoint = endpoint;
|
|
23
|
+
this.name = "NavarchTransportError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.NavarchTransportError = NavarchTransportError;
|
|
15
27
|
/**
|
|
16
28
|
* Typed client for the Navarch control-plane API surface WP-07 depends on:
|
|
17
29
|
* dispatch/claim, per-lease heartbeat, complete, and broker/issue
|
|
@@ -44,11 +56,20 @@ class NavarchApiClient {
|
|
|
44
56
|
}
|
|
45
57
|
headers.authorization = `Bearer ${this.token}`;
|
|
46
58
|
}
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
const requestUrl = `${this.baseUrl}${pathname}`;
|
|
60
|
+
let response;
|
|
61
|
+
try {
|
|
62
|
+
response = await this.fetchImpl(requestUrl, {
|
|
63
|
+
method,
|
|
64
|
+
headers,
|
|
65
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
// Include enough request/cause context to diagnose DNS, connection, and
|
|
70
|
+
// TLS failures. Deliberately omit headers and URL credentials/query data.
|
|
71
|
+
throw new NavarchTransportError(method, safeEndpoint(requestUrl, pathname), err);
|
|
72
|
+
}
|
|
52
73
|
if (response.status === 204)
|
|
53
74
|
return null;
|
|
54
75
|
const text = await response.text();
|
|
@@ -133,3 +154,25 @@ function safeJsonParse(text) {
|
|
|
133
154
|
return null;
|
|
134
155
|
}
|
|
135
156
|
}
|
|
157
|
+
function safeEndpoint(requestUrl, pathname) {
|
|
158
|
+
try {
|
|
159
|
+
const parsed = new URL(requestUrl);
|
|
160
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return pathname;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function describeError(err) {
|
|
167
|
+
if (!(err instanceof Error))
|
|
168
|
+
return String(err);
|
|
169
|
+
let detail = `${err.name}: ${err.message}`;
|
|
170
|
+
const cause = err.cause;
|
|
171
|
+
if (cause instanceof Error) {
|
|
172
|
+
detail += `; cause: ${cause.name}: ${cause.message}`;
|
|
173
|
+
}
|
|
174
|
+
else if (cause && typeof cause === "object" && "code" in cause) {
|
|
175
|
+
detail += `; cause code: ${String(cause.code)}`;
|
|
176
|
+
}
|
|
177
|
+
return detail;
|
|
178
|
+
}
|
package/dist/claim-loop.cjs
CHANGED
|
@@ -5,6 +5,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
5
5
|
const api_cjs_1 = require("./api.cjs");
|
|
6
6
|
const logger_cjs_1 = require("./logger.cjs");
|
|
7
7
|
const log = (0, logger_cjs_1.createLogger)("claim");
|
|
8
|
+
const MAX_CLAIM_BACKOFF_MS = 60_000;
|
|
8
9
|
/**
|
|
9
10
|
* Polls dispatch/claim on an interval, gated by available capacity
|
|
10
11
|
* (implementation-plan.md WP-07 "Claim loop: poll dispatch, receive context
|
|
@@ -20,6 +21,8 @@ class ClaimLoop {
|
|
|
20
21
|
timer = null;
|
|
21
22
|
stopped = false;
|
|
22
23
|
claimInFlight = false;
|
|
24
|
+
consecutiveFailures = 0;
|
|
25
|
+
nextClaimAt = 0;
|
|
23
26
|
quiescenceWaiters = new Set();
|
|
24
27
|
constructor(api, config, capacity, runSession) {
|
|
25
28
|
this.api = api;
|
|
@@ -31,6 +34,8 @@ class ClaimLoop {
|
|
|
31
34
|
if (this.timer)
|
|
32
35
|
return;
|
|
33
36
|
this.stopped = false;
|
|
37
|
+
this.consecutiveFailures = 0;
|
|
38
|
+
this.nextClaimAt = 0;
|
|
34
39
|
this.timer = setInterval(() => void this.tick(), this.config.pollIntervalMs);
|
|
35
40
|
}
|
|
36
41
|
stop() {
|
|
@@ -52,7 +57,10 @@ class ClaimLoop {
|
|
|
52
57
|
await new Promise((resolve) => this.quiescenceWaiters.add(resolve));
|
|
53
58
|
}
|
|
54
59
|
async tick() {
|
|
55
|
-
if (this.stopped ||
|
|
60
|
+
if (this.stopped ||
|
|
61
|
+
this.claimInFlight ||
|
|
62
|
+
!this.capacity.hasCapacity() ||
|
|
63
|
+
Date.now() < this.nextClaimAt)
|
|
56
64
|
return;
|
|
57
65
|
this.claimInFlight = true;
|
|
58
66
|
try {
|
|
@@ -66,6 +74,11 @@ class ClaimLoop {
|
|
|
66
74
|
agent_type: this.config.agentType,
|
|
67
75
|
session_id: sessionId,
|
|
68
76
|
});
|
|
77
|
+
if (this.consecutiveFailures > 0) {
|
|
78
|
+
log.info(`claim polling recovered after ${this.consecutiveFailures} failed attempt(s)`);
|
|
79
|
+
}
|
|
80
|
+
this.consecutiveFailures = 0;
|
|
81
|
+
this.nextClaimAt = 0;
|
|
69
82
|
if (!claimed)
|
|
70
83
|
return;
|
|
71
84
|
this.capacity.acquire(claimed.lease_id);
|
|
@@ -75,16 +88,20 @@ class ClaimLoop {
|
|
|
75
88
|
.finally(() => this.capacity.release(claimed.lease_id));
|
|
76
89
|
}
|
|
77
90
|
catch (err) {
|
|
91
|
+
this.consecutiveFailures += 1;
|
|
92
|
+
const retryDelayMs = Math.min(MAX_CLAIM_BACKOFF_MS, this.config.pollIntervalMs * 2 ** this.consecutiveFailures);
|
|
93
|
+
this.nextClaimAt = Date.now() + retryDelayMs;
|
|
78
94
|
// NavarchApiError's message is only the status line ("… failed with
|
|
79
95
|
// 500"); the control plane's actual error text lives in `.body`. Log it
|
|
80
96
|
// so a server-side claim failure is diagnosable from the runtime alone
|
|
81
97
|
// instead of an opaque bare status.
|
|
82
98
|
if (err instanceof api_cjs_1.NavarchApiError) {
|
|
83
99
|
const detail = typeof err.body === "string" ? err.body : JSON.stringify(err.body);
|
|
84
|
-
log.warn(`claim failed
|
|
100
|
+
log.warn(`claim failed (attempt ${this.consecutiveFailures}; retrying in ${retryDelayMs}ms): ` +
|
|
101
|
+
`${err.message}${detail ? ` — ${detail}` : ""}`);
|
|
85
102
|
}
|
|
86
103
|
else {
|
|
87
|
-
log.warn(`claim failed: ${String(err)}`);
|
|
104
|
+
log.warn(`claim failed (attempt ${this.consecutiveFailures}; retrying in ${retryDelayMs}ms): ${String(err)}`);
|
|
88
105
|
}
|
|
89
106
|
}
|
|
90
107
|
finally {
|
package/dist/worktree-guard.cjs
CHANGED
|
@@ -5,9 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.guardHookScriptPath = guardHookScriptPath;
|
|
7
7
|
exports.prepareWorktreeGuard = prepareWorktreeGuard;
|
|
8
|
+
exports.codexToolReadRoots = codexToolReadRoots;
|
|
8
9
|
exports.codexWorktreeGuardArgs = codexWorktreeGuardArgs;
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
11
|
const node_fs_1 = require("node:fs");
|
|
12
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
11
13
|
const CODEX_GUARD_PROFILE = "navarch-worktree";
|
|
12
14
|
/**
|
|
13
15
|
* Tools the hook screens. Everything else — the lease-scoped Navarch MCP
|
|
@@ -55,6 +57,29 @@ async function prepareWorktreeGuard(options) {
|
|
|
55
57
|
await node_fs_1.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
56
58
|
return { settingsPath, configPath, hookScriptPath };
|
|
57
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Read-only roots required by Codex's own tools and installed skills.
|
|
62
|
+
*
|
|
63
|
+
* Deliberately exclude the CODEX_HOME root itself: it may contain auth.json
|
|
64
|
+
* and user configuration. Package executables, installed skill instructions,
|
|
65
|
+
* plugin code, the operator's executable directory, and browser/toolchain
|
|
66
|
+
* installations are sufficient for helpers such as apply_patch, npm, gh, and
|
|
67
|
+
* browser evidence tooling.
|
|
68
|
+
*/
|
|
69
|
+
function codexToolReadRoots(env = process.env) {
|
|
70
|
+
const homeDir = env.HOME?.trim() || node_os_1.default.homedir();
|
|
71
|
+
const codexHome = env.CODEX_HOME?.trim() || node_path_1.default.join(homeDir, ".codex");
|
|
72
|
+
return [
|
|
73
|
+
node_path_1.default.join(codexHome, "packages"),
|
|
74
|
+
node_path_1.default.join(codexHome, "skills"),
|
|
75
|
+
node_path_1.default.join(codexHome, "plugins"),
|
|
76
|
+
node_path_1.default.join(homeDir, ".agents", "skills"),
|
|
77
|
+
node_path_1.default.join(homeDir, ".local", "bin"),
|
|
78
|
+
"/opt/homebrew",
|
|
79
|
+
node_path_1.default.join(homeDir, "Library", "Caches", "ms-playwright"),
|
|
80
|
+
node_path_1.default.join(homeDir, ".cache", "ms-playwright"),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
58
83
|
/**
|
|
59
84
|
* Builds one-off Codex permission-profile arguments for a host session.
|
|
60
85
|
*
|
|
@@ -78,6 +103,12 @@ function codexWorktreeGuardArgs(options) {
|
|
|
78
103
|
[node_path_1.default.resolve(options.worktreePath)]: "write",
|
|
79
104
|
[node_path_1.default.resolve(options.repositoryPath)]: "write",
|
|
80
105
|
};
|
|
106
|
+
for (const root of codexToolReadRoots()) {
|
|
107
|
+
const resolved = node_path_1.default.resolve(root);
|
|
108
|
+
if (isPathInside(resolved, node_path_1.default.resolve(options.workspaceRoot)))
|
|
109
|
+
continue;
|
|
110
|
+
filesystem[resolved] = "read";
|
|
111
|
+
}
|
|
81
112
|
for (const root of options.extraRoots ?? []) {
|
|
82
113
|
const resolved = node_path_1.default.resolve(root);
|
|
83
114
|
// Match the Claude hook's denied-root precedence: an extra root cannot
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sagentlab/navarch-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Navarch machine-side session manager: registers a machine, claims tasks from the control-plane dispatcher, runs them via the Claude Code or Codex adapter, and reports results back.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"task-runner"
|
|
19
19
|
],
|
|
20
20
|
"bin": {
|
|
21
|
-
"navarch-runtime": "
|
|
21
|
+
"navarch-runtime": "bin/navarch.cjs"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|