opencode-worker 0.1.8 → 0.1.10

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
@@ -69,10 +69,10 @@ Exit codes: `0` ok, `1` worker failed (timeout, nonzero exit, error event, model
69
69
  | `opencode` | `{bin, version}` |
70
70
  | `requested` | `{model, variant, agent, timeoutSec}` |
71
71
  | `actual` | provider/model/variant recorded in the isolated session DB (needs `sqlite3`; `null` if unavailable) |
72
- | `text` | worker's final text |
72
+ | `text` | worker's final answer: the text after its last tool call, without narration like "I'll read the file." (all text, joined by blank lines, if nothing follows the last tool call). Safe to paste into the next job's prompt; workers never see each other's results otherwise |
73
73
  | `tools[]` | `{tool, status, input, exit, output, error}` per tool call; `exit` is the bash exit code (else `null`), `output` capped at 4000 chars |
74
74
  | `cost`, `tokens` | summed from `step_finish` events |
75
- | `diff` | `diff -ruN` of `<dir>` before vs after (excludes `.opencode-worker/`) |
75
+ | `diff` | unified diff of `<dir>` before vs after. Excludes `.opencode-worker/` and tool caches (`__pycache__`, `*.pyc`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.cache`); the worker also runs with `PYTHONDONTWRITEBYTECODE=1`, so test runs don't show up as changes |
76
76
  | `errors[]` | `timeout`, `cancelled`, `nonzero_exit`, `event_error`, `model_mismatch`, `diff_failed`, `unparsed_line` |
77
77
  | `artifacts` | raw `events.jsonl` and `stderr.txt` under `<dir>/.opencode-worker/runs/<id>/` |
78
78
 
@@ -117,6 +117,8 @@ env = { OPENCODE_WORKER_BIN = "/path/to/opencode" }
117
117
  default_tools_approval_mode = "approve"
118
118
  ```
119
119
 
120
+ Codex does not read a project-level `.codex/config.toml`. To enable the worker only when needed, put the same block in a profile file `~/.codex/ocw.config.toml` and start Codex with `codex -p ocw`.
121
+
120
122
  OMP (`.omp/mcp.json` or `~/.omp/agent/mcp.json`): the JSON above with `"type": "stdio"`. Non-interactive: `omp -p --auto-approve "…"`.
121
123
 
122
124
  Job records are written to `$OPENCODE_WORKER_STATE_DIR` (default `$XDG_STATE_HOME/opencode-worker/jobs`, else `~/.local/state/opencode-worker/jobs`), so `worker_status`/`worker_result` work after a server or client restart.
package/lib/worker.mjs CHANGED
@@ -60,6 +60,8 @@ function isolatedEnv(stateRoot, configPath) {
60
60
  OPENCODE_CONFIG_DIR: path.join(stateRoot, "xdg/config/opencode"),
61
61
  OPENCODE_DISABLE_PROJECT_CONFIG: "1",
62
62
  TERM: "dumb",
63
+ // Test runs must not show up as file changes in the diff.
64
+ PYTHONDONTWRITEBYTECODE: "1",
63
65
  };
64
66
  for (const k of ["LANG", "LC_ALL", "TMPDIR"]) if (process.env[k]) env[k] = process.env[k];
65
67
  for (const k of ["HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME", "XDG_STATE_HOME", "OPENCODE_CONFIG_DIR"])
@@ -95,27 +97,40 @@ function snapshot(dir, dest) {
95
97
  }
96
98
  }
97
99
 
100
+ // Tool caches that commands write as a side effect; never meaningful in a worker diff.
101
+ export const DIFF_EXCLUDES = [STATE_DIR, "__pycache__", "*.pyc", ".pytest_cache", ".mypy_cache", ".ruff_cache", ".cache"];
102
+
98
103
  function unifiedDiff(before, dir) {
99
- const r = spawnSync("diff", ["-ruN", `--exclude=${STATE_DIR}`, "before", dir], {
104
+ const r = spawnSync("diff", ["-ruN", ...DIFF_EXCLUDES.map((p) => `--exclude=${p}`), "before", dir], {
100
105
  cwd: path.dirname(before),
101
106
  encoding: "utf8",
102
107
  maxBuffer: 32 * 1024 * 1024,
103
108
  });
104
109
  // diff exit: 0 same, 1 different, 2 trouble
105
110
  if (r.status === 2) return { diff: null, error: r.stderr.trim() };
106
- return { diff: r.stdout.replaceAll(dir + "/", "after/"), error: null };
111
+ // Drop the per-file "diff -ruN --exclude=…" command lines; ---/+++ headers already name each file.
112
+ const body = r.stdout.split("\n").filter((l) => !l.startsWith("diff -ruN ")).join("\n");
113
+ return { diff: body.replaceAll(dir + "/", "after/"), error: null };
107
114
  }
108
115
 
116
+ /**
117
+ * `text` is the worker's final answer: text emitted after its last tool call.
118
+ * Narration before tool calls ("I'll read the file.") is dropped so the answer can be
119
+ * handed to the next job verbatim. If nothing follows the last tool call, all text is kept.
120
+ */
109
121
  function parseEvents(stdout) {
110
122
  const out = { text: "", tools: [], cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0 }, errors: [], sessionID: null };
123
+ const allText = [];
124
+ let finalText = [];
111
125
  for (const line of stdout.split("\n")) {
112
126
  if (!line.trim()) continue;
113
127
  let ev;
114
128
  try { ev = JSON.parse(line); } catch { out.errors.push({ type: "unparsed_line", detail: line.slice(0, 300) }); continue; }
115
129
  out.sessionID ??= ev.sessionID ?? null;
116
130
  const p = ev.part ?? {};
117
- if (ev.type === "text") out.text += p.text ?? "";
131
+ if (ev.type === "text" && p.text) { allText.push(p.text); finalText.push(p.text); }
118
132
  else if (ev.type === "tool_use") {
133
+ finalText = [];
119
134
  const s = p.state ?? {};
120
135
  const output = typeof s.output === "string" ? s.output : s.output == null ? null : JSON.stringify(s.output);
121
136
  out.tools.push({
@@ -137,6 +152,7 @@ function parseEvents(stdout) {
137
152
  out.tokens.cacheWrite += t.cache?.write ?? 0;
138
153
  } else if (ev.type === "error") out.errors.push({ type: "event_error", detail: ev.error ?? p });
139
154
  }
155
+ out.text = (finalText.length ? finalText : allText).join("\n\n").trim();
140
156
  return out;
141
157
  }
142
158
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-worker",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Run the official OpenCode CLI as an isolated, time-boxed worker. CLI + MCP stdio server.",
5
5
  "repository": {
6
6
  "type": "git",