@kal-elsam/kairo-runtime 0.2.0 → 0.2.2
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/package.json +1 -1
- package/scripts/runtime-mvp-smoke.sh +152 -0
- package/src/cli.js +77 -4
- package/src/global/ink/orchestrator-app.js +365 -81
- package/src/global/ink/orchestrator-state.js +233 -60
- package/src/global/orchestrator.js +8 -39
- package/src/global/paths.js +13 -0
- package/src/global/profile.js +17 -2
- package/src/global/runtime/execution-adapters/claude.js +65 -0
- package/src/global/runtime/execution-adapters/codex.js +78 -0
- package/src/global/runtime/execution-adapters/create-execution-adapter.js +92 -0
- package/src/global/runtime/execution-adapters/cursor.js +104 -0
- package/src/global/runtime/execution-adapters/index.js +36 -0
- package/src/global/runtime/execution-adapters/opencode.js +38 -0
- package/src/global/runtime/run-cancel-signal.js +29 -0
- package/src/global/runtime/run-cli.js +221 -0
- package/src/global/runtime/run-events.js +144 -0
- package/src/global/runtime/run-handoff.js +71 -0
- package/src/global/runtime/run-liveness.js +28 -0
- package/src/global/runtime/run-manager.js +271 -0
- package/src/global/runtime/run-profile.js +93 -0
- package/src/global/runtime/run-redact.js +66 -0
- package/src/global/runtime/run-starting.js +13 -0
- package/src/global/runtime/run-store.js +159 -0
- package/src/global/runtime/run-supervisor-lock.js +37 -0
- package/src/global/runtime/run-supervisor-worker.js +12 -0
- package/src/global/runtime/run-supervisor.js +289 -0
- package/src/global/runtime/run-types.js +117 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { runPaths } from "../paths.js";
|
|
4
|
+
|
|
5
|
+
function lockPath(homeDir, runId) {
|
|
6
|
+
return `${runPaths(homeDir, runId).runDir}/supervisor.lock.json`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function writeSupervisorLock(homeDir, runId, lock) {
|
|
10
|
+
const { runDir } = runPaths(homeDir, runId);
|
|
11
|
+
await mkdir(runDir, { recursive: true });
|
|
12
|
+
await writeFile(lockPath(homeDir, runId), `${JSON.stringify(lock, null, 2)}\n`, "utf8");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function readSupervisorLock(homeDir, runId) {
|
|
16
|
+
const path = lockPath(homeDir, runId);
|
|
17
|
+
if (!existsSync(path)) return null;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function touchSupervisorLock(homeDir, runId, fields = {}) {
|
|
27
|
+
const current = await readSupervisorLock(homeDir, runId);
|
|
28
|
+
if (!current) return null;
|
|
29
|
+
|
|
30
|
+
const next = {
|
|
31
|
+
...current,
|
|
32
|
+
...fields,
|
|
33
|
+
lastHeartbeat: new Date().toISOString()
|
|
34
|
+
};
|
|
35
|
+
await writeSupervisorLock(homeDir, runId, next);
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { supervisePreparedRun } from "./run-supervisor.js";
|
|
2
|
+
import { deleteRunHandoff } from "./run-handoff.js";
|
|
3
|
+
|
|
4
|
+
const homeDir = process.env.KAIRO_SUPERVISOR_HOME;
|
|
5
|
+
const runId = process.env.KAIRO_SUPERVISOR_RUN_ID;
|
|
6
|
+
|
|
7
|
+
if (homeDir && runId) {
|
|
8
|
+
supervisePreparedRun({ homeDir, runId }).catch(async () => {
|
|
9
|
+
await deleteRunHandoff(homeDir, runId).catch(() => {});
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { RUN_STATES } from "./run-types.js";
|
|
5
|
+
import {
|
|
6
|
+
applyEventToMetadata,
|
|
7
|
+
createRunEvent,
|
|
8
|
+
normalizeAdapterEvent,
|
|
9
|
+
transitionRunState
|
|
10
|
+
} from "./run-events.js";
|
|
11
|
+
import {
|
|
12
|
+
appendRunEvent,
|
|
13
|
+
readRunState,
|
|
14
|
+
writeRunState
|
|
15
|
+
} from "./run-store.js";
|
|
16
|
+
import { resolveExecutionAdapter } from "./execution-adapters/index.js";
|
|
17
|
+
import { consumeRunHandoff } from "./run-handoff.js";
|
|
18
|
+
import { isRunCancelRequested } from "./run-cancel-signal.js";
|
|
19
|
+
import { readSupervisorLock, touchSupervisorLock, writeSupervisorLock } from "./run-supervisor-lock.js";
|
|
20
|
+
import { shouldPersistTranscript } from "./run-redact.js";
|
|
21
|
+
|
|
22
|
+
async function shouldPreserveCancelledState(homeDir, runId) {
|
|
23
|
+
const fresh = await readRunState(homeDir, runId);
|
|
24
|
+
if (fresh?.state === RUN_STATES.CANCELLED) {
|
|
25
|
+
return fresh;
|
|
26
|
+
}
|
|
27
|
+
if (await isRunCancelRequested(homeDir, runId)) {
|
|
28
|
+
return fresh ?? null;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const workerPath = fileURLToPath(new URL("./run-supervisor-worker.js", import.meta.url));
|
|
34
|
+
|
|
35
|
+
export function spawnDetachedSupervisor({ homeDir, runId, spawnImpl = spawn }) {
|
|
36
|
+
const child = spawnImpl(process.execPath, [workerPath], {
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
KAIRO_SUPERVISOR_HOME: homeDir,
|
|
40
|
+
KAIRO_SUPERVISOR_RUN_ID: runId
|
|
41
|
+
},
|
|
42
|
+
detached: true,
|
|
43
|
+
stdio: "ignore"
|
|
44
|
+
});
|
|
45
|
+
child.unref();
|
|
46
|
+
return child.pid ?? null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const forkDetachedSupervisor = spawnDetachedSupervisor;
|
|
50
|
+
|
|
51
|
+
export async function supervisePreparedRun({
|
|
52
|
+
homeDir,
|
|
53
|
+
runId,
|
|
54
|
+
follow = false,
|
|
55
|
+
timeoutMs = null,
|
|
56
|
+
spawnImpl = spawn,
|
|
57
|
+
cancelledRuns = null,
|
|
58
|
+
activeProcesses = null
|
|
59
|
+
}) {
|
|
60
|
+
const handoff = await consumeRunHandoff(homeDir, runId);
|
|
61
|
+
const adapter = resolveExecutionAdapter(handoff.agentId);
|
|
62
|
+
let metadata = await readRunState(homeDir, runId);
|
|
63
|
+
|
|
64
|
+
if (!metadata) {
|
|
65
|
+
throw new Error(`Run "${runId}" not found.`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const captureTranscript = handoff.captureTranscript === true;
|
|
69
|
+
const launch = adapter.buildLaunch({
|
|
70
|
+
task: handoff.task,
|
|
71
|
+
cwd: handoff.cwd,
|
|
72
|
+
model: handoff.model,
|
|
73
|
+
permissions: handoff.permissions ?? [],
|
|
74
|
+
profile: handoff.profile ?? null
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
metadata = {
|
|
78
|
+
...metadata,
|
|
79
|
+
state: RUN_STATES.RUNNING,
|
|
80
|
+
supervisorPid: process.pid,
|
|
81
|
+
updatedAt: new Date().toISOString()
|
|
82
|
+
};
|
|
83
|
+
await writeRunState(homeDir, metadata);
|
|
84
|
+
await writeSupervisorLock(homeDir, runId, {
|
|
85
|
+
supervisorPid: process.pid,
|
|
86
|
+
agentPid: null,
|
|
87
|
+
startedAt: new Date().toISOString(),
|
|
88
|
+
lastHeartbeat: new Date().toISOString()
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const child = spawnImpl(launch.command, launch.args, {
|
|
92
|
+
cwd: launch.cwd,
|
|
93
|
+
env: launch.env,
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
95
|
+
});
|
|
96
|
+
activeProcesses?.set(runId, child);
|
|
97
|
+
|
|
98
|
+
let stdoutBuffer = "";
|
|
99
|
+
let stderrBuffer = "";
|
|
100
|
+
let timeoutHandle = null;
|
|
101
|
+
let processing = Promise.resolve();
|
|
102
|
+
let stateWrites = Promise.resolve();
|
|
103
|
+
|
|
104
|
+
const enqueue = (work) => {
|
|
105
|
+
processing = processing.then(work);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const serializeStateWrite = (work) => {
|
|
109
|
+
stateWrites = stateWrites.then(work);
|
|
110
|
+
return stateWrites;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const persistEvent = async (event) => {
|
|
114
|
+
await serializeStateWrite(async () => {
|
|
115
|
+
metadata = applyEventToMetadata(metadata, event);
|
|
116
|
+
await appendRunEvent(homeDir, event, { captureTranscript: shouldPersistTranscript(captureTranscript) });
|
|
117
|
+
await writeRunState(homeDir, metadata);
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const handleLine = async (line, stream) => {
|
|
122
|
+
const structured = adapter.parseEventLine(line, { runId, cwd: handoff.cwd });
|
|
123
|
+
const normalized = structured
|
|
124
|
+
? normalizeAdapterEvent(adapter.id, structured, { captureTranscript })
|
|
125
|
+
: normalizeAdapterEvent(adapter.id, line, { captureTranscript });
|
|
126
|
+
|
|
127
|
+
if (!normalized) return;
|
|
128
|
+
|
|
129
|
+
const event = {
|
|
130
|
+
...normalized,
|
|
131
|
+
runId,
|
|
132
|
+
timestamp: normalized.timestamp ?? new Date().toISOString()
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (captureTranscript && (event.type === "agent.assistant" || event.type === "agent.result")) {
|
|
136
|
+
await persistEvent({
|
|
137
|
+
...createRunEvent({
|
|
138
|
+
runId,
|
|
139
|
+
type: "run.transcript",
|
|
140
|
+
data: event.data,
|
|
141
|
+
captureTranscript: true
|
|
142
|
+
}),
|
|
143
|
+
runId,
|
|
144
|
+
timestamp: event.timestamp
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await persistEvent(event);
|
|
149
|
+
|
|
150
|
+
if (follow && stream === "stdout") {
|
|
151
|
+
process.stdout.write(`${line}\n`);
|
|
152
|
+
}
|
|
153
|
+
if (follow && stream === "stderr") {
|
|
154
|
+
process.stderr.write(`${line}\n`);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const flushBuffer = async (buffer, stream) => {
|
|
159
|
+
const lines = buffer.split("\n");
|
|
160
|
+
const remainder = lines.pop() ?? "";
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
await handleLine(line, stream);
|
|
163
|
+
}
|
|
164
|
+
return remainder;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const completion = new Promise((resolve, reject) => {
|
|
168
|
+
child.on("error", async (error) => {
|
|
169
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
170
|
+
activeProcesses?.delete(runId);
|
|
171
|
+
|
|
172
|
+
await serializeStateWrite(async () => {
|
|
173
|
+
const preserved = await shouldPreserveCancelledState(homeDir, runId);
|
|
174
|
+
if (preserved) {
|
|
175
|
+
cancelledRuns?.delete(runId);
|
|
176
|
+
resolve(preserved.state === RUN_STATES.CANCELLED
|
|
177
|
+
? preserved
|
|
178
|
+
: { ...preserved, state: RUN_STATES.CANCELLED });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
metadata = transitionRunState(metadata, RUN_STATES.FAILED, {
|
|
183
|
+
error: error.message
|
|
184
|
+
});
|
|
185
|
+
await writeRunState(homeDir, metadata);
|
|
186
|
+
await appendRunEvent(homeDir, createRunEvent({
|
|
187
|
+
runId,
|
|
188
|
+
type: "run.failed",
|
|
189
|
+
data: { error: error.message }
|
|
190
|
+
}), { captureTranscript: shouldPersistTranscript(captureTranscript) });
|
|
191
|
+
});
|
|
192
|
+
reject(error);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
child.on("close", async (exitCode) => {
|
|
196
|
+
try {
|
|
197
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
198
|
+
activeProcesses?.delete(runId);
|
|
199
|
+
|
|
200
|
+
await processing;
|
|
201
|
+
await stateWrites;
|
|
202
|
+
|
|
203
|
+
if (stdoutBuffer.trim()) {
|
|
204
|
+
await handleLine(stdoutBuffer.trim(), "stdout");
|
|
205
|
+
stdoutBuffer = "";
|
|
206
|
+
}
|
|
207
|
+
if (stderrBuffer.trim()) {
|
|
208
|
+
await handleLine(stderrBuffer.trim(), "stderr");
|
|
209
|
+
stderrBuffer = "";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await processing;
|
|
213
|
+
await stateWrites;
|
|
214
|
+
|
|
215
|
+
await serializeStateWrite(async () => {
|
|
216
|
+
const preserved = await shouldPreserveCancelledState(homeDir, runId);
|
|
217
|
+
if (preserved) {
|
|
218
|
+
cancelledRuns?.delete(runId);
|
|
219
|
+
resolve(preserved.state === RUN_STATES.CANCELLED
|
|
220
|
+
? preserved
|
|
221
|
+
: { ...preserved, state: RUN_STATES.CANCELLED });
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (cancelledRuns?.has(runId)) {
|
|
226
|
+
cancelledRuns.delete(runId);
|
|
227
|
+
try {
|
|
228
|
+
resolve(await readRunState(homeDir, runId));
|
|
229
|
+
} catch {
|
|
230
|
+
resolve({ ...metadata, state: RUN_STATES.CANCELLED });
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const failed = exitCode !== 0;
|
|
236
|
+
const nextState = failed ? RUN_STATES.FAILED : RUN_STATES.COMPLETED;
|
|
237
|
+
metadata = transitionRunState(metadata, nextState, {
|
|
238
|
+
exitCode,
|
|
239
|
+
error: failed ? `Process exited with code ${exitCode}` : null
|
|
240
|
+
});
|
|
241
|
+
await writeRunState(homeDir, metadata);
|
|
242
|
+
await appendRunEvent(homeDir, createRunEvent({
|
|
243
|
+
runId,
|
|
244
|
+
type: failed ? "run.failed" : "run.completed",
|
|
245
|
+
data: { exitCode }
|
|
246
|
+
}), { captureTranscript: shouldPersistTranscript(captureTranscript) });
|
|
247
|
+
resolve(metadata);
|
|
248
|
+
});
|
|
249
|
+
} catch (error) {
|
|
250
|
+
reject(error);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
child.stdout.on("data", (chunk) => {
|
|
256
|
+
enqueue(async () => {
|
|
257
|
+
stdoutBuffer += chunk.toString();
|
|
258
|
+
stdoutBuffer = await flushBuffer(stdoutBuffer, "stdout");
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
child.stderr.on("data", (chunk) => {
|
|
263
|
+
enqueue(async () => {
|
|
264
|
+
stderrBuffer += chunk.toString();
|
|
265
|
+
stderrBuffer = await flushBuffer(stderrBuffer, "stderr");
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (timeoutMs != null && timeoutMs > 0) {
|
|
270
|
+
timeoutHandle = setTimeout(() => {
|
|
271
|
+
child.kill("SIGTERM");
|
|
272
|
+
}, timeoutMs);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
void serializeStateWrite(async () => {
|
|
276
|
+
metadata = {
|
|
277
|
+
...metadata,
|
|
278
|
+
pid: child.pid ?? null
|
|
279
|
+
};
|
|
280
|
+
await writeRunState(homeDir, metadata);
|
|
281
|
+
await touchSupervisorLock(homeDir, runId, { agentPid: child.pid ?? null });
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return completion;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function readSupervisorLockForRun(homeDir, runId) {
|
|
288
|
+
return readSupervisorLock(homeDir, runId);
|
|
289
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const RUN_STATES = {
|
|
4
|
+
PENDING: "pending",
|
|
5
|
+
STARTING: "starting",
|
|
6
|
+
RUNNING: "running",
|
|
7
|
+
COMPLETED: "completed",
|
|
8
|
+
FAILED: "failed",
|
|
9
|
+
CANCELLED: "cancelled",
|
|
10
|
+
INTERRUPTED: "interrupted"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const STARTING_GRACE_MS = 30_000;
|
|
14
|
+
|
|
15
|
+
export const RUN_EVENT_TYPES = {
|
|
16
|
+
RUN_STARTED: "run.started",
|
|
17
|
+
RUN_COMPLETED: "run.completed",
|
|
18
|
+
RUN_FAILED: "run.failed",
|
|
19
|
+
RUN_CANCELLED: "run.cancelled",
|
|
20
|
+
STDOUT: "process.stdout",
|
|
21
|
+
STDERR: "process.stderr",
|
|
22
|
+
TOOL_CALL: "agent.tool_call",
|
|
23
|
+
TOOL_RESULT: "agent.tool_result",
|
|
24
|
+
ASSISTANT: "agent.assistant",
|
|
25
|
+
TOKEN_USAGE: "agent.token_usage",
|
|
26
|
+
DIFF_SUMMARY: "agent.diff_summary",
|
|
27
|
+
SYSTEM: "agent.system",
|
|
28
|
+
RESULT: "agent.result",
|
|
29
|
+
TRANSCRIPT: "run.transcript"
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const TERMINAL_RUN_STATES = new Set([
|
|
33
|
+
RUN_STATES.COMPLETED,
|
|
34
|
+
RUN_STATES.FAILED,
|
|
35
|
+
RUN_STATES.CANCELLED,
|
|
36
|
+
RUN_STATES.INTERRUPTED
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
export const ACTIVE_RUN_STATES = new Set([
|
|
40
|
+
RUN_STATES.PENDING,
|
|
41
|
+
RUN_STATES.STARTING,
|
|
42
|
+
RUN_STATES.RUNNING
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
export function isTerminalRunState(state) {
|
|
46
|
+
return TERMINAL_RUN_STATES.has(state);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isActiveRunState(state) {
|
|
50
|
+
return state === RUN_STATES.PENDING
|
|
51
|
+
|| state === RUN_STATES.STARTING
|
|
52
|
+
|| state === RUN_STATES.RUNNING;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createRunId() {
|
|
56
|
+
const timestamp = Date.now().toString(36);
|
|
57
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
58
|
+
return `run_${timestamp}_${random}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createTaskFingerprint(task) {
|
|
62
|
+
const normalized = String(task ?? "").replace(/\s+/g, " ").trim();
|
|
63
|
+
const digest = createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
64
|
+
return {
|
|
65
|
+
taskDigest: digest,
|
|
66
|
+
taskLength: normalized.length
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function formatTaskLabel(metadata) {
|
|
71
|
+
const digest = metadata?.taskDigest ?? "unknown";
|
|
72
|
+
const length = metadata?.taskLength ?? 0;
|
|
73
|
+
return `task:${digest} (${length} chars, content not stored)`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createRunMetadata({
|
|
77
|
+
runId,
|
|
78
|
+
agentId,
|
|
79
|
+
provider,
|
|
80
|
+
model = null,
|
|
81
|
+
task,
|
|
82
|
+
cwd,
|
|
83
|
+
permissions = [],
|
|
84
|
+
captureTranscript = false,
|
|
85
|
+
cliVersion,
|
|
86
|
+
profileSources = null
|
|
87
|
+
}) {
|
|
88
|
+
const { taskDigest, taskLength } = createTaskFingerprint(task);
|
|
89
|
+
const now = new Date().toISOString();
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
runId,
|
|
93
|
+
agentId,
|
|
94
|
+
provider,
|
|
95
|
+
model,
|
|
96
|
+
taskDigest,
|
|
97
|
+
taskLength,
|
|
98
|
+
cwd,
|
|
99
|
+
permissions,
|
|
100
|
+
captureTranscript,
|
|
101
|
+
cliVersion,
|
|
102
|
+
profileSources,
|
|
103
|
+
state: RUN_STATES.PENDING,
|
|
104
|
+
pid: null,
|
|
105
|
+
supervisorPid: null,
|
|
106
|
+
exitCode: null,
|
|
107
|
+
startedAt: now,
|
|
108
|
+
updatedAt: now,
|
|
109
|
+
completedAt: null,
|
|
110
|
+
tools: [],
|
|
111
|
+
commands: [],
|
|
112
|
+
tokenUsage: null,
|
|
113
|
+
cost: null,
|
|
114
|
+
diffSummary: null,
|
|
115
|
+
error: null
|
|
116
|
+
};
|
|
117
|
+
}
|