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 +4 -2
- package/lib/worker.mjs +19 -3
- package/package.json +1 -1
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` |
|
|
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=${
|
|
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
|
-
|
|
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"
|
|
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
|
|