@sagentlab/navarch-runtime 0.1.53 → 0.1.55

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.
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.openCodeAdapter = void 0;
4
7
  exports.runOpenCodeAdapter = runOpenCodeAdapter;
5
8
  const node_child_process_1 = require("node:child_process");
6
9
  const node_fs_1 = require("node:fs");
10
+ const node_os_1 = __importDefault(require("node:os"));
11
+ const node_path_1 = __importDefault(require("node:path"));
7
12
  const config_cjs_1 = require("../config.cjs");
8
13
  const kill_agent_cjs_1 = require("../kill-agent.cjs");
9
14
  /**
@@ -30,7 +35,7 @@ async function runOpenCodeAdapter(options) {
30
35
  };
31
36
  }
32
37
  const env = { ...options.env };
33
- await prepareOpenCodeConfig(options.mcpConfigPath, env);
38
+ await prepareOpenCodeConfig(options.mcpConfigPath, env, Boolean(options.dockerExec));
34
39
  const args = ["run", options.prompt];
35
40
  // Provider failures may only appear in logs, absent from assistant events.
36
41
  if (!hasArg(options.extraArgs, "--print-logs"))
@@ -57,7 +62,7 @@ async function runOpenCodeAdapter(options) {
57
62
  function hasArg(args, ...flags) {
58
63
  return args.some((arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
59
64
  }
60
- async function prepareOpenCodeConfig(mcpConfigPath, env) {
65
+ async function prepareOpenCodeConfig(mcpConfigPath, env, inDocker) {
61
66
  if (!mcpConfigPath) {
62
67
  throw new Error("OpenCode requires a per-session MCP config path for isolated execution.");
63
68
  }
@@ -101,6 +106,11 @@ async function prepareOpenCodeConfig(mcpConfigPath, env) {
101
106
  permissions: [
102
107
  { action: "*", resource: "*", effect: "allow" },
103
108
  { action: "external_directory", resource: "*", effect: "deny" },
109
+ ...(await openCodeManagedDirectories(env, inDocker)).map((resource) => ({
110
+ action: "external_directory",
111
+ resource,
112
+ effect: "allow",
113
+ })),
104
114
  { action: "question", resource: "*", effect: "deny" },
105
115
  ],
106
116
  };
@@ -117,6 +127,39 @@ async function prepareOpenCodeConfig(mcpConfigPath, env) {
117
127
  env.OPENCODE_DISABLE_PROJECT_CONFIG = "true";
118
128
  env.XDG_CONFIG_HOME = xdgConfigHome;
119
129
  }
130
+ /**
131
+ * OpenCode's own scratch locations: `os.tmpdir()/opencode` and the data dir's
132
+ * truncated tool-output and background-shell output files. OpenCode grants
133
+ * itself `external_directory` access to these, but config rules are appended
134
+ * after its grants and the last matching rule wins, so the blanket deny above
135
+ * would otherwise hide a background command's log from the agent. Inside the
136
+ * sandbox container the Linux defaults apply (`~` expands to the OpenCode
137
+ * process's home); on the host they follow the child environment, plus the
138
+ * canonical temp path since OpenCode canonicalizes before matching (macOS
139
+ * `/var` -> `/private/var`).
140
+ */
141
+ async function openCodeManagedDirectories(env, inDocker) {
142
+ if (inDocker) {
143
+ return [
144
+ "/tmp/opencode/*",
145
+ "~/.local/share/opencode/tool-output/*",
146
+ "~/.local/share/opencode/shell/*/*",
147
+ ];
148
+ }
149
+ const tmpRoot = env.TMPDIR || process.env.TMPDIR || node_os_1.default.tmpdir();
150
+ const tmpRoots = [tmpRoot, await node_fs_1.promises.realpath(tmpRoot).catch(() => tmpRoot)];
151
+ const dataHome = env.XDG_DATA_HOME ||
152
+ process.env.XDG_DATA_HOME ||
153
+ node_path_1.default.join(env.HOME || node_os_1.default.homedir(), ".local", "share");
154
+ const data = node_path_1.default.join(dataHome, "opencode");
155
+ return [
156
+ ...new Set([
157
+ ...tmpRoots.map((root) => node_path_1.default.join(root, "opencode", "*")),
158
+ node_path_1.default.join(data, "tool-output", "*"),
159
+ node_path_1.default.join(data, "shell", "*", "*"),
160
+ ]),
161
+ ];
162
+ }
120
163
  function safeEnvSegment(value) {
121
164
  return value.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
122
165
  }
package/dist/session.cjs CHANGED
@@ -94,7 +94,7 @@ async function runSession(deps, claimed, sessionId) {
94
94
  await api
95
95
  .completeLease(leaseId, {
96
96
  status: "failed",
97
- report: verificationFailureReport(task.task_type, failureSummary),
97
+ report: verificationFailureReport(task.task_type, failureSummary, "crashed"),
98
98
  failure_summary: failureSummary,
99
99
  failed_before_start: true,
100
100
  evidence_urls: [],
@@ -174,7 +174,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
174
174
  const failureSummary = `Project ${task.project_id} has no GitHub repository URL. Set it in Project settings before dispatching work.`;
175
175
  await api.completeLease(leaseId, {
176
176
  status: "failed",
177
- report: verificationFailureReport(task.task_type, failureSummary),
177
+ report: verificationFailureReport(task.task_type, failureSummary, "crashed"),
178
178
  failure_summary: failureSummary,
179
179
  failed_before_start: true,
180
180
  evidence_urls: [],
@@ -294,7 +294,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
294
294
  await api
295
295
  .completeLease(leaseId, {
296
296
  status: "failed",
297
- report: verificationFailureReport(task.task_type, "Docker sandbox unavailable on this machine."),
297
+ report: verificationFailureReport(task.task_type, "Docker sandbox unavailable on this machine.", "crashed"),
298
298
  failure_summary: "Docker sandbox unavailable on this machine.",
299
299
  failed_before_start: true,
300
300
  evidence_urls: [],
@@ -616,7 +616,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
616
616
  const completion = {
617
617
  status: mapping.leaseOutcome,
618
618
  report: mapping.leaseOutcome === "failed"
619
- ? verificationFailureReport(task.task_type, redactedReport)
619
+ ? verificationFailureReport(task.task_type, redactedReport, mapping.exitStatus)
620
620
  : redactedReport,
621
621
  evidence_urls: mapping.evidenceUrls,
622
622
  ...(deliveryFailureSummary
@@ -674,7 +674,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
674
674
  status: "failed",
675
675
  report: verificationFailureReport(task.task_type, remediable
676
676
  ? redactedRejection
677
- : `${redactedRejection}\n\n---\n\n${completion.report}`),
677
+ : `${redactedRejection}\n\n---\n\n${completion.report}`, "failed"),
678
678
  failure_summary: redactedRejection,
679
679
  exit_status: "failed",
680
680
  });
@@ -708,7 +708,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
708
708
  status: "failed",
709
709
  report: verificationFailureReport(task.task_type, lastCompletion
710
710
  ? `${lastCompletion.report}\n\n---\n\n${failureSummary}`
711
- : failureSummary),
711
+ : failureSummary, "crashed"),
712
712
  failure_summary: failureSummary,
713
713
  failed_before_start: !adapterStarted,
714
714
  evidence_urls: lastCompletion?.evidence_urls ?? [],
@@ -794,14 +794,23 @@ function sumReportedUsage(attempts, key) {
794
794
  * does not open with `Verification verdict: FAIL` or `BLOCKED`
795
795
  * (verification_verdict_invalid), and that gate applies to runtime-authored
796
796
  * failure reports too. When the report carries no usable verdict — the agent
797
- * crashed, timed out, exited non-zero, or opened with PASS despite the failed
798
- * outcome — label it BLOCKED: the failure prevented an acceptance decision.
799
- * Without this, the failure completion is itself rejected, the session
800
- * crashes, and the lease dangles until expiry.
797
+ * exited non-zero, or opened with PASS despite the failed outcome — label it
798
+ * BLOCKED: the failure prevented an acceptance decision. Without this, the
799
+ * failure completion is itself rejected, the session crashes, and the lease
800
+ * dangles until expiry.
801
+ *
802
+ * A `killed` or `crashed` exit is left unlabelled: the control plane accepts
803
+ * those verdict-less as the runtime's own failure and routes them through
804
+ * failure attribution (an infrastructure retry). A BLOCKED label there would
805
+ * misreport a transient timeout as a verifier verdict and escalate it to a
806
+ * human; the control plane discards such a label, so posting it only muddies
807
+ * the session report.
801
808
  */
802
- function verificationFailureReport(taskType, report) {
809
+ function verificationFailureReport(taskType, report, exitStatus) {
803
810
  if (taskType !== "verify")
804
811
  return report;
812
+ if (exitStatus === "killed" || exitStatus === "crashed")
813
+ return report;
805
814
  const verdict = (0, exit_conditions_cjs_1.leadingVerificationVerdict)(report);
806
815
  if (verdict === "fail" || verdict === "blocked")
807
816
  return report;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagentlab/navarch-runtime",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
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",