codedeck 0.1.1 → 0.1.3
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 +50 -0
- package/dist/cli/commands/logs.js +5 -1
- package/dist/cli/commands/logs.js.map +1 -1
- package/dist/cli/commands/ps.js +40 -12
- package/dist/cli/commands/ps.js.map +1 -1
- package/dist/cli/commands/run.js +86 -24
- 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/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 +298 -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,37 @@
|
|
|
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 { parseClaudeLine } from "./parser.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
+
import { createRuntimeHooks, SessionDriver } from "../session-driver.js";
|
|
7
|
+
// Pure so the flag spellings are testable without spawning claude.
|
|
8
|
+
export function buildClaudeArgs(options) {
|
|
9
|
+
const args = ["-p", "--output-format", "stream-json", "--verbose"];
|
|
10
|
+
// Bypass permissions for automation (as spec allows).
|
|
11
|
+
args.push("--dangerously-skip-permissions");
|
|
12
|
+
if (options.model)
|
|
13
|
+
args.push("--model", options.model);
|
|
14
|
+
// Claude spells effort as a first-class flag and accepts the same levels as
|
|
15
|
+
// the others. It has NO service-tier equivalent, so `options.fast` is
|
|
16
|
+
// deliberately dropped here rather than translated into an invalid flag.
|
|
17
|
+
if (options.effort)
|
|
18
|
+
args.push("--effort", options.effort);
|
|
19
|
+
if (options.resumeSessionId)
|
|
20
|
+
args.push("--resume", options.resumeSessionId);
|
|
21
|
+
args.push(options.prompt);
|
|
22
|
+
return args;
|
|
23
|
+
}
|
|
24
|
+
export class ClaudeDriver extends SessionDriver {
|
|
6
25
|
id = "claude";
|
|
7
|
-
|
|
26
|
+
hooks = createRuntimeHooks({
|
|
27
|
+
parse: parseClaudeLine,
|
|
28
|
+
nativeKeys: ["session_id", "sessionId"],
|
|
29
|
+
harness: "Claude",
|
|
30
|
+
});
|
|
31
|
+
resumeError = "No native session id available for resume";
|
|
32
|
+
buildArgs(options) {
|
|
33
|
+
return buildClaudeArgs(options);
|
|
34
|
+
}
|
|
8
35
|
capabilities() {
|
|
9
36
|
return {
|
|
10
37
|
streaming: true,
|
|
@@ -22,16 +49,10 @@ export class ClaudeDriver {
|
|
|
22
49
|
const res = await detectBinary("claude");
|
|
23
50
|
if (!res.installed)
|
|
24
51
|
return { installed: false, error: "claude binary not found" };
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// For auth, we can check if ANTHROPIC_API_KEY or OAuth token exists
|
|
28
|
-
// Simplify: if binary exists, authenticated = true if we can run a dummy print? Too heavy
|
|
29
|
-
// Check for ~/.claude/.credentials.json or ANTHROPIC_API_KEY
|
|
52
|
+
// Lightweight auth heuristic: a credentials file or an env token counts as
|
|
53
|
+
// authenticated; otherwise unknown — driving decides.
|
|
30
54
|
let authenticated;
|
|
31
55
|
try {
|
|
32
|
-
const fs = await import("node:fs");
|
|
33
|
-
const os = await import("node:os");
|
|
34
|
-
const path = await import("node:path");
|
|
35
56
|
const credPath = path.join(os.homedir(), ".claude", ".credentials.json");
|
|
36
57
|
if (fs.existsSync(credPath))
|
|
37
58
|
authenticated = true;
|
|
@@ -51,197 +72,5 @@ export class ClaudeDriver {
|
|
|
51
72
|
details: "stream-json via claude -p",
|
|
52
73
|
};
|
|
53
74
|
}
|
|
54
|
-
async start(options) {
|
|
55
|
-
const args = ["-p", "--output-format", "stream-json", "--verbose"];
|
|
56
|
-
// Use bypass permissions for automation (as spec allows)
|
|
57
|
-
args.push("--dangerously-skip-permissions");
|
|
58
|
-
if (options.model) {
|
|
59
|
-
args.push("--model", options.model);
|
|
60
|
-
}
|
|
61
|
-
if (options.resumeSessionId) {
|
|
62
|
-
args.push("--resume", options.resumeSessionId);
|
|
63
|
-
}
|
|
64
|
-
// prompt as final arg
|
|
65
|
-
args.push(options.prompt);
|
|
66
|
-
const proc = spawn("claude", args, {
|
|
67
|
-
cwd: options.cwd,
|
|
68
|
-
env: { ...process.env },
|
|
69
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
70
|
-
});
|
|
71
|
-
proc.stdin?.end();
|
|
72
|
-
const emitter = new EventEmitter();
|
|
73
|
-
const buffer = [];
|
|
74
|
-
let nativeSessionId = options.resumeSessionId;
|
|
75
|
-
let done = false;
|
|
76
|
-
let exitCode = null;
|
|
77
|
-
const push = (ev) => {
|
|
78
|
-
// Enrich sessionId
|
|
79
|
-
ev.sessionId = options.sessionId;
|
|
80
|
-
if (ev.nativeSessionId)
|
|
81
|
-
nativeSessionId = ev.nativeSessionId;
|
|
82
|
-
// Also try to extract session_id from raw
|
|
83
|
-
if (!nativeSessionId && ev.raw?.session_id)
|
|
84
|
-
nativeSessionId = ev.raw.session_id;
|
|
85
|
-
if (!nativeSessionId && ev.raw?.sessionId)
|
|
86
|
-
nativeSessionId = ev.raw.sessionId;
|
|
87
|
-
buffer.push(ev);
|
|
88
|
-
emitter.emit("event", ev);
|
|
89
|
-
};
|
|
90
|
-
// Handle stdout lines
|
|
91
|
-
createLineReader(proc.stdout, (line) => {
|
|
92
|
-
const evs = parseClaudeLine(line, options.sessionId);
|
|
93
|
-
for (const ev of evs)
|
|
94
|
-
push(ev);
|
|
95
|
-
// Fallback: try to extract session_id even if no event
|
|
96
|
-
try {
|
|
97
|
-
const obj = JSON.parse(line);
|
|
98
|
-
if (obj.session_id && !nativeSessionId)
|
|
99
|
-
nativeSessionId = obj.session_id;
|
|
100
|
-
if (obj.sessionId && !nativeSessionId)
|
|
101
|
-
nativeSessionId = obj.sessionId;
|
|
102
|
-
}
|
|
103
|
-
catch { }
|
|
104
|
-
});
|
|
105
|
-
let stderrBuf = "";
|
|
106
|
-
proc.stderr?.on("data", (c) => (stderrBuf += c.toString()));
|
|
107
|
-
proc.on("close", (code) => {
|
|
108
|
-
exitCode = code;
|
|
109
|
-
done = true;
|
|
110
|
-
if (buffer.length === 0 || !buffer.some((e) => e.type === "session.completed" || e.type === "session.failed")) {
|
|
111
|
-
if (code === 0) {
|
|
112
|
-
push({
|
|
113
|
-
type: "session.completed",
|
|
114
|
-
sessionId: options.sessionId,
|
|
115
|
-
timestamp: new Date().toISOString(),
|
|
116
|
-
reason: "exit 0",
|
|
117
|
-
exitCode: 0,
|
|
118
|
-
raw: { stderr: stderrBuf.slice(0, 2000) },
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
push({
|
|
123
|
-
type: "session.failed",
|
|
124
|
-
sessionId: options.sessionId,
|
|
125
|
-
timestamp: new Date().toISOString(),
|
|
126
|
-
error: stderrBuf.slice(0, 2000) || `Claude exited with code ${code}`,
|
|
127
|
-
exitCode: code ?? 1,
|
|
128
|
-
raw: { stderr: stderrBuf.slice(0, 2000) },
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
emitter.emit("done");
|
|
133
|
-
});
|
|
134
|
-
proc.on("error", (err) => {
|
|
135
|
-
push({
|
|
136
|
-
type: "session.failed",
|
|
137
|
-
sessionId: options.sessionId,
|
|
138
|
-
timestamp: new Date().toISOString(),
|
|
139
|
-
error: err.message,
|
|
140
|
-
raw: err,
|
|
141
|
-
});
|
|
142
|
-
done = true;
|
|
143
|
-
emitter.emit("done");
|
|
144
|
-
});
|
|
145
|
-
const handle = { proc, emitter, buffer, done, exitCode, nativeSessionId };
|
|
146
|
-
this.handles.set(options.sessionId, handle);
|
|
147
|
-
// Wait a tick to capture initial events? Return immediately
|
|
148
|
-
// Native session id will be discovered asynchronously; daemon can poll handle.nativeSessionId
|
|
149
|
-
return {
|
|
150
|
-
id: options.sessionId,
|
|
151
|
-
nativeSessionId,
|
|
152
|
-
pid: proc.pid,
|
|
153
|
-
cwd: options.cwd,
|
|
154
|
-
model: options.model,
|
|
155
|
-
handle,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
async send(session, message) {
|
|
159
|
-
const h = this.handles.get(session.id);
|
|
160
|
-
// For claude, send means resume with new message as new process
|
|
161
|
-
// We don't have persistent stdin; we start a new process with --resume
|
|
162
|
-
if (!session.nativeSessionId && h?.nativeSessionId) {
|
|
163
|
-
session.nativeSessionId = h.nativeSessionId;
|
|
164
|
-
}
|
|
165
|
-
// If no nativeSessionId, we can't resume; throw
|
|
166
|
-
if (!session.nativeSessionId) {
|
|
167
|
-
throw new Error("No native session id available for resume");
|
|
168
|
-
}
|
|
169
|
-
// Spawn a new process for the follow-up turn, replace handle
|
|
170
|
-
const newSession = await this.start({
|
|
171
|
-
sessionId: session.id,
|
|
172
|
-
prompt: message,
|
|
173
|
-
cwd: session.cwd,
|
|
174
|
-
model: session.model,
|
|
175
|
-
resumeSessionId: session.nativeSessionId,
|
|
176
|
-
});
|
|
177
|
-
// Update session handle reference
|
|
178
|
-
session.pid = newSession.pid;
|
|
179
|
-
session.handle = newSession.handle;
|
|
180
|
-
session.nativeSessionId = newSession.nativeSessionId || session.nativeSessionId;
|
|
181
|
-
}
|
|
182
|
-
async stop(session) {
|
|
183
|
-
const h = this.handles.get(session.id);
|
|
184
|
-
const proc = (h?.proc ?? session.handle?.proc);
|
|
185
|
-
if (!proc || proc.killed)
|
|
186
|
-
return;
|
|
187
|
-
try {
|
|
188
|
-
proc.kill("SIGTERM");
|
|
189
|
-
await new Promise((resolve) => {
|
|
190
|
-
let done = false;
|
|
191
|
-
const t = setTimeout(() => {
|
|
192
|
-
if (!done) {
|
|
193
|
-
try {
|
|
194
|
-
proc.kill("SIGKILL");
|
|
195
|
-
}
|
|
196
|
-
catch { }
|
|
197
|
-
done = true;
|
|
198
|
-
resolve();
|
|
199
|
-
}
|
|
200
|
-
}, 3000);
|
|
201
|
-
proc.once("close", () => {
|
|
202
|
-
if (!done) {
|
|
203
|
-
clearTimeout(t);
|
|
204
|
-
done = true;
|
|
205
|
-
resolve();
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
catch { }
|
|
211
|
-
}
|
|
212
|
-
async *events(session) {
|
|
213
|
-
const h = this.handles.get(session.id);
|
|
214
|
-
if (!h)
|
|
215
|
-
return;
|
|
216
|
-
let idx = 0;
|
|
217
|
-
while (true) {
|
|
218
|
-
while (idx < h.buffer.length) {
|
|
219
|
-
yield h.buffer[idx++];
|
|
220
|
-
}
|
|
221
|
-
if (h.done && idx >= h.buffer.length)
|
|
222
|
-
break;
|
|
223
|
-
await new Promise((resolve) => {
|
|
224
|
-
const onEvent = () => {
|
|
225
|
-
h.emitter.off("event", onEvent);
|
|
226
|
-
h.emitter.off("done", onDone);
|
|
227
|
-
resolve();
|
|
228
|
-
};
|
|
229
|
-
const onDone = () => {
|
|
230
|
-
h.emitter.off("event", onEvent);
|
|
231
|
-
h.emitter.off("done", onDone);
|
|
232
|
-
resolve();
|
|
233
|
-
};
|
|
234
|
-
h.emitter.once("event", onEvent);
|
|
235
|
-
h.emitter.once("done", onDone);
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
// Helper for daemon to poll native id
|
|
240
|
-
getNativeId(sessionId) {
|
|
241
|
-
return this.handles.get(sessionId)?.nativeSessionId;
|
|
242
|
-
}
|
|
243
|
-
getHandle(sessionId) {
|
|
244
|
-
return this.handles.get(sessionId);
|
|
245
|
-
}
|
|
246
75
|
}
|
|
247
76
|
//# sourceMappingURL=driver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/drivers/claude/driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/drivers/claude/driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|