codedeck 0.1.2 → 0.1.4
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 +61 -3
- package/dist/cli/commands/logs.js +5 -1
- package/dist/cli/commands/logs.js.map +1 -1
- package/dist/cli/commands/run.js +91 -28
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/show.js +4 -0
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/wait.js +57 -0
- package/dist/cli/commands/wait.js.map +1 -0
- package/dist/cli/index.js +10 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/wait.js +44 -0
- package/dist/cli/wait.js.map +1 -0
- package/dist/core/driver.js +9 -1
- package/dist/core/driver.js.map +1 -1
- package/dist/core/errors.js +56 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/events.js.map +1 -1
- package/dist/core/session.js.map +1 -1
- package/dist/daemon/daemon.js +316 -71
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/drivers/claude/driver.js +34 -205
- package/dist/drivers/claude/driver.js.map +1 -1
- package/dist/drivers/codex/driver.js +65 -205
- package/dist/drivers/codex/driver.js.map +1 -1
- package/dist/drivers/helpers.js +47 -37
- package/dist/drivers/helpers.js.map +1 -1
- package/dist/drivers/omp/driver.js +38 -206
- package/dist/drivers/omp/driver.js.map +1 -1
- package/dist/drivers/opencode/driver.js +30 -182
- package/dist/drivers/opencode/driver.js.map +1 -1
- package/dist/drivers/session-driver.js +122 -0
- package/dist/drivers/session-driver.js.map +1 -0
- package/dist/drivers/session-runtime.js +333 -0
- package/dist/drivers/session-runtime.js.map +1 -0
- package/dist/drivers/tailer.js +149 -0
- package/dist/drivers/tailer.js.map +1 -0
- package/dist/drivers/terminal.js +53 -0
- package/dist/drivers/terminal.js.map +1 -0
- package/dist/store/database.js +28 -1
- package/dist/store/database.js.map +1 -1
- package/dist/store/events.js +8 -3
- package/dist/store/events.js.map +1 -1
- package/dist/store/sessions.js +27 -4
- package/dist/store/sessions.js.map +1 -1
- package/dist/utils/process.js +104 -4
- package/dist/utils/process.js.map +1 -1
- package/package.json +10 -2
|
@@ -1,10 +1,70 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { detectBinary } from "../helpers.js";
|
|
3
5
|
import { parseCodexLine } from "./parser.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
+
import { createRuntimeHooks, SessionDriver } from "../session-driver.js";
|
|
7
|
+
// Arg building is a pure function so the flag spellings can be tested without
|
|
8
|
+
// spawning codex. Measured against codex-cli 0.150.1.
|
|
9
|
+
export function buildCodexArgs(options) {
|
|
10
|
+
// resume is a SUBCOMMAND with its own arg list; every flag below has to be
|
|
11
|
+
// mirrored onto it or `send()` silently runs with different settings than
|
|
12
|
+
// `start()` did.
|
|
13
|
+
const args = options.resumeSessionId
|
|
14
|
+
? ["exec", "resume", options.resumeSessionId, "--json"]
|
|
15
|
+
: ["exec", "--json"];
|
|
16
|
+
if (options.model)
|
|
17
|
+
args.push("-m", options.model);
|
|
18
|
+
// Without this codex uses its read-only default and rejects every patch with
|
|
19
|
+
// "writing is blocked by read-only sandbox" -- while still exiting 0, so the
|
|
20
|
+
// session is recorded as completed despite having written nothing.
|
|
21
|
+
args.push("-s", "workspace-write");
|
|
22
|
+
// codex has no dedicated flags for these; both are config overrides whose
|
|
23
|
+
// values are parsed as TOML, hence the embedded quotes.
|
|
24
|
+
if (options.effort)
|
|
25
|
+
args.push("-c", `model_reasoning_effort="${options.effort}"`);
|
|
26
|
+
if (options.fast)
|
|
27
|
+
args.push("-c", 'service_tier="priority"');
|
|
28
|
+
args.push("--skip-git-repo-check");
|
|
29
|
+
args.push("-C", options.cwd);
|
|
30
|
+
args.push(options.prompt);
|
|
31
|
+
return args;
|
|
32
|
+
}
|
|
33
|
+
// Tool-level failures (sandbox denials, approval refusals) are logged to stderr
|
|
34
|
+
// by the Rust core and NEVER appear as JSON frames on stdout. Without this the
|
|
35
|
+
// only trace is an agent message saying it could not do the work, followed by a
|
|
36
|
+
// clean turn.completed.
|
|
37
|
+
export function parseCodexStderrLine(line, sessionId) {
|
|
38
|
+
const match = /\bERROR\b\s+(.+)$/.exec(line);
|
|
39
|
+
if (!match)
|
|
40
|
+
return null;
|
|
41
|
+
let message = match[1].trim();
|
|
42
|
+
// Drop the `<rust::module::path>: error=` prefix; keep the human part.
|
|
43
|
+
const marker = message.indexOf("error=");
|
|
44
|
+
if (marker !== -1)
|
|
45
|
+
message = message.slice(marker + "error=".length).trim();
|
|
46
|
+
if (!message)
|
|
47
|
+
return null;
|
|
48
|
+
return {
|
|
49
|
+
type: "error",
|
|
50
|
+
sessionId,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
error: message,
|
|
53
|
+
raw: { stderr: line },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export class CodexDriver extends SessionDriver {
|
|
6
57
|
id = "codex";
|
|
7
|
-
|
|
58
|
+
hooks = createRuntimeHooks({
|
|
59
|
+
parse: parseCodexLine,
|
|
60
|
+
parseStderr: parseCodexStderrLine,
|
|
61
|
+
nativeKeys: ["thread_id", "threadId"],
|
|
62
|
+
harness: "Codex",
|
|
63
|
+
});
|
|
64
|
+
resumeError = "No native session id for Codex resume";
|
|
65
|
+
buildArgs(options) {
|
|
66
|
+
return buildCodexArgs(options);
|
|
67
|
+
}
|
|
8
68
|
capabilities() {
|
|
9
69
|
return {
|
|
10
70
|
streaming: true,
|
|
@@ -26,9 +86,6 @@ export class CodexDriver {
|
|
|
26
86
|
// Simplify: check CODEX env or config
|
|
27
87
|
let authenticated;
|
|
28
88
|
try {
|
|
29
|
-
const fs = await import("node:fs");
|
|
30
|
-
const os = await import("node:os");
|
|
31
|
-
const path = await import("node:path");
|
|
32
89
|
const home = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
33
90
|
if (fs.existsSync(path.join(home, "auth.json")) || fs.existsSync(path.join(home, "config.toml"))) {
|
|
34
91
|
// not definitive but assume authenticated if file exists
|
|
@@ -40,202 +97,5 @@ export class CodexDriver {
|
|
|
40
97
|
catch { }
|
|
41
98
|
return { installed: true, path: res.path, version: res.version, authenticated, details: "codex exec --json" };
|
|
42
99
|
}
|
|
43
|
-
async start(options) {
|
|
44
|
-
const args = ["exec", "--json"];
|
|
45
|
-
// Model
|
|
46
|
-
if (options.model)
|
|
47
|
-
args.push("-m", options.model);
|
|
48
|
-
// Sandbox: use workspace-write for automation; allow tests to run
|
|
49
|
-
args.push("--skip-git-repo-check");
|
|
50
|
-
// Ensure cwd
|
|
51
|
-
args.push("-C", options.cwd);
|
|
52
|
-
// If resume, we need to use codex exec resume <id> instead
|
|
53
|
-
// But for MVP start with prompt arg; codex exec resume expects prior thread id
|
|
54
|
-
if (options.resumeSessionId) {
|
|
55
|
-
// Use resume subcommand: codex exec resume --json <id> "prompt"
|
|
56
|
-
// Actually shape: codex exec resume <thread_id> --json?
|
|
57
|
-
// Check help: codex exec resume --help shows?
|
|
58
|
-
// We observed: codex exec resume <id>
|
|
59
|
-
// Simpler: try codex exec resume <nativeId> --json
|
|
60
|
-
// We'll build alternative arg list
|
|
61
|
-
const resumeArgs = ["exec", "resume", options.resumeSessionId, "--json", "--skip-git-repo-check", "-C", options.cwd];
|
|
62
|
-
if (options.model)
|
|
63
|
-
resumeArgs.push("-m", options.model);
|
|
64
|
-
resumeArgs.push(options.prompt);
|
|
65
|
-
return this.spawnWithArgs(resumeArgs, options);
|
|
66
|
-
}
|
|
67
|
-
args.push(options.prompt);
|
|
68
|
-
return this.spawnWithArgs(args, options);
|
|
69
|
-
}
|
|
70
|
-
async spawnWithArgs(args, options) {
|
|
71
|
-
const proc = spawn("codex", args, {
|
|
72
|
-
cwd: options.cwd,
|
|
73
|
-
env: { ...process.env },
|
|
74
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
75
|
-
});
|
|
76
|
-
proc.stdin?.end();
|
|
77
|
-
const emitter = new EventEmitter();
|
|
78
|
-
const buffer = [];
|
|
79
|
-
let nativeId = options.resumeSessionId;
|
|
80
|
-
let done = false;
|
|
81
|
-
let exitCode = null;
|
|
82
|
-
const push = (ev) => {
|
|
83
|
-
ev.sessionId = options.sessionId;
|
|
84
|
-
if (ev.nativeSessionId)
|
|
85
|
-
nativeId = ev.nativeSessionId;
|
|
86
|
-
if (!nativeId && ev.raw?.thread_id)
|
|
87
|
-
nativeId = ev.raw.thread_id;
|
|
88
|
-
if (!nativeId && ev.raw?.threadId)
|
|
89
|
-
nativeId = ev.raw.threadId;
|
|
90
|
-
buffer.push(ev);
|
|
91
|
-
emitter.emit("event", ev);
|
|
92
|
-
};
|
|
93
|
-
createLineReader(proc.stdout, (line) => {
|
|
94
|
-
// Codex may emit empty or prefix "Reading additional input" on stderr, not stdout
|
|
95
|
-
// Assume JSON lines
|
|
96
|
-
const evs = parseCodexLine(line, options.sessionId);
|
|
97
|
-
if (evs.length) {
|
|
98
|
-
for (const ev of evs)
|
|
99
|
-
push(ev);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
// Try to extract thread_id anyway
|
|
103
|
-
try {
|
|
104
|
-
const obj = JSON.parse(line);
|
|
105
|
-
if (obj.thread_id && !nativeId)
|
|
106
|
-
nativeId = obj.thread_id;
|
|
107
|
-
if (obj.threadId && !nativeId)
|
|
108
|
-
nativeId = obj.threadId;
|
|
109
|
-
}
|
|
110
|
-
catch { }
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
let stderrBuf = "";
|
|
114
|
-
proc.stderr?.on("data", (c) => (stderrBuf += c.toString()));
|
|
115
|
-
proc.on("close", (code) => {
|
|
116
|
-
exitCode = code;
|
|
117
|
-
done = true;
|
|
118
|
-
// If not already completed/failed, emit final
|
|
119
|
-
const hasTerminal = buffer.some((e) => e.type === "session.completed" || e.type === "session.failed");
|
|
120
|
-
if (!hasTerminal) {
|
|
121
|
-
if (code === 0) {
|
|
122
|
-
push({
|
|
123
|
-
type: "session.completed",
|
|
124
|
-
sessionId: options.sessionId,
|
|
125
|
-
timestamp: new Date().toISOString(),
|
|
126
|
-
reason: "exit 0",
|
|
127
|
-
exitCode: 0,
|
|
128
|
-
raw: { stderr: stderrBuf.slice(0, 2000) },
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
push({
|
|
133
|
-
type: "session.failed",
|
|
134
|
-
sessionId: options.sessionId,
|
|
135
|
-
timestamp: new Date().toISOString(),
|
|
136
|
-
error: stderrBuf.slice(0, 2000) || `Codex exited with code ${code}`,
|
|
137
|
-
exitCode: code ?? 1,
|
|
138
|
-
raw: { stderr: stderrBuf.slice(0, 2000) },
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
emitter.emit("done");
|
|
143
|
-
});
|
|
144
|
-
proc.on("error", (err) => {
|
|
145
|
-
push({
|
|
146
|
-
type: "session.failed",
|
|
147
|
-
sessionId: options.sessionId,
|
|
148
|
-
timestamp: new Date().toISOString(),
|
|
149
|
-
error: err.message,
|
|
150
|
-
raw: err,
|
|
151
|
-
});
|
|
152
|
-
done = true;
|
|
153
|
-
emitter.emit("done");
|
|
154
|
-
});
|
|
155
|
-
const handle = { proc, emitter, buffer, done, exitCode, nativeSessionId: nativeId };
|
|
156
|
-
this.handles.set(options.sessionId, handle);
|
|
157
|
-
// Also watch for nativeId extracted lazily; update handle reference
|
|
158
|
-
const pollNative = setInterval(() => {
|
|
159
|
-
if (nativeId)
|
|
160
|
-
handle.nativeSessionId = nativeId;
|
|
161
|
-
if (done)
|
|
162
|
-
clearInterval(pollNative);
|
|
163
|
-
}, 100);
|
|
164
|
-
setTimeout(() => clearInterval(pollNative), 5000);
|
|
165
|
-
return {
|
|
166
|
-
id: options.sessionId,
|
|
167
|
-
nativeSessionId: nativeId,
|
|
168
|
-
pid: proc.pid,
|
|
169
|
-
cwd: options.cwd,
|
|
170
|
-
model: options.model,
|
|
171
|
-
handle,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
async send(session, message) {
|
|
175
|
-
const h = this.handles.get(session.id);
|
|
176
|
-
let nativeId = session.nativeSessionId || h?.nativeSessionId;
|
|
177
|
-
if (!nativeId)
|
|
178
|
-
throw new Error("No native session id for Codex resume");
|
|
179
|
-
const newSession = await this.start({
|
|
180
|
-
sessionId: session.id,
|
|
181
|
-
prompt: message,
|
|
182
|
-
cwd: session.cwd,
|
|
183
|
-
model: session.model,
|
|
184
|
-
resumeSessionId: nativeId,
|
|
185
|
-
});
|
|
186
|
-
session.pid = newSession.pid;
|
|
187
|
-
session.handle = newSession.handle;
|
|
188
|
-
session.nativeSessionId = newSession.handle?.nativeSessionId || nativeId;
|
|
189
|
-
}
|
|
190
|
-
async stop(session) {
|
|
191
|
-
const h = this.handles.get(session.id);
|
|
192
|
-
const proc = (h?.proc ?? session.handle?.proc);
|
|
193
|
-
if (!proc || proc.killed)
|
|
194
|
-
return;
|
|
195
|
-
try {
|
|
196
|
-
proc.kill("SIGTERM");
|
|
197
|
-
await new Promise((resolve) => {
|
|
198
|
-
let done = false;
|
|
199
|
-
const t = setTimeout(() => {
|
|
200
|
-
if (!done) {
|
|
201
|
-
try {
|
|
202
|
-
proc.kill("SIGKILL");
|
|
203
|
-
}
|
|
204
|
-
catch { }
|
|
205
|
-
;
|
|
206
|
-
done = true;
|
|
207
|
-
resolve();
|
|
208
|
-
}
|
|
209
|
-
}, 3000);
|
|
210
|
-
proc.once("close", () => { if (!done) {
|
|
211
|
-
clearTimeout(t);
|
|
212
|
-
done = true;
|
|
213
|
-
resolve();
|
|
214
|
-
} });
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
catch { }
|
|
218
|
-
}
|
|
219
|
-
async *events(session) {
|
|
220
|
-
const h = this.handles.get(session.id);
|
|
221
|
-
if (!h)
|
|
222
|
-
return;
|
|
223
|
-
let idx = 0;
|
|
224
|
-
while (true) {
|
|
225
|
-
while (idx < h.buffer.length)
|
|
226
|
-
yield h.buffer[idx++];
|
|
227
|
-
if (h.done && idx >= h.buffer.length)
|
|
228
|
-
break;
|
|
229
|
-
await new Promise((resolve) => {
|
|
230
|
-
const onEvent = () => { h.emitter.off("event", onEvent); h.emitter.off("done", onDone); resolve(); };
|
|
231
|
-
const onDone = () => { h.emitter.off("event", onEvent); h.emitter.off("done", onDone); resolve(); };
|
|
232
|
-
h.emitter.once("event", onEvent);
|
|
233
|
-
h.emitter.once("done", onDone);
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
getHandle(sessionId) {
|
|
238
|
-
return this.handles.get(sessionId);
|
|
239
|
-
}
|
|
240
100
|
}
|
|
241
101
|
//# sourceMappingURL=driver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/drivers/codex/driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/drivers/codex/driver.ts"],"names":[],"mappings":"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"}
|
package/dist/drivers/helpers.js
CHANGED
|
@@ -1,14 +1,54 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { execFile } from "node:child_process";
|
|
3
5
|
import { promisify } from "node:util";
|
|
4
6
|
const execFileAsync = promisify(execFile);
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
// Spawn the harness DETACHED with stdout/stderr in FILES instead of pipes.
|
|
8
|
+
// This is the codedeck equivalent of the operator's `nohup … > file`:
|
|
9
|
+
// - `detached` gives the child its own process group, so it survives the
|
|
10
|
+
// daemon (and anything that kills the daemon's group);
|
|
11
|
+
// - file output has no reader to lose — a restarting daemon cannot EPIPE the
|
|
12
|
+
// child, and cannot apply pipe backpressure that freezes it;
|
|
13
|
+
// - the output stays on disk, so the next daemon reattaches by tailing the
|
|
14
|
+
// same files from a persisted offset.
|
|
15
|
+
export function spawnDetached(opts) {
|
|
16
|
+
fs.mkdirSync(path.dirname(opts.stdoutPath), { recursive: true });
|
|
17
|
+
fs.mkdirSync(path.dirname(opts.stderrPath), { recursive: true });
|
|
18
|
+
const sizeOf = (p) => {
|
|
19
|
+
try {
|
|
20
|
+
return fs.statSync(p).size;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const stdoutOffset = sizeOf(opts.stdoutPath);
|
|
27
|
+
const stderrOffset = sizeOf(opts.stderrPath);
|
|
28
|
+
const out = fs.openSync(opts.stdoutPath, "a");
|
|
29
|
+
const err = fs.openSync(opts.stderrPath, "a");
|
|
30
|
+
try {
|
|
31
|
+
const proc = spawn(opts.cmd, opts.args, {
|
|
32
|
+
cwd: opts.cwd,
|
|
33
|
+
env: { ...process.env, ...opts.env },
|
|
34
|
+
detached: true,
|
|
35
|
+
// stdin is "ignore", never a closed pipe: a closed pipe works for some
|
|
36
|
+
// harnesses (they read EOF) but an OPEN one hangs forever waiting for
|
|
37
|
+
// input that never comes. "ignore" removes the whole failure class.
|
|
38
|
+
stdio: ["ignore", out, err],
|
|
39
|
+
});
|
|
40
|
+
return { proc, stdoutOffset, stderrOffset };
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
try {
|
|
44
|
+
fs.closeSync(out);
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
try {
|
|
48
|
+
fs.closeSync(err);
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
12
52
|
}
|
|
13
53
|
export async function detectBinary(cmd, versionArgs = ["--version"]) {
|
|
14
54
|
try {
|
|
@@ -44,36 +84,6 @@ export async function detectBinary(cmd, versionArgs = ["--version"]) {
|
|
|
44
84
|
return { installed: false, error: e instanceof Error ? e.message : String(e) };
|
|
45
85
|
}
|
|
46
86
|
}
|
|
47
|
-
export function createLineReader(stream, onLine, onError) {
|
|
48
|
-
let buf = "";
|
|
49
|
-
const handler = (chunk) => {
|
|
50
|
-
buf += chunk.toString();
|
|
51
|
-
let idx;
|
|
52
|
-
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
53
|
-
const line = buf.slice(0, idx).trim();
|
|
54
|
-
buf = buf.slice(idx + 1);
|
|
55
|
-
if (line) {
|
|
56
|
-
try {
|
|
57
|
-
onLine(line);
|
|
58
|
-
}
|
|
59
|
-
catch (e) {
|
|
60
|
-
onError?.(e);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
stream.on("data", handler);
|
|
66
|
-
const cleanup = () => {
|
|
67
|
-
stream.off("data", handler);
|
|
68
|
-
if (buf.trim()) {
|
|
69
|
-
try {
|
|
70
|
-
onLine(buf.trim());
|
|
71
|
-
}
|
|
72
|
-
catch { }
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
return cleanup;
|
|
76
|
-
}
|
|
77
87
|
export function safeJsonParse(s) {
|
|
78
88
|
try {
|
|
79
89
|
return JSON.parse(s);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/drivers/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/drivers/helpers.ts"],"names":[],"mappings":"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"}
|