codedeck 0.1.2 → 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/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
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { getPaths } from "../config/paths.js";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { FileTailer } from "./tailer.js";
|
|
6
|
+
import { spawnDetached } from "./helpers.js";
|
|
7
|
+
import { killTree, processAlive, processGroupAlive, processStartTime, sleep } from "../utils/process.js";
|
|
8
|
+
export function sessionLogPaths(sessionId) {
|
|
9
|
+
const { logsDir } = getPaths();
|
|
10
|
+
return {
|
|
11
|
+
stdoutPath: path.join(logsDir, `${sessionId}.ndjson`),
|
|
12
|
+
stderrPath: path.join(logsDir, `${sessionId}.stderr.log`),
|
|
13
|
+
metadataPath: path.join(logsDir, `${sessionId}.process.json`),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function readSessionProcessMetadata(sessionId) {
|
|
17
|
+
try {
|
|
18
|
+
const value = JSON.parse(fs.readFileSync(sessionLogPaths(sessionId).metadataPath, "utf8"));
|
|
19
|
+
if (typeof value.pid !== "number" || !Number.isInteger(value.pid) || value.pid <= 0)
|
|
20
|
+
return undefined;
|
|
21
|
+
return {
|
|
22
|
+
pid: value.pid,
|
|
23
|
+
pidStartTime: typeof value.pidStartTime === "string" ? value.pidStartTime : undefined,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Shared session wire-up for all drivers: detached spawn with file output
|
|
31
|
+
// (or reattach to files of a process that outlived the daemon), incremental
|
|
32
|
+
// parse into normalized events, terminal synthesis on silent death, and the
|
|
33
|
+
// buffer+emitter event loop drivers expose through `events()`.
|
|
34
|
+
export class SessionRuntime {
|
|
35
|
+
sessionId;
|
|
36
|
+
hooks;
|
|
37
|
+
pid;
|
|
38
|
+
emitter = new EventEmitter();
|
|
39
|
+
buffer = [];
|
|
40
|
+
done = false;
|
|
41
|
+
exitCode = null;
|
|
42
|
+
nativeSessionId;
|
|
43
|
+
proc;
|
|
44
|
+
tailer;
|
|
45
|
+
stderrTailer;
|
|
46
|
+
stderrBuf = "";
|
|
47
|
+
exitWatch = null;
|
|
48
|
+
expectedStartTime;
|
|
49
|
+
stopRequested = false;
|
|
50
|
+
safeLogOffset = 0;
|
|
51
|
+
safeStderrOffset = 0;
|
|
52
|
+
yieldedEvents = 0;
|
|
53
|
+
// A raw line can normalize to several events (e.g. opencode message emits
|
|
54
|
+
// message + text.delta). Keep the safe offset behind the line until the
|
|
55
|
+
// final event from that line has been yielded to the daemon. A line that
|
|
56
|
+
// normalizes to no event gets the same treatment: read-ahead must never
|
|
57
|
+
// move the cursor past an earlier buffered event.
|
|
58
|
+
eventBoundaries = new WeakMap();
|
|
59
|
+
pendingNoEvent = { log: null, stderr: null };
|
|
60
|
+
constructor(sessionId, hooks) {
|
|
61
|
+
this.sessionId = sessionId;
|
|
62
|
+
this.hooks = hooks;
|
|
63
|
+
}
|
|
64
|
+
static spawn(opts) {
|
|
65
|
+
const rt = new SessionRuntime(opts.sessionId, opts.hooks);
|
|
66
|
+
if (opts.nativeSessionId)
|
|
67
|
+
rt.nativeSessionId = opts.nativeSessionId;
|
|
68
|
+
const paths = sessionLogPaths(opts.sessionId);
|
|
69
|
+
const { proc, stdoutOffset, stderrOffset } = spawnDetached({
|
|
70
|
+
cmd: opts.cmd,
|
|
71
|
+
args: opts.args,
|
|
72
|
+
cwd: opts.cwd,
|
|
73
|
+
stdoutPath: paths.stdoutPath,
|
|
74
|
+
stderrPath: paths.stderrPath,
|
|
75
|
+
});
|
|
76
|
+
rt.proc = proc;
|
|
77
|
+
rt.pid = proc.pid;
|
|
78
|
+
rt.expectedStartTime = processStartTime(rt.pid);
|
|
79
|
+
try {
|
|
80
|
+
// Sidecar closes the tiny spawn→SQLite-update window. Rename makes the
|
|
81
|
+
// record all-or-nothing if the daemon dies during serialization.
|
|
82
|
+
const tempPath = `${paths.metadataPath}.tmp-${process.pid}`;
|
|
83
|
+
fs.writeFileSync(tempPath, JSON.stringify({ pid: rt.pid, pidStartTime: rt.expectedStartTime }) + "\n", "utf8");
|
|
84
|
+
fs.renameSync(tempPath, paths.metadataPath);
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
87
|
+
rt.wire(paths, stdoutOffset, stderrOffset);
|
|
88
|
+
// Spawn-level failure (ENOENT = binary missing): infra, not task.
|
|
89
|
+
proc.on("error", (err) => {
|
|
90
|
+
const failure = {
|
|
91
|
+
code: "SPAWN_FAILED",
|
|
92
|
+
blame: "infra",
|
|
93
|
+
retryable: err.code !== "ENOENT",
|
|
94
|
+
detail: err.message,
|
|
95
|
+
};
|
|
96
|
+
rt.push({
|
|
97
|
+
type: "session.failed",
|
|
98
|
+
sessionId: opts.sessionId,
|
|
99
|
+
timestamp: new Date().toISOString(),
|
|
100
|
+
error: err.message,
|
|
101
|
+
failure,
|
|
102
|
+
raw: err,
|
|
103
|
+
});
|
|
104
|
+
rt.finish(null, null);
|
|
105
|
+
});
|
|
106
|
+
return rt;
|
|
107
|
+
}
|
|
108
|
+
// Rebuild the event feed for a session whose detached process outlived a
|
|
109
|
+
// daemon restart: tail the log files from the persisted offsets. Nothing
|
|
110
|
+
// is spawned; if the pid is already dead, the runtime drains the files and
|
|
111
|
+
// classifies the death like any other exit.
|
|
112
|
+
static reattach(opts) {
|
|
113
|
+
const rt = new SessionRuntime(opts.sessionId, opts.hooks);
|
|
114
|
+
rt.pid = opts.pid;
|
|
115
|
+
rt.expectedStartTime = opts.pidStartTime;
|
|
116
|
+
if (opts.nativeSessionId)
|
|
117
|
+
rt.nativeSessionId = opts.nativeSessionId;
|
|
118
|
+
const paths = sessionLogPaths(opts.sessionId);
|
|
119
|
+
rt.wire(paths, opts.logOffset ?? 0, opts.stderrOffset ?? 0);
|
|
120
|
+
return rt;
|
|
121
|
+
}
|
|
122
|
+
wire(paths, stdoutOffset, stderrOffset) {
|
|
123
|
+
this.safeLogOffset = stdoutOffset;
|
|
124
|
+
this.safeStderrOffset = stderrOffset;
|
|
125
|
+
this.stderrTailer = new FileTailer(paths.stderrPath, {
|
|
126
|
+
startOffset: stderrOffset,
|
|
127
|
+
onLine: (line, offset) => {
|
|
128
|
+
this.stderrBuf = (this.stderrBuf + line + "\n").slice(-65536);
|
|
129
|
+
this.handleLine("stderr", line, offset);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
this.tailer = new FileTailer(paths.stdoutPath, {
|
|
133
|
+
startOffset: stdoutOffset,
|
|
134
|
+
onLine: (line, offset) => this.handleLine("log", line, offset),
|
|
135
|
+
});
|
|
136
|
+
if (this.proc) {
|
|
137
|
+
// Same-lifetime case: the ChildProcess observes the pid directly.
|
|
138
|
+
this.proc.on("exit", (code, signal) => this.finish(code, signal));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Cross-restart case: no ChildProcess — poll the pid until it is gone,
|
|
142
|
+
// or until its start identity changes (PID reuse), then drain the files.
|
|
143
|
+
this.exitWatch = setInterval(() => {
|
|
144
|
+
if (this.done) {
|
|
145
|
+
if (this.exitWatch)
|
|
146
|
+
clearInterval(this.exitWatch);
|
|
147
|
+
this.exitWatch = null;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (this.processMatches())
|
|
151
|
+
return;
|
|
152
|
+
if (this.exitWatch)
|
|
153
|
+
clearInterval(this.exitWatch);
|
|
154
|
+
this.exitWatch = null;
|
|
155
|
+
// Grace so the harness finishes its final write before we drain.
|
|
156
|
+
void sleep(300).then(() => this.finish(null, null));
|
|
157
|
+
}, 500);
|
|
158
|
+
}
|
|
159
|
+
this.tailer.start();
|
|
160
|
+
this.stderrTailer.start();
|
|
161
|
+
}
|
|
162
|
+
processMatches() {
|
|
163
|
+
if (!processAlive(this.pid))
|
|
164
|
+
return processGroupAlive(this.pid);
|
|
165
|
+
if (this.expectedStartTime === undefined)
|
|
166
|
+
return true;
|
|
167
|
+
const current = processStartTime(this.pid);
|
|
168
|
+
return current !== undefined && current === this.expectedStartTime;
|
|
169
|
+
}
|
|
170
|
+
handleLine(stream, line, offset) {
|
|
171
|
+
const before = this.buffer.length;
|
|
172
|
+
try {
|
|
173
|
+
this.hooks.onLine(line, this.lineCtx(stream === "stderr"));
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
177
|
+
// A malformed/unexpected frame must not crash the daemon or strand the
|
|
178
|
+
// detached harness. Surface it as a normal event and keep tailing.
|
|
179
|
+
this.push({
|
|
180
|
+
type: "error",
|
|
181
|
+
sessionId: this.sessionId,
|
|
182
|
+
timestamp: new Date().toISOString(),
|
|
183
|
+
error: `failed to parse ${stream} line: ${message}`,
|
|
184
|
+
raw: line,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const added = this.buffer.length - before;
|
|
188
|
+
if (added === 0) {
|
|
189
|
+
// Do not advance immediately if earlier events are still buffered: this
|
|
190
|
+
// line may have been read ahead before the daemon persisted them.
|
|
191
|
+
this.pendingNoEvent[stream] = { beforeEventCount: this.buffer.length, offset };
|
|
192
|
+
this.flushPendingOffsets();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
for (let index = before; index < this.buffer.length; index += 1) {
|
|
196
|
+
const event = this.buffer[index];
|
|
197
|
+
event.sourceKey = `${stream}:${offset}:${index - before}`;
|
|
198
|
+
this.eventBoundaries.set(event, { stream, offset, last: index === this.buffer.length - 1 });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
lineCtx(isStderr) {
|
|
202
|
+
return {
|
|
203
|
+
sessionId: this.sessionId,
|
|
204
|
+
push: this.push,
|
|
205
|
+
setNativeId: this.setNativeId,
|
|
206
|
+
isStderr,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
push = (ev) => {
|
|
210
|
+
ev.sessionId = this.sessionId;
|
|
211
|
+
this.buffer.push(ev);
|
|
212
|
+
this.emitter.emit("event", ev);
|
|
213
|
+
};
|
|
214
|
+
setNativeId = (id) => {
|
|
215
|
+
if (id && !this.nativeSessionId)
|
|
216
|
+
this.nativeSessionId = id;
|
|
217
|
+
};
|
|
218
|
+
markSafeOffset(stream, offset) {
|
|
219
|
+
if (stream === "log")
|
|
220
|
+
this.safeLogOffset = Math.max(this.safeLogOffset, offset);
|
|
221
|
+
else
|
|
222
|
+
this.safeStderrOffset = Math.max(this.safeStderrOffset, offset);
|
|
223
|
+
}
|
|
224
|
+
flushPendingOffsets() {
|
|
225
|
+
for (const stream of ["log", "stderr"]) {
|
|
226
|
+
const pending = this.pendingNoEvent[stream];
|
|
227
|
+
if (pending && pending.beforeEventCount <= this.yieldedEvents) {
|
|
228
|
+
this.markSafeOffset(stream, pending.offset);
|
|
229
|
+
this.pendingNoEvent[stream] = null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
markEventYielded(event) {
|
|
234
|
+
const boundary = this.eventBoundaries.get(event);
|
|
235
|
+
if (boundary?.last)
|
|
236
|
+
this.markSafeOffset(boundary.stream, boundary.offset);
|
|
237
|
+
this.flushPendingOffsets();
|
|
238
|
+
}
|
|
239
|
+
finish(exitCode, signal) {
|
|
240
|
+
if (this.done)
|
|
241
|
+
return;
|
|
242
|
+
// Final drain so lines written just before exit are parsed before any
|
|
243
|
+
// terminal synthesis looks at the buffer.
|
|
244
|
+
this.tailer.flush();
|
|
245
|
+
this.stderrTailer.flush();
|
|
246
|
+
this.exitCode = exitCode;
|
|
247
|
+
this.done = true;
|
|
248
|
+
const hasTerminal = this.buffer.some((e) => e.type === "session.completed" || e.type === "session.failed");
|
|
249
|
+
if (!hasTerminal && !this.stopRequested) {
|
|
250
|
+
const hasMessage = this.buffer.some((e) => e.type === "message" || e.type === "text.delta");
|
|
251
|
+
for (const ev of this.hooks.synthesizeTerminal({
|
|
252
|
+
sessionId: this.sessionId,
|
|
253
|
+
exitCode,
|
|
254
|
+
signal,
|
|
255
|
+
hasTerminal,
|
|
256
|
+
hasMessage,
|
|
257
|
+
stderr: this.stderrBuf,
|
|
258
|
+
})) {
|
|
259
|
+
this.push(ev);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
this.emitter.emit("done");
|
|
263
|
+
this.tailer.stop();
|
|
264
|
+
this.stderrTailer.stop();
|
|
265
|
+
}
|
|
266
|
+
// Byte offsets of the last fully yielded line in each file — persisted by
|
|
267
|
+
// the daemon so a reattaching runtime does not replay stored events.
|
|
268
|
+
get offsets() {
|
|
269
|
+
return { log: this.safeLogOffset, stderr: this.safeStderrOffset };
|
|
270
|
+
}
|
|
271
|
+
get drained() {
|
|
272
|
+
return this.done && this.yieldedEvents >= this.buffer.length;
|
|
273
|
+
}
|
|
274
|
+
async stop(graceMs = 3000) {
|
|
275
|
+
if (!this.processMatches()) {
|
|
276
|
+
// The process raced us to exit (or changed identity). Close this runtime
|
|
277
|
+
// without synthesizing a conflicting harness failure; the daemon owns
|
|
278
|
+
// the single explicit stopped terminal event.
|
|
279
|
+
this.stopRequested = true;
|
|
280
|
+
this.finish(null, "SIGTERM");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
this.stopRequested = true;
|
|
284
|
+
// Deliberately does NOT cancel the pid-death watcher: killing the process
|
|
285
|
+
// is how the runtime learns it died (exit event or poll) and concludes the
|
|
286
|
+
// event stream. If polling has not fired by the time killTree returns,
|
|
287
|
+
// finish synchronously once the pid is gone.
|
|
288
|
+
await killTree(this.pid, graceMs, this.expectedStartTime);
|
|
289
|
+
if (!processAlive(this.pid) && !processGroupAlive(this.pid))
|
|
290
|
+
this.finish(null, "SIGTERM");
|
|
291
|
+
}
|
|
292
|
+
async *events() {
|
|
293
|
+
let idx = 0;
|
|
294
|
+
while (true) {
|
|
295
|
+
while (idx < this.buffer.length) {
|
|
296
|
+
const event = this.buffer[idx++];
|
|
297
|
+
this.yieldedEvents += 1;
|
|
298
|
+
this.markEventYielded(event);
|
|
299
|
+
yield event;
|
|
300
|
+
}
|
|
301
|
+
if (this.done && idx >= this.buffer.length)
|
|
302
|
+
break;
|
|
303
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
304
|
+
const onEvent = () => {
|
|
305
|
+
this.emitter.off("event", onEvent);
|
|
306
|
+
this.emitter.off("done", onDone);
|
|
307
|
+
resolve();
|
|
308
|
+
};
|
|
309
|
+
const onDone = () => {
|
|
310
|
+
this.emitter.off("event", onEvent);
|
|
311
|
+
this.emitter.off("done", onDone);
|
|
312
|
+
resolve();
|
|
313
|
+
};
|
|
314
|
+
this.emitter.once("event", onEvent);
|
|
315
|
+
this.emitter.once("done", onDone);
|
|
316
|
+
await promise;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Shared helper for driver hooks: pull a native session id out of a line's
|
|
321
|
+
// parsed event or raw JSON without duplicating try/catch in every driver.
|
|
322
|
+
export function nativeIdFrom(obj, keys) {
|
|
323
|
+
if (!obj || typeof obj !== "object")
|
|
324
|
+
return undefined;
|
|
325
|
+
const record = obj;
|
|
326
|
+
for (const key of keys) {
|
|
327
|
+
const value = record[key];
|
|
328
|
+
if (typeof value === "string" && value)
|
|
329
|
+
return value;
|
|
330
|
+
}
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
//# sourceMappingURL=session-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-runtime.js","sourceRoot":"","sources":["../../src/drivers/session-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
|
+
const MAX_READ_BYTES = 64 * 1024;
|
|
4
|
+
// Incremental line reader over a FILE — the replacement for stdio pipes.
|
|
5
|
+
// The harness writes its output to disk, so a daemon restart never breaks
|
|
6
|
+
// the stream: the new daemon reopens the file at the persisted offset and
|
|
7
|
+
// keeps parsing where the old one stopped. A pipe, by contrast, dies with
|
|
8
|
+
// the daemon and EPIPEs the child (the crash class this removes).
|
|
9
|
+
export class FileTailer {
|
|
10
|
+
filePath;
|
|
11
|
+
opts;
|
|
12
|
+
// Bytes actually read (including a partial trailing line held back).
|
|
13
|
+
readOffset = 0;
|
|
14
|
+
// Bytes up to the end of the last COMPLETE line — the persistable cursor.
|
|
15
|
+
lineOffset = 0;
|
|
16
|
+
leftover = "";
|
|
17
|
+
decoder = new StringDecoder("utf8");
|
|
18
|
+
decoderEnded = false;
|
|
19
|
+
watcher = null;
|
|
20
|
+
pollTimer = null;
|
|
21
|
+
stopped = false;
|
|
22
|
+
constructor(filePath, opts) {
|
|
23
|
+
this.filePath = filePath;
|
|
24
|
+
this.opts = opts;
|
|
25
|
+
}
|
|
26
|
+
start() {
|
|
27
|
+
if (this.stopped)
|
|
28
|
+
return;
|
|
29
|
+
const start = this.opts.startOffset ?? 0;
|
|
30
|
+
this.readOffset = start;
|
|
31
|
+
this.lineOffset = start;
|
|
32
|
+
this.pump();
|
|
33
|
+
try {
|
|
34
|
+
this.watcher = fs.watch(this.filePath, () => this.pump());
|
|
35
|
+
// The file may not exist yet (spawn raced us) or may sit on a mount
|
|
36
|
+
// without watch support — the poll below covers both.
|
|
37
|
+
this.watcher.on("error", () => { });
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
this.pollTimer = setInterval(() => this.pump(), this.opts.pollMs ?? 300);
|
|
41
|
+
}
|
|
42
|
+
stop() {
|
|
43
|
+
this.stopped = true;
|
|
44
|
+
try {
|
|
45
|
+
this.watcher?.close();
|
|
46
|
+
}
|
|
47
|
+
catch { }
|
|
48
|
+
if (this.pollTimer)
|
|
49
|
+
clearInterval(this.pollTimer);
|
|
50
|
+
this.watcher = null;
|
|
51
|
+
this.pollTimer = null;
|
|
52
|
+
}
|
|
53
|
+
// Byte position after the last fully consumed line. A partial trailing
|
|
54
|
+
// line is NOT counted, so a persisted offset never re-emits half a line
|
|
55
|
+
// after a restart.
|
|
56
|
+
get consumedOffset() {
|
|
57
|
+
return this.lineOffset;
|
|
58
|
+
}
|
|
59
|
+
// Drain the entire file to EOF, including a partial trailing line. The
|
|
60
|
+
// regular pump is bounded to avoid allocating the whole unread history;
|
|
61
|
+
// flush loops over bounded chunks because the process is known to be dead.
|
|
62
|
+
flush() {
|
|
63
|
+
if (this.stopped)
|
|
64
|
+
return;
|
|
65
|
+
const deadline = Date.now() + 1000;
|
|
66
|
+
while (Date.now() < deadline) {
|
|
67
|
+
const before = this.readOffset;
|
|
68
|
+
this.pump();
|
|
69
|
+
if (this.readOffset === before)
|
|
70
|
+
break;
|
|
71
|
+
let size;
|
|
72
|
+
try {
|
|
73
|
+
size = fs.statSync(this.filePath).size;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
if (size <= this.readOffset)
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
this.finishPartial();
|
|
82
|
+
}
|
|
83
|
+
pump() {
|
|
84
|
+
if (this.stopped)
|
|
85
|
+
return;
|
|
86
|
+
let size;
|
|
87
|
+
try {
|
|
88
|
+
size = fs.statSync(this.filePath).size;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return; // not created yet
|
|
92
|
+
}
|
|
93
|
+
if (size < this.readOffset) {
|
|
94
|
+
// Truncated (log rewritten): both cursors and the UTF-8 decoder restart.
|
|
95
|
+
this.readOffset = 0;
|
|
96
|
+
this.lineOffset = 0;
|
|
97
|
+
this.leftover = "";
|
|
98
|
+
this.decoder = new StringDecoder("utf8");
|
|
99
|
+
this.decoderEnded = false;
|
|
100
|
+
}
|
|
101
|
+
if (size === this.readOffset)
|
|
102
|
+
return;
|
|
103
|
+
let chunk;
|
|
104
|
+
try {
|
|
105
|
+
const fd = fs.openSync(this.filePath, "r");
|
|
106
|
+
try {
|
|
107
|
+
const len = Math.min(size - this.readOffset, MAX_READ_BYTES);
|
|
108
|
+
const buf = Buffer.allocUnsafe(len);
|
|
109
|
+
const bytesRead = fs.readSync(fd, buf, 0, len, this.readOffset);
|
|
110
|
+
if (bytesRead === 0)
|
|
111
|
+
return;
|
|
112
|
+
this.readOffset += bytesRead;
|
|
113
|
+
chunk = this.decoder.write(buf.subarray(0, bytesRead));
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
fs.closeSync(fd);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.processText(chunk);
|
|
123
|
+
}
|
|
124
|
+
processText(chunk) {
|
|
125
|
+
const data = this.leftover + chunk;
|
|
126
|
+
const lines = data.split("\n");
|
|
127
|
+
this.leftover = lines.pop() ?? "";
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
this.lineOffset += Buffer.byteLength(line, "utf8") + 1;
|
|
130
|
+
// Empty lines still advance the safe cursor; parsers may ignore them.
|
|
131
|
+
this.opts.onLine(line, this.lineOffset);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
finishPartial() {
|
|
135
|
+
if (!this.decoderEnded) {
|
|
136
|
+
const finalChunk = this.decoder.end();
|
|
137
|
+
this.decoderEnded = true;
|
|
138
|
+
if (finalChunk)
|
|
139
|
+
this.processText(finalChunk);
|
|
140
|
+
}
|
|
141
|
+
if (this.leftover) {
|
|
142
|
+
const line = this.leftover;
|
|
143
|
+
this.lineOffset += Buffer.byteLength(line, "utf8");
|
|
144
|
+
this.leftover = "";
|
|
145
|
+
this.opts.onLine(line, this.lineOffset);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=tailer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailer.js","sourceRoot":"","sources":["../../src/drivers/tailer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { classifyFailure } from "../core/errors.js";
|
|
2
|
+
// Pure so the exit-handler decision is testable without spawning a harness.
|
|
3
|
+
// When a harness dies it often does so WITHOUT emitting a terminal frame —
|
|
4
|
+
// the EPIPE/signal class of crash. This decides what the driver reports
|
|
5
|
+
// instead. A ghost "completed" is the one outcome it refuses to produce: an
|
|
6
|
+
// agent that sees completed proceeds on missing work, while a false "failed"
|
|
7
|
+
// only costs one retry of a retryable session.
|
|
8
|
+
export function synthesizeTerminalEvent(input) {
|
|
9
|
+
if (input.hasTerminal)
|
|
10
|
+
return null;
|
|
11
|
+
const ts = new Date().toISOString();
|
|
12
|
+
if (input.exitCode === 0) {
|
|
13
|
+
// Exit 0 with produced output is completion. Exit 0 with NO output but
|
|
14
|
+
// stderr content is how "exit 0 anyway" crashes look — treat as failure.
|
|
15
|
+
if (!input.hasMessage && input.stderr.trim()) {
|
|
16
|
+
return {
|
|
17
|
+
type: "session.failed",
|
|
18
|
+
sessionId: input.sessionId,
|
|
19
|
+
timestamp: ts,
|
|
20
|
+
error: input.stderr.slice(0, 2000),
|
|
21
|
+
exitCode: 0,
|
|
22
|
+
failure: classifyFailure(input.stderr, 0, input.signal ?? null),
|
|
23
|
+
raw: { stderr: input.stderr.slice(0, 2000) },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
type: "session.completed",
|
|
28
|
+
sessionId: input.sessionId,
|
|
29
|
+
timestamp: ts,
|
|
30
|
+
reason: "exit 0",
|
|
31
|
+
exitCode: 0,
|
|
32
|
+
raw: { stderr: input.stderr.slice(0, 2000) },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// exitCode null (signal death, or daemon was restarted and only learned of
|
|
36
|
+
// the death by polling the pid): there is no exit code to report — say so
|
|
37
|
+
// instead of inventing one.
|
|
38
|
+
const errorText = input.stderr.trim()
|
|
39
|
+
? input.stderr.slice(0, 2000)
|
|
40
|
+
: input.exitCode != null
|
|
41
|
+
? `${input.harness} exited with code ${input.exitCode}`
|
|
42
|
+
: `${input.harness} exited without reporting a terminal event`;
|
|
43
|
+
return {
|
|
44
|
+
type: "session.failed",
|
|
45
|
+
sessionId: input.sessionId,
|
|
46
|
+
timestamp: ts,
|
|
47
|
+
error: errorText,
|
|
48
|
+
exitCode: input.exitCode ?? 1,
|
|
49
|
+
failure: classifyFailure(input.stderr, input.exitCode, input.signal ?? null),
|
|
50
|
+
raw: { stderr: input.stderr.slice(0, 2000) },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/drivers/terminal.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,4EAA4E;AAC5E,2EAA2E;AAC3E,wEAAwE;AACxE,4EAA4E;AAC5E,6EAA6E;AAC7E,+CAA+C;AAC/C,MAAM,UAAU,uBAAuB,CAAC,KAQvC;IACC,IAAI,KAAK,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpC,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QACzB,uEAAuE;QACvE,yEAAyE;QACzE,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,OAAO;gBACL,IAAI,EAAE,gBAAgB;gBACtB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,SAAS,EAAE,EAAE;gBACb,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;gBAClC,QAAQ,EAAE,CAAC;gBACX,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC;gBAC/D,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE;aAC/B,CAAC;QAClB,CAAC;QACD,OAAO;YACL,IAAI,EAAE,mBAAmB;YACzB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,SAAS,EAAE,EAAE;YACb,MAAM,EAAE,QAAQ;YAChB,QAAQ,EAAE,CAAC;YACX,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE;SAC/B,CAAC;IAClB,CAAC;IACD,2EAA2E;IAC3E,0EAA0E;IAC1E,4BAA4B;IAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;QACnC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;QAC7B,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI;YACtB,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,qBAAqB,KAAK,CAAC,QAAQ,EAAE;YACvD,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,4CAA4C,CAAC;IACnE,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,EAAE;QACb,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,CAAC;QAC7B,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC;QAC5E,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE;KAC/B,CAAC;AAClB,CAAC"}
|
package/dist/store/database.js
CHANGED
|
@@ -32,6 +32,7 @@ export class Database {
|
|
|
32
32
|
branch TEXT,
|
|
33
33
|
base_commit TEXT,
|
|
34
34
|
pid INTEGER,
|
|
35
|
+
pid_start_time TEXT,
|
|
35
36
|
created_at TEXT NOT NULL,
|
|
36
37
|
updated_at TEXT NOT NULL,
|
|
37
38
|
completed_at TEXT,
|
|
@@ -39,7 +40,9 @@ export class Database {
|
|
|
39
40
|
usage_output_tokens INTEGER,
|
|
40
41
|
usage_cached_tokens INTEGER,
|
|
41
42
|
usage_cost REAL,
|
|
42
|
-
last_event TEXT
|
|
43
|
+
last_event TEXT,
|
|
44
|
+
effort TEXT,
|
|
45
|
+
fast INTEGER NOT NULL DEFAULT 0
|
|
43
46
|
);
|
|
44
47
|
|
|
45
48
|
CREATE TABLE IF NOT EXISTS events (
|
|
@@ -50,6 +53,7 @@ export class Database {
|
|
|
50
53
|
timestamp TEXT NOT NULL,
|
|
51
54
|
normalized_payload TEXT NOT NULL,
|
|
52
55
|
raw_payload TEXT,
|
|
56
|
+
source_key TEXT,
|
|
53
57
|
FOREIGN KEY(session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
54
58
|
);
|
|
55
59
|
|
|
@@ -57,6 +61,29 @@ export class Database {
|
|
|
57
61
|
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
58
62
|
CREATE INDEX IF NOT EXISTS idx_sessions_created ON sessions(created_at DESC);
|
|
59
63
|
`);
|
|
64
|
+
this.addMissingColumns();
|
|
65
|
+
}
|
|
66
|
+
// CREATE TABLE IF NOT EXISTS is a no-op on databases that already exist, so
|
|
67
|
+
// columns added after the first release have to be applied explicitly or
|
|
68
|
+
// every existing ~/.run-agent/run-agent.db throws "no such column".
|
|
69
|
+
addMissingColumns() {
|
|
70
|
+
const existing = new Set(this.db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
71
|
+
const additions = [
|
|
72
|
+
["effort", "TEXT"],
|
|
73
|
+
["fast", "INTEGER NOT NULL DEFAULT 0"],
|
|
74
|
+
["failure", "TEXT"],
|
|
75
|
+
["log_offset", "INTEGER"],
|
|
76
|
+
["stderr_offset", "INTEGER"],
|
|
77
|
+
["pid_start_time", "TEXT"],
|
|
78
|
+
];
|
|
79
|
+
for (const [name, type] of additions) {
|
|
80
|
+
if (!existing.has(name))
|
|
81
|
+
this.db.exec(`ALTER TABLE sessions ADD COLUMN ${name} ${type}`);
|
|
82
|
+
}
|
|
83
|
+
const eventColumns = new Set(this.db.prepare(`PRAGMA table_info(events)`).all().map((c) => c.name));
|
|
84
|
+
if (!eventColumns.has("source_key"))
|
|
85
|
+
this.db.exec(`ALTER TABLE events ADD COLUMN source_key TEXT`);
|
|
86
|
+
this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_events_source_key ON events(session_id, source_key) WHERE source_key IS NOT NULL`);
|
|
60
87
|
}
|
|
61
88
|
getHandle() {
|
|
62
89
|
return this.db;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/store/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,OAAO,QAAQ;IACX,EAAE,CAAe;IACjB,MAAM,CAAS;IAEvB,YAAY,MAAe;QACzB,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC;QAC7B,UAAU,EAAE,CAAC;QACb,2BAA2B;QAC3B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/store/database.ts"],"names":[],"mappings":"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"}
|
package/dist/store/events.js
CHANGED
|
@@ -4,16 +4,21 @@ export class EventStore {
|
|
|
4
4
|
this.db = db;
|
|
5
5
|
}
|
|
6
6
|
append(sessionId, event, raw) {
|
|
7
|
+
if (event.sourceKey) {
|
|
8
|
+
const existing = this.db.prepare(`SELECT sequence FROM events WHERE session_id = ? AND source_key = ? LIMIT 1`).get(sessionId, event.sourceKey);
|
|
9
|
+
if (existing)
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
7
12
|
// Get next sequence
|
|
8
13
|
const row = this.db.prepare(`SELECT COALESCE(MAX(sequence), 0) as maxSeq FROM events WHERE session_id = ?`).get(sessionId);
|
|
9
14
|
const nextSeq = (row?.maxSeq ?? 0) + 1;
|
|
10
15
|
const normalized = JSON.stringify(event);
|
|
11
16
|
const rawStr = raw !== undefined ? JSON.stringify(raw) : event.raw !== undefined ? JSON.stringify(event.raw) : null;
|
|
12
17
|
const stmt = this.db.prepare(`
|
|
13
|
-
INSERT INTO events (session_id, sequence, type, timestamp, normalized_payload, raw_payload)
|
|
14
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
18
|
+
INSERT INTO events (session_id, sequence, type, timestamp, normalized_payload, raw_payload, source_key)
|
|
19
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
15
20
|
`);
|
|
16
|
-
|
|
21
|
+
stmt.run(sessionId, nextSeq, event.type, event.timestamp, normalized, rawStr, event.sourceKey ?? null);
|
|
17
22
|
// Update session last_event and updatedAt?
|
|
18
23
|
// Let daemon do it
|
|
19
24
|
return nextSeq;
|