@sagentlab/navarch-runtime 0.1.64 → 0.1.66
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 +5 -0
- package/dist/adapters/opencode.cjs +19 -1
- package/dist/api.cjs +4 -0
- package/dist/prompt.cjs +3 -1
- package/dist/session.cjs +24 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,11 @@ it. Plain Node/TypeScript, no Next.js coupling, and no shared app-code coupling
|
|
|
6
6
|
— this directory is a self-contained package you can `npx` on any fresh
|
|
7
7
|
machine.
|
|
8
8
|
|
|
9
|
+
During a connected run, the runtime reports preparation, agent start, output
|
|
10
|
+
activity, and completion checks to the control plane. Project members can see
|
|
11
|
+
these phase updates in the task view. The activity signal contains no prompt
|
|
12
|
+
or CLI output; the redacted transcript is uploaded when the run finishes.
|
|
13
|
+
|
|
9
14
|
The machine operator selects **Claude Code**, **OpenAI Codex**, **Google
|
|
10
15
|
Gemini CLI**, **OpenCode**, or an **Agent Client Protocol (ACP)** agent when connecting a worker. Any selected agent can
|
|
11
16
|
run any unpinned project task; task eligibility depends on capabilities and
|
|
@@ -36,11 +36,30 @@ async function runOpenCodeAdapter(options) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
const env = { ...options.env };
|
|
39
|
+
// V2 resolves its workspace from PWD. spawn({ cwd }) alone leaves the
|
|
40
|
+
// supervisor's PWD in the inherited environment (often the home directory).
|
|
41
|
+
if (!options.dockerExec && options.cwd)
|
|
42
|
+
env.PWD = node_path_1.default.resolve(options.cwd);
|
|
39
43
|
await prepareOpenCodeConfig(options.mcpConfigPath, env, Boolean(options.dockerExec));
|
|
44
|
+
// V2's default background service owns its own cwd, environment and MCP
|
|
45
|
+
// configuration. A client process's isolated config does not isolate that
|
|
46
|
+
// service. Probe help without starting a turn, so older in-process CLIs
|
|
47
|
+
// remain supported and upgrades are detected on the next session.
|
|
48
|
+
const runOptions = { ...options, env };
|
|
49
|
+
const probeOptions = { ...runOptions, timeoutMs: 10_000, onProcessStarted: undefined };
|
|
50
|
+
const help = options.dockerExec
|
|
51
|
+
? await runViaDocker(probeOptions, ["run", "--help"])
|
|
52
|
+
: await runOnHost(probeOptions, ["run", "--help"]);
|
|
53
|
+
if (help.exitCode !== 0 || help.timedOut || help.killedByLeaseLoss) {
|
|
54
|
+
return { ...help, stderr: `OpenCode CLI capability check failed. ${help.stderr}` };
|
|
55
|
+
}
|
|
40
56
|
const binaryKey = `${options.dockerExec ? "docker:" : ""}${options.bin ?? "opencode"}`;
|
|
41
57
|
const defaultLogLevel = !hasArg(options.extraArgs, "--log-level");
|
|
42
58
|
const logLevel = defaultLogLevel ? (openCodeLogLevels.get(binaryKey) ?? "ERROR") : null;
|
|
43
59
|
const args = ["run", options.prompt];
|
|
60
|
+
if (/^\s*--standalone\b/m.test(help.stdout) && !hasArg(options.extraArgs, "--standalone")) {
|
|
61
|
+
args.push("--standalone");
|
|
62
|
+
}
|
|
44
63
|
// Provider failures may only appear in logs, absent from assistant events.
|
|
45
64
|
if (!hasArg(options.extraArgs, "--print-logs"))
|
|
46
65
|
args.push("--print-logs");
|
|
@@ -57,7 +76,6 @@ async function runOpenCodeAdapter(options) {
|
|
|
57
76
|
// use the model's own default.
|
|
58
77
|
args.push("--model", options.reasoningEffort ? `${options.model}#${options.reasoningEffort}` : options.model);
|
|
59
78
|
}
|
|
60
|
-
const runOptions = { ...options, env };
|
|
61
79
|
const run = (runArgs) => runOptions.dockerExec ? runViaDocker(runOptions, runArgs) : runOnHost(runOptions, runArgs);
|
|
62
80
|
let raw = await run(args);
|
|
63
81
|
if (logLevel && raw.exitCode !== 0 && isLogLevelRejection(raw.stderr)) {
|
package/dist/api.cjs
CHANGED
|
@@ -141,6 +141,10 @@ class NavarchApiClient {
|
|
|
141
141
|
throw new Error("heartbeatLease: empty response from control plane.");
|
|
142
142
|
return result;
|
|
143
143
|
}
|
|
144
|
+
/** Best-effort, lease-scoped activity signal for connected machines. */
|
|
145
|
+
async reportActivity(leaseId, phase) {
|
|
146
|
+
await this.request("POST", `/api/dispatch/${encodeURIComponent(leaseId)}/activity`, { phase }, { allowEmpty: true });
|
|
147
|
+
}
|
|
144
148
|
/** schema-design.md §7 — `POST /api/dispatch/:leaseId/complete {status, report, evidence_urls, cost}`. */
|
|
145
149
|
async completeLease(leaseId, req) {
|
|
146
150
|
await this.request("POST", `/api/dispatch/${encodeURIComponent(leaseId)}/complete`, req, {
|
package/dist/prompt.cjs
CHANGED
|
@@ -4,6 +4,7 @@ exports.renderPrompt = renderPrompt;
|
|
|
4
4
|
exports.renderGuidanceCorrectionPrompt = renderGuidanceCorrectionPrompt;
|
|
5
5
|
exports.renderHostedCheckpointNote = renderHostedCheckpointNote;
|
|
6
6
|
const CODE_GRAPH_GUIDANCE = "For JS/TS dependency inspection, the runtime provides `navarch-runtime code-graph --query <symbol-or-file> --mode search|callers|callees|impact --commit HEAD`. If the runtime is not on PATH, use its installed entry point; do not install another copy mid-task. Results describe committed source and include commit/freshness warnings; inspect current files for uncommitted changes and unresolved dependencies.";
|
|
7
|
+
const PROCESS_GUIDANCE = "Process management: this host may run other agent sessions. Keep the PID or tool handle of each preview server you start and stop only that owned process. Never use broad process-name or command-line matching (pkill, killall) to clean up servers: the agent launcher carries the task prompt in its arguments and can match those patterns, terminating your own session. Use a different port when a port is occupied by a process you do not own. Keep scratch files in the current worktree; paths in previous transcripts belong to earlier sessions.";
|
|
7
8
|
/**
|
|
8
9
|
* Renders the four-layer context bundle (project-plan.md §3.8: steering docs,
|
|
9
10
|
* task itself, task-type playbook, depends_on reports) into the single prompt
|
|
@@ -16,13 +17,14 @@ function renderPrompt(task, bundle) {
|
|
|
16
17
|
const repositoryContext = bundle.repository
|
|
17
18
|
? `Repository: ${bundle.repository.full_name} (${bundle.repository.url})\nLocal checkout: this session's isolated git worktree (current working directory).`
|
|
18
19
|
: null;
|
|
19
|
-
return [repositoryContext, bundle.task_context.trim(), CODE_GRAPH_GUIDANCE, bundle.retry_context?.trim()]
|
|
20
|
+
return [repositoryContext, PROCESS_GUIDANCE, bundle.task_context.trim(), CODE_GRAPH_GUIDANCE, bundle.retry_context?.trim()]
|
|
20
21
|
.filter((section) => Boolean(section))
|
|
21
22
|
.join("\n\n") + "\n";
|
|
22
23
|
}
|
|
23
24
|
const sections = [];
|
|
24
25
|
sections.push(`# Task: ${task.summary}`);
|
|
25
26
|
sections.push(`Type: ${task.task_type} | Repo: ${task.repo} | Environment: ${task.environment_scope}`);
|
|
27
|
+
sections.push(PROCESS_GUIDANCE);
|
|
26
28
|
if (bundle.repository) {
|
|
27
29
|
sections.push(`Repository URL: ${bundle.repository.url}\nLocal checkout: this session's isolated git worktree (current working directory).`);
|
|
28
30
|
}
|
package/dist/session.cjs
CHANGED
|
@@ -395,6 +395,23 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
395
395
|
// session actually produced instead of zeroing it.
|
|
396
396
|
let lastCompletion = null;
|
|
397
397
|
let adapterStarted = false;
|
|
398
|
+
// Activity is metadata only: no prompt, model output, or secret-bearing CLI
|
|
399
|
+
// text crosses this path. Failures never affect lease ownership or delivery.
|
|
400
|
+
let activityPending = Promise.resolve();
|
|
401
|
+
let outputObservedAt = 0;
|
|
402
|
+
let outputReportedAt = 0;
|
|
403
|
+
let adapterRunning = false;
|
|
404
|
+
const reportActivity = (phase) => {
|
|
405
|
+
activityPending = activityPending.then(() => api.reportActivity(leaseId, phase)).catch((error) => {
|
|
406
|
+
log.warn(`activity update failed for ${leaseId}: ${(0, redact_cjs_1.redactText)(String(error), registry.list())}`);
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
const activityTimer = setInterval(() => {
|
|
410
|
+
if (adapterRunning && outputObservedAt > outputReportedAt && Date.now() - outputReportedAt >= 60_000) {
|
|
411
|
+
outputReportedAt = Date.now();
|
|
412
|
+
reportActivity("output");
|
|
413
|
+
}
|
|
414
|
+
}, 15_000);
|
|
398
415
|
const heartbeatGuard = new lease_heartbeat_cjs_1.LeaseHeartbeatGuard(claimed.expires_at, () => {
|
|
399
416
|
log.warn(`confirmed lease expired for ${leaseId} without renewal — killing session.`);
|
|
400
417
|
leaseLost = true;
|
|
@@ -407,6 +424,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
407
424
|
abortForRuntimeShutdown();
|
|
408
425
|
lifecycle.enteredMainSessionTry = true;
|
|
409
426
|
try {
|
|
427
|
+
reportActivity("preparing");
|
|
410
428
|
await gitWorktree.prepare();
|
|
411
429
|
if (gitWorktree.hostedCheckpointRestoreFailure) {
|
|
412
430
|
log.warn(`hosted checkpoint for ${leaseId} could not be restored: ` +
|
|
@@ -465,6 +483,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
465
483
|
nextPrompt = null;
|
|
466
484
|
await node_fs_1.promises.writeFile(node_path_1.default.join(workDir, "prompt.md"), runPrompt, "utf8");
|
|
467
485
|
adapterStarted = true;
|
|
486
|
+
adapterRunning = true;
|
|
487
|
+
reportActivity("running");
|
|
468
488
|
const remainingRuntimeMs = maxRuntimeMs > 0
|
|
469
489
|
? maxRuntimeMs - (node_perf_hooks_1.performance.now() - agentBudgetStartedAt)
|
|
470
490
|
: 0;
|
|
@@ -488,8 +508,10 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
488
508
|
cwd: gitWorktree.worktreePath,
|
|
489
509
|
dockerExec: sandbox ? { containerName: sandbox.name, runner: sandbox_cjs_1.nodeCommandRunner } : undefined,
|
|
490
510
|
signal: activeAbortController.signal,
|
|
511
|
+
onActivity: () => { outputObservedAt = Date.now(); },
|
|
491
512
|
}, task.idle_timeout_ms ?? config.sessionIdleTimeoutMs ?? 45 * 60_000, runtime === "opencode" ? config.opencodeStartupTimeoutMs : 0);
|
|
492
513
|
activeAbortController = null;
|
|
514
|
+
adapterRunning = false;
|
|
493
515
|
if (turnResult.timedOut || leaseLost)
|
|
494
516
|
await checkpointForRecovery(turnResult);
|
|
495
517
|
if (turnResult.model) {
|
|
@@ -497,6 +519,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
497
519
|
executionReport = { ...executionReport, model: turnResult.model };
|
|
498
520
|
}
|
|
499
521
|
attempts.push(turnResult);
|
|
522
|
+
reportActivity("finishing");
|
|
500
523
|
const capacityLimit = (0, adapter_capacity_cjs_1.detectAdapterCapacityLimit)(turnResult);
|
|
501
524
|
if (capacityLimit) {
|
|
502
525
|
await checkpointForRecovery(turnResult);
|
|
@@ -750,6 +773,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
750
773
|
deps.shutdownSignal?.removeEventListener("abort", abortForRuntimeShutdown);
|
|
751
774
|
sessionClosing = true;
|
|
752
775
|
clearInterval(heartbeatTimer);
|
|
776
|
+
clearInterval(activityTimer);
|
|
753
777
|
heartbeatGuard.stop();
|
|
754
778
|
activeAbortController?.abort();
|
|
755
779
|
// Drain only an already-started filesystem update, never a stalled network
|
package/package.json
CHANGED