@hasna/workflows 0.1.4 → 0.2.1
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 +180 -98
- package/dist/agent-tools.js +554 -1418
- package/dist/cli/index.js +4120 -2474
- package/dist/handlers.js +3339 -55
- package/dist/index.js +2372 -1253
- package/dist/mcp/index.js +1738 -1744
- package/dist/sdk.js +1620 -0
- package/dist/serve/admin.js +4291 -0
- package/dist/serve/index.js +3392 -1381
- package/dist/service.js +2134 -1165
- package/hasna.contract.json +17 -17
- package/package.json +16 -13
- package/types/agent-tools.d.ts +3 -17
- package/types/client-error.d.ts +4 -0
- package/types/client-tools.d.ts +4 -0
- package/types/client.d.ts +60 -0
- package/types/daemon.d.ts +15 -9
- package/types/db/postgres-store.d.ts +54 -0
- package/types/graph.d.ts +6 -0
- package/types/handlers.d.ts +2 -1
- package/types/identity.d.ts +7 -0
- package/types/lanes/codex.d.ts +0 -6
- package/types/lanes/types.d.ts +16 -3
- package/types/openapi.d.ts +7 -0
- package/types/paths.d.ts +10 -1
- package/types/sdk/generated-api.d.ts +161 -0
- package/types/sdk.d.ts +6 -0
- package/types/serve/admin.d.ts +2 -0
- package/types/service.d.ts +75 -4
- package/types/session.d.ts +7 -8
- package/types/setup.d.ts +71 -0
- package/types/store.d.ts +30 -0
- package/types/tool-definitions.d.ts +18 -0
- package/types/types.d.ts +8 -0
- package/types/version.d.ts +1 -0
- package/types/wal.d.ts +9 -1
package/dist/agent-tools.js
CHANGED
|
@@ -16,454 +16,7 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
var __require = import.meta.require;
|
|
18
18
|
|
|
19
|
-
// src/lanes/types.ts
|
|
20
|
-
import { statSync as statSync2 } from "fs";
|
|
21
|
-
function binaryOnPath(binary) {
|
|
22
|
-
const pathEntries = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
23
|
-
for (const entry of pathEntries) {
|
|
24
|
-
try {
|
|
25
|
-
const stat = statSync2(`${entry}/${binary}`);
|
|
26
|
-
if (stat.isFile())
|
|
27
|
-
return true;
|
|
28
|
-
} catch {}
|
|
29
|
-
}
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
function withTimeout(promise, timeoutMs, what) {
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
const timer = setTimeout(() => {
|
|
35
|
-
reject(new Error(`${what} exceeded the ${timeoutMs}ms budget`));
|
|
36
|
-
}, timeoutMs);
|
|
37
|
-
promise.then((value) => {
|
|
38
|
-
clearTimeout(timer);
|
|
39
|
-
resolve(value);
|
|
40
|
-
}, (err) => {
|
|
41
|
-
clearTimeout(timer);
|
|
42
|
-
reject(err);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
function spawnCli(cmd, opts) {
|
|
47
|
-
const started = Date.now();
|
|
48
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
49
|
-
let proc;
|
|
50
|
-
try {
|
|
51
|
-
proc = Bun.spawnSync({
|
|
52
|
-
cmd,
|
|
53
|
-
cwd: opts.cwd,
|
|
54
|
-
env: { ...process.env, ...opts.env ?? {} },
|
|
55
|
-
stdout: "pipe",
|
|
56
|
-
stderr: "pipe",
|
|
57
|
-
timeout: timeoutMs
|
|
58
|
-
});
|
|
59
|
-
} catch (err) {
|
|
60
|
-
const message = String(err instanceof Error ? err.message : err);
|
|
61
|
-
if (message.includes("ENOENT") || /not found/i.test(message)) {
|
|
62
|
-
throw new LaneDependencyMissingError(`lane binary ${JSON.stringify(opts.binaryName)} not found on PATH; install it or provide ${opts.packageHint}`);
|
|
63
|
-
}
|
|
64
|
-
return { ok: false, exitCode: 1, output: "", durationMs: Date.now() - started, error: message };
|
|
65
|
-
}
|
|
66
|
-
const durationMs = Date.now() - started;
|
|
67
|
-
const stdout = proc.stdout ? new TextDecoder().decode(proc.stdout) : "";
|
|
68
|
-
const stderr = proc.stderr ? new TextDecoder().decode(proc.stderr) : "";
|
|
69
|
-
const output = stderr ? `${stdout}
|
|
70
|
-
${stderr}`.trim() : stdout.trim();
|
|
71
|
-
const exitCode = proc.exitCode ?? 1;
|
|
72
|
-
return { ok: exitCode === 0, exitCode, output, durationMs };
|
|
73
|
-
}
|
|
74
|
-
var LANE_KINDS2, LaneDependencyMissingError, LaneAdapterShapeError, DEFAULT_LANE_TIMEOUT_MS = 120000;
|
|
75
|
-
var init_types = __esm(() => {
|
|
76
|
-
LANE_KINDS2 = ["claude", "codex", "cursor", "grok"];
|
|
77
|
-
LaneDependencyMissingError = class LaneDependencyMissingError extends Error {
|
|
78
|
-
constructor(message) {
|
|
79
|
-
super(message);
|
|
80
|
-
this.name = "LaneDependencyMissingError";
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
LaneAdapterShapeError = class LaneAdapterShapeError extends Error {
|
|
84
|
-
constructor(message) {
|
|
85
|
-
super(message);
|
|
86
|
-
this.name = "LaneAdapterShapeError";
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// src/lanes/claude.ts
|
|
92
|
-
function createClaudeAdapter(deps = {}) {
|
|
93
|
-
return {
|
|
94
|
-
kind: "claude",
|
|
95
|
-
async probe() {
|
|
96
|
-
const loader = deps.sdkLoader ?? (async () => await import("@anthropic-ai/claude-agent-sdk"));
|
|
97
|
-
try {
|
|
98
|
-
await loader();
|
|
99
|
-
return { kind: "claude", sdk: "@anthropic-ai/claude-agent-sdk", wired: true, via: "sdk" };
|
|
100
|
-
} catch {
|
|
101
|
-
if (binaryOnPath(deps.cliPath ?? "claude")) {
|
|
102
|
-
return { kind: "claude", sdk: "@anthropic-ai/claude-agent-sdk", wired: true, via: "cli" };
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
kind: "claude",
|
|
106
|
-
sdk: "@anthropic-ai/claude-agent-sdk",
|
|
107
|
-
wired: false,
|
|
108
|
-
reason: "neither @anthropic-ai/claude-agent-sdk nor the claude CLI is available on this machine"
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
async run(job) {
|
|
113
|
-
if (!job.prompt) {
|
|
114
|
-
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "claude lane requires a prompt" };
|
|
115
|
-
}
|
|
116
|
-
const started = Date.now();
|
|
117
|
-
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
118
|
-
const loader = deps.sdkLoader ?? (async () => await import("@anthropic-ai/claude-agent-sdk"));
|
|
119
|
-
let sdk;
|
|
120
|
-
try {
|
|
121
|
-
sdk = await loader();
|
|
122
|
-
} catch (err) {
|
|
123
|
-
return runClaudeCli(job, deps.cliPath ?? "claude", started, timeoutMs);
|
|
124
|
-
}
|
|
125
|
-
const query = sdk.query;
|
|
126
|
-
if (typeof query !== "function") {
|
|
127
|
-
throw new LaneAdapterShapeError("@anthropic-ai/claude-agent-sdk exports no query() function");
|
|
128
|
-
}
|
|
129
|
-
const prompt = job.prompt;
|
|
130
|
-
try {
|
|
131
|
-
const { output, isError } = await withTimeout((async () => {
|
|
132
|
-
const generator = await query({
|
|
133
|
-
prompt,
|
|
134
|
-
options: { model: job.model, maxTurns: 32, abortSignal: undefined }
|
|
135
|
-
});
|
|
136
|
-
let output2 = "";
|
|
137
|
-
let isError2 = false;
|
|
138
|
-
for await (const message of generator) {
|
|
139
|
-
if (message.type === "assistant") {
|
|
140
|
-
const content = message.message?.content;
|
|
141
|
-
if (Array.isArray(content)) {
|
|
142
|
-
for (const block of content) {
|
|
143
|
-
if (block && typeof block === "object" && "text" in block && typeof block.text === "string") {
|
|
144
|
-
output2 += block.text;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (message.type === "error")
|
|
150
|
-
isError2 = true;
|
|
151
|
-
}
|
|
152
|
-
return { output: output2, isError: isError2 };
|
|
153
|
-
})(), timeoutMs, `claude lane (${job.prompt.slice(0, 60)})`);
|
|
154
|
-
const durationMs = Date.now() - started;
|
|
155
|
-
return { ok: !isError, exitCode: isError ? 1 : 0, output: output.trim(), durationMs };
|
|
156
|
-
} catch (err) {
|
|
157
|
-
const durationMs = Date.now() - started;
|
|
158
|
-
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err) };
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
function runClaudeCli(job, cliPath, started, timeoutMs) {
|
|
164
|
-
try {
|
|
165
|
-
const result = spawnCli([cliPath, "-p", job.prompt], {
|
|
166
|
-
binaryName: cliPath,
|
|
167
|
-
packageHint: "@anthropic-ai/claude-agent-sdk or the claude CLI",
|
|
168
|
-
timeoutMs,
|
|
169
|
-
env: job.env,
|
|
170
|
-
cwd: job.cwd
|
|
171
|
-
});
|
|
172
|
-
return { ...result, durationMs: Date.now() - started };
|
|
173
|
-
} catch (err) {
|
|
174
|
-
if (err instanceof LaneDependencyMissingError) {
|
|
175
|
-
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
176
|
-
}
|
|
177
|
-
throw err;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
var init_claude = __esm(() => {
|
|
181
|
-
init_types();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// src/lanes/codex.ts
|
|
185
|
-
function createCodexAdapter(deps = {}) {
|
|
186
|
-
return {
|
|
187
|
-
kind: "codex",
|
|
188
|
-
async probe() {
|
|
189
|
-
const loader = deps.sdkLoader ?? (async () => await import("@openai/codex-sdk"));
|
|
190
|
-
try {
|
|
191
|
-
await loader();
|
|
192
|
-
return { kind: "codex", sdk: "@openai/codex-sdk", wired: true, via: "sdk" };
|
|
193
|
-
} catch {
|
|
194
|
-
if (binaryOnPath(deps.cliPath ?? "codex")) {
|
|
195
|
-
return { kind: "codex", sdk: "@openai/codex-sdk", wired: true, via: "cli" };
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
kind: "codex",
|
|
199
|
-
sdk: "@openai/codex-sdk",
|
|
200
|
-
wired: false,
|
|
201
|
-
reason: "neither @openai/codex-sdk nor the codex CLI is available on this machine"
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
async run(job) {
|
|
206
|
-
if (!job.prompt) {
|
|
207
|
-
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "codex lane requires a prompt" };
|
|
208
|
-
}
|
|
209
|
-
const started = Date.now();
|
|
210
|
-
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
211
|
-
const loader = deps.sdkLoader ?? (async () => await import("@openai/codex-sdk"));
|
|
212
|
-
let sdk;
|
|
213
|
-
try {
|
|
214
|
-
sdk = await loader();
|
|
215
|
-
} catch {
|
|
216
|
-
return runCodexCli(job, deps.cliPath ?? "codex", started, timeoutMs);
|
|
217
|
-
}
|
|
218
|
-
const CodexClass = sdk.Codex;
|
|
219
|
-
if (typeof CodexClass !== "function") {
|
|
220
|
-
throw new LaneAdapterShapeError("@openai/codex-sdk exports no Codex class");
|
|
221
|
-
}
|
|
222
|
-
try {
|
|
223
|
-
const client = await withTimeout(Promise.resolve(new CodexClass({
|
|
224
|
-
config: { model: job.model },
|
|
225
|
-
env: job.env
|
|
226
|
-
})), timeoutMs, `codex lane (${job.prompt.slice(0, 60)})`);
|
|
227
|
-
const thread = client.startThread?.();
|
|
228
|
-
if (!thread || typeof thread.run !== "function") {
|
|
229
|
-
throw new LaneAdapterShapeError("@openai/codex-sdk Codex.startThread() returned no run()");
|
|
230
|
-
}
|
|
231
|
-
const turn = await withTimeout(thread.run(job.prompt), timeoutMs, `codex lane (${job.prompt.slice(0, 60)})`);
|
|
232
|
-
const output = turn?.finalResponse ?? "";
|
|
233
|
-
const durationMs = Date.now() - started;
|
|
234
|
-
return { ok: true, exitCode: 0, output, durationMs };
|
|
235
|
-
} catch (err) {
|
|
236
|
-
const durationMs = Date.now() - started;
|
|
237
|
-
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err) };
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function runCodexCli(job, cliPath, started, timeoutMs) {
|
|
243
|
-
try {
|
|
244
|
-
const result = spawnCli([cliPath, "exec", "--", job.prompt], {
|
|
245
|
-
binaryName: cliPath,
|
|
246
|
-
packageHint: "@openai/codex-sdk or the codex CLI",
|
|
247
|
-
timeoutMs,
|
|
248
|
-
env: job.env,
|
|
249
|
-
cwd: job.cwd
|
|
250
|
-
});
|
|
251
|
-
return { ...result, durationMs: Date.now() - started };
|
|
252
|
-
} catch (err) {
|
|
253
|
-
if (err instanceof LaneDependencyMissingError) {
|
|
254
|
-
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
255
|
-
}
|
|
256
|
-
throw err;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
var init_codex = __esm(() => {
|
|
260
|
-
init_types();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// src/lanes/cursor.ts
|
|
264
|
-
function createCursorAdapter(deps = {}) {
|
|
265
|
-
return {
|
|
266
|
-
kind: "cursor",
|
|
267
|
-
async probe() {
|
|
268
|
-
const loader = deps.sdkLoader ?? (async () => await import("@cursor/sdk"));
|
|
269
|
-
try {
|
|
270
|
-
await loader();
|
|
271
|
-
return { kind: "cursor", sdk: "@cursor/sdk", wired: true, via: "sdk (local mode)" };
|
|
272
|
-
} catch {
|
|
273
|
-
if (binaryOnPath(deps.cliPath ?? "cursor-agent")) {
|
|
274
|
-
return { kind: "cursor", sdk: "@cursor/sdk", wired: true, via: "cli" };
|
|
275
|
-
}
|
|
276
|
-
return {
|
|
277
|
-
kind: "cursor",
|
|
278
|
-
sdk: "@cursor/sdk",
|
|
279
|
-
wired: false,
|
|
280
|
-
reason: "neither @cursor/sdk nor the cursor-agent CLI is available on this machine"
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
async run(job) {
|
|
285
|
-
if (!job.prompt) {
|
|
286
|
-
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "cursor lane requires a prompt" };
|
|
287
|
-
}
|
|
288
|
-
const started = Date.now();
|
|
289
|
-
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
290
|
-
const loader = deps.sdkLoader ?? (async () => await import("@cursor/sdk"));
|
|
291
|
-
let sdk;
|
|
292
|
-
try {
|
|
293
|
-
sdk = await loader();
|
|
294
|
-
} catch {
|
|
295
|
-
return runCursorCli(job, deps.cliPath ?? "cursor-agent", started, timeoutMs);
|
|
296
|
-
}
|
|
297
|
-
try {
|
|
298
|
-
const run = await withTimeout(runLocalAgent(sdk, job), timeoutMs, `cursor lane (${job.prompt.slice(0, 60)})`);
|
|
299
|
-
const durationMs = Date.now() - started;
|
|
300
|
-
const status = run.status;
|
|
301
|
-
const ok = status === "finished";
|
|
302
|
-
return { ok, exitCode: ok ? 0 : 1, output: run.result, durationMs };
|
|
303
|
-
} catch (err) {
|
|
304
|
-
if (err instanceof LaneAdapterShapeError)
|
|
305
|
-
throw err;
|
|
306
|
-
const durationMs = Date.now() - started;
|
|
307
|
-
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err) };
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
async function runLocalAgent(sdk, job) {
|
|
313
|
-
const AgentClass = sdk.Agent;
|
|
314
|
-
const CursorClass = sdk.Cursor;
|
|
315
|
-
if (typeof AgentClass !== "function" && typeof CursorClass !== "function") {
|
|
316
|
-
throw new LaneAdapterShapeError("@cursor/sdk exports no Agent/Cursor class");
|
|
317
|
-
}
|
|
318
|
-
const agent = typeof AgentClass === "function" ? new AgentClass : undefined;
|
|
319
|
-
if (!agent || typeof agent.send !== "function") {
|
|
320
|
-
throw new LaneAdapterShapeError("@cursor/sdk Agent has no send()");
|
|
321
|
-
}
|
|
322
|
-
const run = await agent.send(job.prompt, { model: job.model });
|
|
323
|
-
if (!run || typeof run !== "object") {
|
|
324
|
-
throw new LaneAdapterShapeError("@cursor/sdk Agent.send() returned no Run");
|
|
325
|
-
}
|
|
326
|
-
const candidate = run;
|
|
327
|
-
if (typeof candidate.status !== "string" || !["finished", "error", "cancelled"].includes(candidate.status)) {
|
|
328
|
-
throw new LaneAdapterShapeError(`@cursor/sdk Agent.send() returned a Run without a terminal status (got ${JSON.stringify(candidate.status)}; expected finished|error|cancelled)`);
|
|
329
|
-
}
|
|
330
|
-
if (typeof candidate.result !== "string") {
|
|
331
|
-
throw new LaneAdapterShapeError("@cursor/sdk Agent.send() returned a Run without a string result");
|
|
332
|
-
}
|
|
333
|
-
return { status: candidate.status, result: candidate.result };
|
|
334
|
-
}
|
|
335
|
-
function runCursorCli(job, cliPath, started, timeoutMs) {
|
|
336
|
-
try {
|
|
337
|
-
const result = spawnCli([cliPath, "run", job.prompt], {
|
|
338
|
-
binaryName: cliPath,
|
|
339
|
-
packageHint: "@cursor/sdk (local mode) or the cursor-agent CLI",
|
|
340
|
-
timeoutMs,
|
|
341
|
-
env: job.env,
|
|
342
|
-
cwd: job.cwd
|
|
343
|
-
});
|
|
344
|
-
return { ...result, durationMs: Date.now() - started };
|
|
345
|
-
} catch (err) {
|
|
346
|
-
if (err instanceof LaneDependencyMissingError) {
|
|
347
|
-
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
348
|
-
}
|
|
349
|
-
throw err;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
var init_cursor = __esm(() => {
|
|
353
|
-
init_types();
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// src/lanes/grok.ts
|
|
357
|
-
function createGrokAdapter(deps = {}) {
|
|
358
|
-
return {
|
|
359
|
-
kind: "grok",
|
|
360
|
-
async probe() {
|
|
361
|
-
const cliPath = deps.cliPath ?? "grok";
|
|
362
|
-
if (binaryOnPath(cliPath)) {
|
|
363
|
-
return { kind: "grok", sdk: "no npm SDK (measured 2026-08-25)", wired: true, via: "cli" };
|
|
364
|
-
}
|
|
365
|
-
return {
|
|
366
|
-
kind: "grok",
|
|
367
|
-
sdk: "no npm SDK (measured 2026-08-25)",
|
|
368
|
-
wired: false,
|
|
369
|
-
reason: `no xAI Grok SDK exists on npm (measured 2026-08-25) and the grok CLI is not on PATH`
|
|
370
|
-
};
|
|
371
|
-
},
|
|
372
|
-
async run(job) {
|
|
373
|
-
if (!job.prompt) {
|
|
374
|
-
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "grok lane requires a prompt" };
|
|
375
|
-
}
|
|
376
|
-
const started = Date.now();
|
|
377
|
-
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
378
|
-
const cliPath = deps.cliPath ?? "grok";
|
|
379
|
-
try {
|
|
380
|
-
const result = spawnCli([cliPath, job.prompt], {
|
|
381
|
-
binaryName: cliPath,
|
|
382
|
-
packageHint: "the grok CLI (no xAI Grok SDK exists on npm \u2014 measured 2026-08-25)",
|
|
383
|
-
timeoutMs,
|
|
384
|
-
env: job.env,
|
|
385
|
-
cwd: job.cwd
|
|
386
|
-
});
|
|
387
|
-
return { ...result, durationMs: Date.now() - started };
|
|
388
|
-
} catch (err) {
|
|
389
|
-
if (err instanceof LaneDependencyMissingError) {
|
|
390
|
-
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
391
|
-
}
|
|
392
|
-
throw err;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
var init_grok = __esm(() => {
|
|
398
|
-
init_types();
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
// src/lanes/index.ts
|
|
402
|
-
var exports_lanes = {};
|
|
403
|
-
__export(exports_lanes, {
|
|
404
|
-
runLaneJob: () => runLaneJob,
|
|
405
|
-
resolveLane: () => resolveLane,
|
|
406
|
-
probeLane: () => probeLane,
|
|
407
|
-
laneInventory: () => laneInventory,
|
|
408
|
-
createGrokAdapter: () => createGrokAdapter,
|
|
409
|
-
createCursorAdapter: () => createCursorAdapter,
|
|
410
|
-
createCodexAdapter: () => createCodexAdapter,
|
|
411
|
-
createClaudeAdapter: () => createClaudeAdapter,
|
|
412
|
-
binaryOnPath: () => binaryOnPath,
|
|
413
|
-
LaneDependencyMissingError: () => LaneDependencyMissingError,
|
|
414
|
-
LaneAdapterShapeError: () => LaneAdapterShapeError,
|
|
415
|
-
LANE_KINDS: () => LANE_KINDS2
|
|
416
|
-
});
|
|
417
|
-
function resolveLane(kind) {
|
|
418
|
-
const adapter = adapters.get(kind);
|
|
419
|
-
if (!adapter)
|
|
420
|
-
throw new Error(`unknown lane ${kind} (expected one of ${LANE_KINDS2.join(", ")})`);
|
|
421
|
-
return adapter;
|
|
422
|
-
}
|
|
423
|
-
async function runLaneJob(job) {
|
|
424
|
-
return resolveLane(job.lane).run(job);
|
|
425
|
-
}
|
|
426
|
-
async function probeLane(kind) {
|
|
427
|
-
return resolveLane(kind).probe();
|
|
428
|
-
}
|
|
429
|
-
async function laneInventory() {
|
|
430
|
-
return Promise.all(LANE_KINDS2.map((kind) => probeLane(kind)));
|
|
431
|
-
}
|
|
432
|
-
var adapters;
|
|
433
|
-
var init_lanes = __esm(() => {
|
|
434
|
-
init_claude();
|
|
435
|
-
init_codex();
|
|
436
|
-
init_cursor();
|
|
437
|
-
init_grok();
|
|
438
|
-
init_types();
|
|
439
|
-
init_types();
|
|
440
|
-
init_claude();
|
|
441
|
-
init_codex();
|
|
442
|
-
init_cursor();
|
|
443
|
-
init_grok();
|
|
444
|
-
adapters = new Map([
|
|
445
|
-
["claude", createClaudeAdapter()],
|
|
446
|
-
["codex", createCodexAdapter()],
|
|
447
|
-
["cursor", createCursorAdapter()],
|
|
448
|
-
["grok", createGrokAdapter()]
|
|
449
|
-
]);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
19
|
// src/expr.ts
|
|
453
|
-
class ExprSyntaxError extends Error {
|
|
454
|
-
constructor(message) {
|
|
455
|
-
super(message);
|
|
456
|
-
this.name = "ExprSyntaxError";
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
class ExprEvalError extends Error {
|
|
461
|
-
constructor(message) {
|
|
462
|
-
super(message);
|
|
463
|
-
this.name = "ExprEvalError";
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
var COMPARISON_OPS = new Set(["==", "!=", "<", "<=", ">", ">="]);
|
|
467
20
|
function tokenize(input) {
|
|
468
21
|
const tokens = [];
|
|
469
22
|
let i = 0;
|
|
@@ -715,11 +268,24 @@ function evaluateExpr(input, context) {
|
|
|
715
268
|
}
|
|
716
269
|
return value;
|
|
717
270
|
}
|
|
271
|
+
var ExprSyntaxError, ExprEvalError, COMPARISON_OPS;
|
|
272
|
+
var init_expr = __esm(() => {
|
|
273
|
+
ExprSyntaxError = class ExprSyntaxError extends Error {
|
|
274
|
+
constructor(message) {
|
|
275
|
+
super(message);
|
|
276
|
+
this.name = "ExprSyntaxError";
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
ExprEvalError = class ExprEvalError extends Error {
|
|
280
|
+
constructor(message) {
|
|
281
|
+
super(message);
|
|
282
|
+
this.name = "ExprEvalError";
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
COMPARISON_OPS = new Set(["==", "!=", "<", "<=", ">", ">="]);
|
|
286
|
+
});
|
|
718
287
|
|
|
719
288
|
// src/graph.ts
|
|
720
|
-
var GRAPH_NODE_TYPES = ["start", "step", "decision", "while", "end"];
|
|
721
|
-
var LANE_KINDS = ["claude", "codex", "cursor", "grok"];
|
|
722
|
-
var NODE_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
723
289
|
function issue(issues, path, message) {
|
|
724
290
|
issues.push({ path, message });
|
|
725
291
|
}
|
|
@@ -816,6 +382,9 @@ function findCycles(graph) {
|
|
|
816
382
|
}
|
|
817
383
|
function validateGraph(graph) {
|
|
818
384
|
const issues = [];
|
|
385
|
+
if (graph === null || typeof graph !== "object" || Array.isArray(graph)) {
|
|
386
|
+
return { ok: false, issues: [{ path: "graph", message: "graph must be an object" }] };
|
|
387
|
+
}
|
|
819
388
|
if (typeof graph.name !== "string" || graph.name.trim() === "") {
|
|
820
389
|
issue(issues, "name", "graph requires a non-empty name");
|
|
821
390
|
}
|
|
@@ -838,6 +407,24 @@ function validateGraph(graph) {
|
|
|
838
407
|
issue(issues, "nodes", "graph requires at least one node");
|
|
839
408
|
return { ok: false, issues };
|
|
840
409
|
}
|
|
410
|
+
for (let index = 0;index < graph.nodes.length; index++) {
|
|
411
|
+
const node = graph.nodes[index];
|
|
412
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
413
|
+
issue(issues, `nodes.${index}`, "node must be an object");
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (!GRAPH_NODE_TYPES.includes(node.type))
|
|
417
|
+
issue(issues, `nodes.${index}.type`, "type must be start, step, decision, while, or end");
|
|
418
|
+
for (const key of ["next", "then", "else", "prompt", "command"]) {
|
|
419
|
+
const value = node[key];
|
|
420
|
+
if (value !== undefined && (typeof value !== "string" || value.trim() === ""))
|
|
421
|
+
issue(issues, `nodes.${index}.${key}`, `${key} must be a non-empty string`);
|
|
422
|
+
}
|
|
423
|
+
if (node.type === "while" && (!Array.isArray(node.body) || node.body.some((id) => typeof id !== "string")))
|
|
424
|
+
issue(issues, `nodes.${index}.body`, "body must be an array of node ids");
|
|
425
|
+
}
|
|
426
|
+
if (issues.length)
|
|
427
|
+
return { ok: false, issues };
|
|
841
428
|
const starts = graph.nodes.filter((n) => n.type === "start");
|
|
842
429
|
if (starts.length === 0)
|
|
843
430
|
issue(issues, "nodes", "graph requires exactly one start node (found none)");
|
|
@@ -899,6 +486,8 @@ function nodeEdges(node) {
|
|
|
899
486
|
return node.next ? [{ kind: "next", target: node.next }] : [];
|
|
900
487
|
case "end":
|
|
901
488
|
return [];
|
|
489
|
+
default:
|
|
490
|
+
return [];
|
|
902
491
|
}
|
|
903
492
|
}
|
|
904
493
|
function validateNode(graph, node, issues) {
|
|
@@ -914,6 +503,16 @@ function validateNode(graph, node, issues) {
|
|
|
914
503
|
if (node.lane !== undefined && !LANE_KINDS.includes(node.lane)) {
|
|
915
504
|
issue(issues, `nodes.${node.id}`, `unknown lane ${JSON.stringify(node.lane)} (expected one of ${LANE_KINDS.join(", ")})`);
|
|
916
505
|
}
|
|
506
|
+
if (node.cwd !== undefined && (typeof node.cwd !== "string" || !node.cwd.trim() || node.cwd.includes("\x00"))) {
|
|
507
|
+
issue(issues, `nodes.${node.id}.cwd`, "cwd must be a non-empty path string without NUL characters on the executor");
|
|
508
|
+
}
|
|
509
|
+
if (node.codexMode !== undefined) {
|
|
510
|
+
if (!CODEX_EXECUTION_MODES.includes(node.codexMode)) {
|
|
511
|
+
issue(issues, `nodes.${node.id}.codexMode`, "codexMode must be repository or read-only");
|
|
512
|
+
} else if (node.lane !== "codex" || !node.prompt || node.command !== undefined) {
|
|
513
|
+
issue(issues, `nodes.${node.id}.codexMode`, "codexMode is supported only for a codex prompt step without command");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
917
516
|
if (node.maxRetries !== undefined && (!Number.isInteger(node.maxRetries) || node.maxRetries < 0)) {
|
|
918
517
|
issue(issues, `nodes.${node.id}`, "maxRetries must be a non-negative integer");
|
|
919
518
|
}
|
|
@@ -973,1002 +572,547 @@ function validateNode(graph, node, issues) {
|
|
|
973
572
|
issue(issues, `nodes.${node.id}`, `while body may only contain step/decision nodes in v1 (found ${bodyNode.type})`);
|
|
974
573
|
}
|
|
975
574
|
}
|
|
976
|
-
const bodySet = new Set(node.body);
|
|
977
|
-
for (const bodyId of node.body) {
|
|
978
|
-
const bodyNode = findNode(graph, bodyId);
|
|
979
|
-
if (!bodyNode)
|
|
980
|
-
continue;
|
|
981
|
-
for (const { kind, target } of nodeEdges(bodyNode)) {
|
|
982
|
-
if (!bodySet.has(target)) {
|
|
983
|
-
issue(issues, `nodes.${node.id}`, `while body node ${bodyId} ${kind}-edge leaves the body (target ${target})`);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
break;
|
|
989
|
-
}
|
|
990
|
-
case "end":
|
|
991
|
-
break;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// src/secrets.ts
|
|
996
|
-
class SecretsGateError extends Error {
|
|
997
|
-
constructor(message) {
|
|
998
|
-
super(message);
|
|
999
|
-
this.name = "SecretsGateError";
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
var p = (...parts) => parts.join("");
|
|
1003
|
-
var VALUE_PATTERNS = [
|
|
1004
|
-
{ detector: "anthropic_api_key", re: new RegExp(p("sk-", "ant-") + "[A-Za-z0-9_-]{10,}") },
|
|
1005
|
-
{ detector: "openai_api_key", re: new RegExp(p("sk-", "proj-") + "[A-Za-z0-9_-]{10,}") },
|
|
1006
|
-
{ detector: "npm_token", re: /npm_[A-Za-z0-9]{20,}/ },
|
|
1007
|
-
{ detector: "github_token", re: /gh[pousr]_[A-Za-z0-9]{20,}/ },
|
|
1008
|
-
{ detector: "aws_access_key", re: /AKIA[0-9A-Z]{16}/ },
|
|
1009
|
-
{ detector: "google_api_key", re: /AIza[0-9A-Za-z_-]{20,}/ },
|
|
1010
|
-
{ detector: "xai_api_key", re: new RegExp(p("xai-") + "[A-Za-z0-9]{20,}") },
|
|
1011
|
-
{ detector: "codex_token", re: new RegExp(p("ctx7", "sk-") + "[A-Za-z0-9]{10,}") },
|
|
1012
|
-
{ detector: "private_key_block", re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/ },
|
|
1013
|
-
{ detector: "bearer_token", re: /Bearer [A-Za-z0-9._~+/=-]{20,}/ }
|
|
1014
|
-
];
|
|
1015
|
-
var CREDENTIAL_KEY_RE = /^(token|api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|secret[_-]?key|refresh[_-]?token|session[_-]?token|bot[_-]?token)$/i;
|
|
1016
|
-
function scanString(value, path, findings) {
|
|
1017
|
-
for (const { detector, re } of VALUE_PATTERNS) {
|
|
1018
|
-
if (re.test(value)) {
|
|
1019
|
-
findings.push({ path, detector });
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
function isTrivial(value) {
|
|
1025
|
-
const v = value.trim();
|
|
1026
|
-
if (v.length === 0)
|
|
1027
|
-
return true;
|
|
1028
|
-
const upper = v.toUpperCase();
|
|
1029
|
-
if (upper.startsWith("REPLACE") || upper.startsWith("CHANGE") || upper.startsWith("YOUR_") || upper.startsWith("EXAMPLE") || upper.startsWith("PLACEHOLDER"))
|
|
1030
|
-
return true;
|
|
1031
|
-
if (v.length < 8)
|
|
1032
|
-
return true;
|
|
1033
|
-
return false;
|
|
1034
|
-
}
|
|
1035
|
-
function scanForSecrets(value, path = "$") {
|
|
1036
|
-
const findings = [];
|
|
1037
|
-
if (value === null || value === undefined)
|
|
1038
|
-
return findings;
|
|
1039
|
-
if (typeof value === "string") {
|
|
1040
|
-
scanString(value, path, findings);
|
|
1041
|
-
return findings;
|
|
1042
|
-
}
|
|
1043
|
-
if (Array.isArray(value)) {
|
|
1044
|
-
value.forEach((item, index) => {
|
|
1045
|
-
findings.push(...scanForSecrets(item, `${path}[${index}]`));
|
|
1046
|
-
});
|
|
1047
|
-
return findings;
|
|
1048
|
-
}
|
|
1049
|
-
if (typeof value === "object") {
|
|
1050
|
-
const record = value;
|
|
1051
|
-
for (const [key, child] of Object.entries(record)) {
|
|
1052
|
-
if (CREDENTIAL_KEY_RE.test(key) && typeof child === "string" && !isTrivial(child)) {
|
|
1053
|
-
findings.push({ path: `${path}.${key}`, detector: "credential_key_field" });
|
|
1054
|
-
} else {
|
|
1055
|
-
findings.push(...scanForSecrets(child, `${path}.${key}`));
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
return findings;
|
|
1060
|
-
}
|
|
1061
|
-
function assertNoSecrets(value, what) {
|
|
1062
|
-
const findings = scanForSecrets(value);
|
|
1063
|
-
if (findings.length === 0)
|
|
1064
|
-
return;
|
|
1065
|
-
const detail = findings.map((f) => `${f.path} (${f.detector})`).join(", ");
|
|
1066
|
-
throw new SecretsGateError(`secrets write-gate refused to persist ${what}: credential-shaped value at ${detail}`);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// src/session.ts
|
|
1070
|
-
import { createHash } from "crypto";
|
|
1071
|
-
import { readFileSync, statSync } from "fs";
|
|
1072
|
-
import { isAbsolute, join } from "path";
|
|
1073
|
-
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
1074
|
-
function repairTornRuns(store, wal, opts = {}) {
|
|
1075
|
-
const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
1076
|
-
const replay = wal.replay();
|
|
1077
|
-
const liveClaims = replay.liveClaims(opts.now);
|
|
1078
|
-
const report = { interrupted: 0, requeued: 0, failed: 0 };
|
|
1079
|
-
const running = store.listRuns({ status: "running", limit: 1000 });
|
|
1080
|
-
for (const run of running) {
|
|
1081
|
-
if (liveClaims.has(run.id))
|
|
1082
|
-
continue;
|
|
1083
|
-
report.interrupted++;
|
|
1084
|
-
const nodes = store.listRunNodes(run.id);
|
|
1085
|
-
if (run.attempts < maxAttempts) {
|
|
1086
|
-
store.bumpAttempts(run.id);
|
|
1087
|
-
store.setRunStatus(run.id, "interrupted", {
|
|
1088
|
-
error: `torn run interrupted after claim loss (attempt ${run.attempts + 1}/${maxAttempts})`
|
|
1089
|
-
});
|
|
1090
|
-
for (const node of nodes) {
|
|
1091
|
-
if (node.status === "running") {
|
|
1092
|
-
store.setRunNodeStatus(node.id, "pending", { error: undefined });
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
report.requeued++;
|
|
1096
|
-
} else {
|
|
1097
|
-
store.setRunStatus(run.id, "failed", {
|
|
1098
|
-
error: `torn run failed after ${maxAttempts} interrupted attempts`
|
|
1099
|
-
});
|
|
1100
|
-
for (const node of nodes) {
|
|
1101
|
-
if (node.status === "running") {
|
|
1102
|
-
store.setRunNodeStatus(node.id, "failed", { error: "run interrupted beyond retry budget" });
|
|
575
|
+
const bodySet = new Set(node.body);
|
|
576
|
+
for (const bodyId of node.body) {
|
|
577
|
+
const bodyNode = findNode(graph, bodyId);
|
|
578
|
+
if (!bodyNode)
|
|
579
|
+
continue;
|
|
580
|
+
for (const { kind, target } of nodeEdges(bodyNode)) {
|
|
581
|
+
if (!bodySet.has(target)) {
|
|
582
|
+
issue(issues, `nodes.${node.id}`, `while body node ${bodyId} ${kind}-edge leaves the body (target ${target})`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
1103
585
|
}
|
|
1104
586
|
}
|
|
1105
|
-
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
return report;
|
|
1109
|
-
}
|
|
1110
|
-
function inputHash(payload) {
|
|
1111
|
-
return createHash("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
1112
|
-
}
|
|
1113
|
-
function memoKey(graphName, nodeId, input) {
|
|
1114
|
-
return `${graphName}:${nodeId}:${inputHash(input)}`;
|
|
1115
|
-
}
|
|
1116
|
-
function tryMemoHit(store, graphName, nodeId, input) {
|
|
1117
|
-
const key = memoKey(graphName, nodeId, input);
|
|
1118
|
-
const memo = store.memoGet(key);
|
|
1119
|
-
if (!memo)
|
|
1120
|
-
return;
|
|
1121
|
-
store.memoHit(key);
|
|
1122
|
-
return memo;
|
|
1123
|
-
}
|
|
1124
|
-
function recordMemo(store, graphName, nodeId, input, output) {
|
|
1125
|
-
const key = memoKey(graphName, nodeId, input);
|
|
1126
|
-
store.memoPut(key, graphName, nodeId, inputHash(input), JSON.stringify(output));
|
|
1127
|
-
}
|
|
1128
|
-
function memoWatchFingerprint(graph, dataDir) {
|
|
1129
|
-
if (!graph.memoWatch || graph.memoWatch.length === 0)
|
|
1130
|
-
return;
|
|
1131
|
-
const entries = [];
|
|
1132
|
-
for (const pattern of graph.memoWatch) {
|
|
1133
|
-
if (hasGlobMagic(pattern)) {
|
|
1134
|
-
const matches = [...new Bun.Glob(pattern).scanSync({ cwd: dataDir, onlyFiles: true })].sort();
|
|
1135
|
-
for (const match of matches) {
|
|
1136
|
-
const path2 = isAbsolute(match) ? match : join(dataDir, match);
|
|
1137
|
-
entries.push(fingerprintFile(pattern, path2));
|
|
1138
|
-
}
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
const path = isAbsolute(pattern) ? pattern : join(dataDir, pattern);
|
|
1142
|
-
entries.push(fingerprintFile(pattern, path));
|
|
1143
|
-
}
|
|
1144
|
-
return entries;
|
|
1145
|
-
}
|
|
1146
|
-
function hasGlobMagic(pattern) {
|
|
1147
|
-
return /[*?[\]{}]/.test(pattern);
|
|
1148
|
-
}
|
|
1149
|
-
function fingerprintFile(pattern, path) {
|
|
1150
|
-
try {
|
|
1151
|
-
const stat = statSync(path);
|
|
1152
|
-
if (!stat.isFile()) {
|
|
1153
|
-
return { pattern, path, missing: true, mtimeMs: null, size: null, sha256: null };
|
|
587
|
+
break;
|
|
1154
588
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
pattern,
|
|
1158
|
-
path,
|
|
1159
|
-
missing: false,
|
|
1160
|
-
mtimeMs: stat.mtimeMs,
|
|
1161
|
-
size: content.byteLength,
|
|
1162
|
-
sha256: createHash("sha256").update(content).digest("hex")
|
|
1163
|
-
};
|
|
1164
|
-
} catch {
|
|
1165
|
-
return { pattern, path, missing: true, mtimeMs: null, size: null, sha256: null };
|
|
589
|
+
case "end":
|
|
590
|
+
break;
|
|
1166
591
|
}
|
|
1167
592
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
593
|
+
var GRAPH_NODE_TYPES, LANE_KINDS, CODEX_EXECUTION_MODES, NODE_ID_RE;
|
|
594
|
+
var init_graph = __esm(() => {
|
|
595
|
+
init_expr();
|
|
596
|
+
GRAPH_NODE_TYPES = ["start", "step", "decision", "while", "end"];
|
|
597
|
+
LANE_KINDS = ["claude", "codex", "cursor", "grok"];
|
|
598
|
+
CODEX_EXECUTION_MODES = ["repository", "read-only"];
|
|
599
|
+
NODE_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// src/lanes/types.ts
|
|
603
|
+
import { statSync } from "fs";
|
|
604
|
+
function binaryOnPath(binary) {
|
|
605
|
+
const pathEntries = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
606
|
+
for (const entry of pathEntries) {
|
|
1171
607
|
try {
|
|
1172
|
-
const
|
|
1173
|
-
if (
|
|
1174
|
-
return
|
|
608
|
+
const stat = statSync(`${entry}/${binary}`);
|
|
609
|
+
if (stat.isFile())
|
|
610
|
+
return true;
|
|
1175
611
|
} catch {}
|
|
1176
612
|
}
|
|
1177
|
-
return;
|
|
613
|
+
return false;
|
|
1178
614
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
615
|
+
function subscriptionEnvironment(overrides = {}) {
|
|
616
|
+
const paid = /^(OPENAI_API_KEY|ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|CODEX_API_KEY|CURSOR_API_KEY)$/;
|
|
617
|
+
const sensitive = /(?:API[_-]?KEY|SECRET|PASSWORD|TOKEN|DATABASE_URL|CREDENTIAL)/i;
|
|
618
|
+
const env = {};
|
|
619
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
620
|
+
if (value !== undefined && !paid.test(name) && (!sensitive.test(name) || name === "CLAUDE_CODE_OAUTH_TOKEN"))
|
|
621
|
+
env[name] = value;
|
|
622
|
+
}
|
|
623
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
624
|
+
if (paid.test(name) || sensitive.test(name))
|
|
625
|
+
throw new Error(`lane environment field ${name} requires a supported scoped credential consumer`);
|
|
626
|
+
env[name] = value;
|
|
627
|
+
}
|
|
628
|
+
return env;
|
|
1189
629
|
}
|
|
1190
|
-
function
|
|
1191
|
-
const
|
|
1192
|
-
return
|
|
630
|
+
function subscriptionSdkEnvironment(overrides = {}) {
|
|
631
|
+
const safe = subscriptionEnvironment(overrides);
|
|
632
|
+
return { ...Object.fromEntries(Object.keys(process.env).map((name) => [name, undefined])), ...safe };
|
|
1193
633
|
}
|
|
1194
|
-
function
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
634
|
+
function withTimeout(promise, timeoutMs, what, cancel) {
|
|
635
|
+
return new Promise((resolve, reject) => {
|
|
636
|
+
const timer = setTimeout(() => {
|
|
637
|
+
cancel?.();
|
|
638
|
+
reject(new LaneTimeoutError(what, timeoutMs));
|
|
639
|
+
}, timeoutMs);
|
|
640
|
+
promise.then((value) => {
|
|
641
|
+
clearTimeout(timer);
|
|
642
|
+
resolve(value);
|
|
643
|
+
}, (err) => {
|
|
644
|
+
clearTimeout(timer);
|
|
645
|
+
reject(err);
|
|
646
|
+
});
|
|
1207
647
|
});
|
|
1208
|
-
const durationMs = Date.now() - started;
|
|
1209
|
-
const stdout = proc.stdout ? new TextDecoder().decode(proc.stdout) : "";
|
|
1210
|
-
const stderr = proc.stderr ? new TextDecoder().decode(proc.stderr) : "";
|
|
1211
|
-
const output = stderr ? `${stdout}
|
|
1212
|
-
${stderr}`.trim() : stdout.trim();
|
|
1213
|
-
const exitCode = proc.exitCode ?? (proc.signalCode ? 1 : 1);
|
|
1214
|
-
return { ok: exitCode === 0, exitCode, output, durationMs };
|
|
1215
648
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
this.wal = wal;
|
|
1235
|
-
this.worker = options.worker ?? "local";
|
|
1236
|
-
this.leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
1237
|
-
this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS2;
|
|
1238
|
-
this.maxDispatchPerCycle = options.maxDispatchPerCycle ?? DEFAULT_MAX_DISPATCH;
|
|
1239
|
-
this.maxStepsPerCycle = options.maxStepsPerCycle ?? DEFAULT_MAX_STEPS;
|
|
1240
|
-
this.time = options.time ?? Date.now;
|
|
1241
|
-
this.env = options.env ?? {};
|
|
1242
|
-
this.laneRunner = options.laneRunner ?? defaultLaneRunner;
|
|
1243
|
-
this.isolatedRunId = options.isolatedRunId ?? null;
|
|
1244
|
-
this.replayRegisteredGraphs();
|
|
1245
|
-
}
|
|
1246
|
-
isolateRun(runId) {
|
|
1247
|
-
this.isolatedRunId = runId;
|
|
1248
|
-
}
|
|
1249
|
-
replayRegisteredGraphs() {
|
|
1250
|
-
const entries = this.wal.replay().entries;
|
|
1251
|
-
for (const entry of entries) {
|
|
1252
|
-
const op = entry.op;
|
|
1253
|
-
if (op.op === "graph_registered") {
|
|
1254
|
-
this.graphCache.set(op.name, op.graph);
|
|
1255
|
-
}
|
|
649
|
+
async function spawnCli(cmd, opts) {
|
|
650
|
+
const started = Date.now();
|
|
651
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
652
|
+
let proc;
|
|
653
|
+
try {
|
|
654
|
+
proc = Bun.spawn({
|
|
655
|
+
cmd,
|
|
656
|
+
cwd: opts.cwd,
|
|
657
|
+
env: subscriptionEnvironment(opts.env),
|
|
658
|
+
stdin: "ignore",
|
|
659
|
+
stdout: "pipe",
|
|
660
|
+
stderr: "pipe",
|
|
661
|
+
timeout: timeoutMs
|
|
662
|
+
});
|
|
663
|
+
} catch (err) {
|
|
664
|
+
const message = String(err instanceof Error ? err.message : err);
|
|
665
|
+
if (message.includes("ENOENT") || /not found/i.test(message)) {
|
|
666
|
+
throw new LaneDependencyMissingError(`lane binary ${JSON.stringify(opts.binaryName)} not found on PATH; install it or provide ${opts.packageHint}`);
|
|
1256
667
|
}
|
|
668
|
+
return { ok: false, exitCode: 1, output: "", durationMs: Date.now() - started, error: message };
|
|
1257
669
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
670
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
671
|
+
new Response(proc.stdout).text(),
|
|
672
|
+
new Response(proc.stderr).text(),
|
|
673
|
+
proc.exited
|
|
674
|
+
]);
|
|
675
|
+
const output = opts.stdoutOnly ? stdout.trim() : stderr ? `${stdout}
|
|
676
|
+
${stderr}`.trim() : stdout.trim();
|
|
677
|
+
return {
|
|
678
|
+
ok: exitCode === 0,
|
|
679
|
+
exitCode,
|
|
680
|
+
output,
|
|
681
|
+
durationMs: Date.now() - started,
|
|
682
|
+
...opts.stdoutOnly && exitCode !== 0 && stderr.trim() ? { error: stderr.trim() } : {}
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
var LANE_KINDS2, LaneDependencyMissingError, LaneAdapterShapeError, DEFAULT_LANE_TIMEOUT_MS = 120000, LaneTimeoutError;
|
|
686
|
+
var init_types = __esm(() => {
|
|
687
|
+
LANE_KINDS2 = ["claude", "codex", "cursor", "grok"];
|
|
688
|
+
LaneDependencyMissingError = class LaneDependencyMissingError extends Error {
|
|
689
|
+
constructor(message) {
|
|
690
|
+
super(message);
|
|
691
|
+
this.name = "LaneDependencyMissingError";
|
|
1263
692
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
if (existing && existing.worker !== this.worker && existing.expiresAtMs > now) {
|
|
1270
|
-
return null;
|
|
693
|
+
};
|
|
694
|
+
LaneAdapterShapeError = class LaneAdapterShapeError extends Error {
|
|
695
|
+
constructor(message) {
|
|
696
|
+
super(message);
|
|
697
|
+
this.name = "LaneAdapterShapeError";
|
|
1271
698
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
699
|
+
};
|
|
700
|
+
LaneTimeoutError = class LaneTimeoutError extends Error {
|
|
701
|
+
constructor(what, timeoutMs) {
|
|
702
|
+
super(`${what} exceeded the ${timeoutMs}ms budget`);
|
|
703
|
+
this.name = "LaneTimeoutError";
|
|
1276
704
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
705
|
+
};
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// src/lanes/claude.ts
|
|
709
|
+
function createClaudeAdapter(deps = {}) {
|
|
710
|
+
return {
|
|
711
|
+
kind: "claude",
|
|
712
|
+
async probe() {
|
|
713
|
+
const loader = deps.sdkLoader ?? (async () => await import("@anthropic-ai/claude-agent-sdk"));
|
|
714
|
+
try {
|
|
715
|
+
await loader();
|
|
716
|
+
return { kind: "claude", sdk: "@anthropic-ai/claude-agent-sdk", wired: true, via: "sdk" };
|
|
717
|
+
} catch {
|
|
718
|
+
if (binaryOnPath(deps.cliPath ?? "claude")) {
|
|
719
|
+
return { kind: "claude", sdk: "@anthropic-ai/claude-agent-sdk", wired: true, via: "cli" };
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
kind: "claude",
|
|
723
|
+
sdk: "@anthropic-ai/claude-agent-sdk",
|
|
724
|
+
wired: false,
|
|
725
|
+
reason: "neither @anthropic-ai/claude-agent-sdk nor the claude CLI is available on this machine"
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
async run(job) {
|
|
730
|
+
if (!job.prompt) {
|
|
731
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "claude lane requires a prompt" };
|
|
732
|
+
}
|
|
733
|
+
const started = Date.now();
|
|
734
|
+
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
735
|
+
const loader = deps.sdkLoader ?? (async () => await import("@anthropic-ai/claude-agent-sdk"));
|
|
736
|
+
let sdk;
|
|
737
|
+
try {
|
|
738
|
+
sdk = await loader();
|
|
739
|
+
} catch (err) {
|
|
740
|
+
return runClaudeCli(job, deps.cliPath ?? "claude", started, timeoutMs);
|
|
741
|
+
}
|
|
742
|
+
const query = sdk.query;
|
|
743
|
+
if (typeof query !== "function") {
|
|
744
|
+
throw new LaneAdapterShapeError("@anthropic-ai/claude-agent-sdk exports no query() function");
|
|
745
|
+
}
|
|
746
|
+
const prompt = job.prompt;
|
|
747
|
+
const abortController = new AbortController;
|
|
748
|
+
try {
|
|
749
|
+
const { output, isError } = await withTimeout((async () => {
|
|
750
|
+
const generator = await query({
|
|
751
|
+
prompt,
|
|
752
|
+
options: { model: job.model, cwd: job.cwd, maxTurns: 32, abortController, env: subscriptionSdkEnvironment(job.env) }
|
|
753
|
+
});
|
|
754
|
+
let output2 = "";
|
|
755
|
+
let isError2 = false;
|
|
756
|
+
for await (const message of generator) {
|
|
757
|
+
if (message.type === "assistant") {
|
|
758
|
+
const content = message.message?.content;
|
|
759
|
+
if (Array.isArray(content)) {
|
|
760
|
+
for (const block of content) {
|
|
761
|
+
if (block && typeof block === "object" && "text" in block && typeof block.text === "string") {
|
|
762
|
+
output2 += block.text;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (message.type === "error")
|
|
768
|
+
isError2 = true;
|
|
769
|
+
}
|
|
770
|
+
return { output: output2, isError: isError2 };
|
|
771
|
+
})(), timeoutMs, "claude lane", () => abortController.abort());
|
|
772
|
+
const durationMs = Date.now() - started;
|
|
773
|
+
return { ok: !isError, exitCode: isError ? 1 : 0, output: output.trim(), durationMs };
|
|
774
|
+
} catch (err) {
|
|
775
|
+
const durationMs = Date.now() - started;
|
|
776
|
+
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err), retryable: !(err instanceof LaneTimeoutError) };
|
|
1313
777
|
}
|
|
1314
778
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
report.dispatched++;
|
|
1331
|
-
}
|
|
1332
|
-
const running = this.store.listRuns({ status: "running", limit: this.maxStepsPerCycle });
|
|
1333
|
-
let steps = 0;
|
|
1334
|
-
for (const run of running) {
|
|
1335
|
-
if (this.isolatedRunId !== null && run.id !== this.isolatedRunId)
|
|
1336
|
-
continue;
|
|
1337
|
-
if (steps >= this.maxStepsPerCycle)
|
|
1338
|
-
break;
|
|
1339
|
-
const outcome = await this.advanceRun(run.id);
|
|
1340
|
-
steps++;
|
|
1341
|
-
if (outcome.kind === "completed")
|
|
1342
|
-
report.completed++;
|
|
1343
|
-
if (outcome.kind === "failed")
|
|
1344
|
-
report.failed++;
|
|
1345
|
-
if (outcome.kind === "advanced")
|
|
1346
|
-
report.advanced++;
|
|
1347
|
-
}
|
|
1348
|
-
return report;
|
|
1349
|
-
}
|
|
1350
|
-
startRun(graph, context) {
|
|
1351
|
-
const validation = validateGraph(graph);
|
|
1352
|
-
if (!validation.ok) {
|
|
1353
|
-
throw new Error(`graph validation failed: ${validation.issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`);
|
|
1354
|
-
}
|
|
1355
|
-
assertNoSecrets(context, "run context");
|
|
1356
|
-
assertNoSecrets(graph, "graph definition");
|
|
1357
|
-
const existing = this.graphCache.get(graph.name);
|
|
1358
|
-
const serialized = JSON.stringify(graph);
|
|
1359
|
-
if (!existing || JSON.stringify(existing) !== serialized) {
|
|
1360
|
-
this.graphCache.set(graph.name, graph);
|
|
1361
|
-
this.wal.append({ op: "graph_registered", name: graph.name, version: graph.version, graph, at: nowIso() });
|
|
1362
|
-
} else {
|
|
1363
|
-
this.graphCache.set(graph.name, existing);
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
async function runClaudeCli(job, cliPath, started, timeoutMs) {
|
|
782
|
+
try {
|
|
783
|
+
const result = await spawnCli([cliPath, "-p", job.prompt], {
|
|
784
|
+
binaryName: cliPath,
|
|
785
|
+
packageHint: "@anthropic-ai/claude-agent-sdk or the claude CLI",
|
|
786
|
+
timeoutMs,
|
|
787
|
+
env: job.env,
|
|
788
|
+
cwd: job.cwd
|
|
789
|
+
});
|
|
790
|
+
return { ...result, durationMs: Date.now() - started };
|
|
791
|
+
} catch (err) {
|
|
792
|
+
if (err instanceof LaneDependencyMissingError) {
|
|
793
|
+
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
1364
794
|
}
|
|
1365
|
-
|
|
795
|
+
throw err;
|
|
1366
796
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
loops: rawWf.loops ?? {},
|
|
1385
|
-
completedLoops: rawWf.completedLoops ?? {}
|
|
1386
|
-
};
|
|
1387
|
-
try {
|
|
1388
|
-
const cursor = wf.cursor;
|
|
1389
|
-
const node = cursor === undefined ? graph.nodes.find((n) => n.type === "start") : findNode(graph, cursor);
|
|
1390
|
-
if (!node) {
|
|
1391
|
-
this.failRun(run, `cursor ${cursor ?? "<start>"} resolves to no node`);
|
|
1392
|
-
return { kind: "failed", error: "no node at cursor" };
|
|
1393
|
-
}
|
|
1394
|
-
switch (node.type) {
|
|
1395
|
-
case "start": {
|
|
1396
|
-
wf.cursor = node.next;
|
|
1397
|
-
this.persistContext(run, context, wf);
|
|
1398
|
-
return { kind: "advanced", nodeId: node.id };
|
|
1399
|
-
}
|
|
1400
|
-
case "step": {
|
|
1401
|
-
return await this.executeStep(run, graph, context, wf, node);
|
|
1402
|
-
}
|
|
1403
|
-
case "decision": {
|
|
1404
|
-
const value = evaluateExpr(node.condition, this.conditionScope(context));
|
|
1405
|
-
const next = value ? node.then : node.else;
|
|
1406
|
-
if (!next) {
|
|
1407
|
-
this.failRun(run, `decision ${node.id} evaluated ${value} but no ${value ? "then" : "else"} edge`);
|
|
1408
|
-
return { kind: "failed", error: "missing decision edge" };
|
|
1409
|
-
}
|
|
1410
|
-
wf.cursor = next;
|
|
1411
|
-
this.persistContext(run, context, wf);
|
|
1412
|
-
return { kind: "advanced", nodeId: node.id };
|
|
797
|
+
}
|
|
798
|
+
var init_claude = __esm(() => {
|
|
799
|
+
init_types();
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
// src/lanes/codex.ts
|
|
803
|
+
function createCodexAdapter(deps = {}) {
|
|
804
|
+
return {
|
|
805
|
+
kind: "codex",
|
|
806
|
+
async probe() {
|
|
807
|
+
const loader = deps.sdkLoader ?? (async () => await import("@openai/codex-sdk"));
|
|
808
|
+
try {
|
|
809
|
+
await loader();
|
|
810
|
+
return { kind: "codex", sdk: "@openai/codex-sdk", wired: true, via: "sdk" };
|
|
811
|
+
} catch {
|
|
812
|
+
if (binaryOnPath(deps.cliPath ?? "codex")) {
|
|
813
|
+
return { kind: "codex", sdk: "@openai/codex-sdk", wired: true, via: "cli" };
|
|
1413
814
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
815
|
+
return {
|
|
816
|
+
kind: "codex",
|
|
817
|
+
sdk: "@openai/codex-sdk",
|
|
818
|
+
wired: false,
|
|
819
|
+
reason: "neither @openai/codex-sdk nor the codex CLI is available on this machine"
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
async run(job) {
|
|
824
|
+
if (!job.prompt) {
|
|
825
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "codex lane requires a prompt" };
|
|
826
|
+
}
|
|
827
|
+
if (job.codexMode !== undefined && (!CODEX_EXECUTION_MODES.includes(job.codexMode) || job.lane !== "codex" || job.command !== undefined)) {
|
|
828
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "codexMode must be repository or read-only on a codex prompt without command", retryable: false };
|
|
829
|
+
}
|
|
830
|
+
const started = Date.now();
|
|
831
|
+
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
832
|
+
const loader = deps.sdkLoader ?? (async () => await import("@openai/codex-sdk"));
|
|
833
|
+
let sdk;
|
|
834
|
+
try {
|
|
835
|
+
sdk = await loader();
|
|
836
|
+
} catch {
|
|
837
|
+
return runCodexCli(job, deps.cliPath ?? "codex", started, timeoutMs);
|
|
838
|
+
}
|
|
839
|
+
const CodexClass = sdk.Codex;
|
|
840
|
+
if (typeof CodexClass !== "function") {
|
|
841
|
+
throw new LaneAdapterShapeError("@openai/codex-sdk exports no Codex class");
|
|
842
|
+
}
|
|
843
|
+
const abortController = new AbortController;
|
|
844
|
+
try {
|
|
845
|
+
const client = await withTimeout(Promise.resolve(new CodexClass({
|
|
846
|
+
config: { model: job.model },
|
|
847
|
+
env: subscriptionEnvironment(job.env)
|
|
848
|
+
})), timeoutMs, "codex lane");
|
|
849
|
+
const thread = client.startThread?.({
|
|
850
|
+
model: job.model,
|
|
851
|
+
workingDirectory: job.cwd,
|
|
852
|
+
...job.codexMode === "read-only" ? { sandboxMode: "read-only", skipGitRepoCheck: true, approvalPolicy: "never" } : {}
|
|
853
|
+
});
|
|
854
|
+
if (!thread || typeof thread.run !== "function") {
|
|
855
|
+
throw new LaneAdapterShapeError("@openai/codex-sdk Codex.startThread() returned no run()");
|
|
1416
856
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
857
|
+
const turn = await withTimeout(thread.run(job.prompt, { signal: abortController.signal }), timeoutMs, "codex lane", () => abortController.abort());
|
|
858
|
+
const durationMs = Date.now() - started;
|
|
859
|
+
const output = turn?.finalResponse;
|
|
860
|
+
if (typeof output !== "string" || !output.trim()) {
|
|
861
|
+
return missingResponse(durationMs);
|
|
1420
862
|
}
|
|
863
|
+
return { ok: true, exitCode: 0, output, durationMs };
|
|
864
|
+
} catch (err) {
|
|
865
|
+
const durationMs = Date.now() - started;
|
|
866
|
+
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err), retryable: !(err instanceof LaneTimeoutError) };
|
|
1421
867
|
}
|
|
1422
|
-
} catch (err) {
|
|
1423
|
-
if (err instanceof ExprEvalError || err instanceof ExprSyntaxError) {
|
|
1424
|
-
this.failRun(run, `condition error at ${wf.cursor ?? "<start>"}: ${err.message}`);
|
|
1425
|
-
return { kind: "failed", error: err.message };
|
|
1426
|
-
}
|
|
1427
|
-
throw err;
|
|
1428
868
|
}
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
async function runCodexCli(job, cliPath, started, timeoutMs) {
|
|
872
|
+
try {
|
|
873
|
+
const controls = job.codexMode === "read-only" ? ["--sandbox", "read-only", "--skip-git-repo-check", "--config", 'approval_policy="never"'] : [];
|
|
874
|
+
const result = await spawnCli([cliPath, "exec", ...controls, "--", job.prompt], {
|
|
875
|
+
binaryName: cliPath,
|
|
876
|
+
packageHint: "@openai/codex-sdk or the codex CLI",
|
|
877
|
+
timeoutMs,
|
|
878
|
+
env: job.env,
|
|
879
|
+
cwd: job.cwd,
|
|
880
|
+
stdoutOnly: true
|
|
881
|
+
});
|
|
882
|
+
const durationMs = Date.now() - started;
|
|
883
|
+
return result.ok && !result.output.trim() ? missingResponse(durationMs) : { ...result, durationMs };
|
|
884
|
+
} catch (err) {
|
|
885
|
+
if (err instanceof LaneDependencyMissingError) {
|
|
886
|
+
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
887
|
+
}
|
|
888
|
+
throw err;
|
|
1429
889
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
890
|
+
}
|
|
891
|
+
function missingResponse(durationMs) {
|
|
892
|
+
return { ok: false, exitCode: 1, output: "", durationMs, error: "Codex completed without an agent response; verify the native subscription and model route", retryable: false };
|
|
893
|
+
}
|
|
894
|
+
var init_codex = __esm(() => {
|
|
895
|
+
init_graph();
|
|
896
|
+
init_types();
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// src/lanes/cursor.ts
|
|
900
|
+
function createCursorAdapter(deps = {}) {
|
|
901
|
+
return {
|
|
902
|
+
kind: "cursor",
|
|
903
|
+
async probe() {
|
|
904
|
+
const loader = deps.sdkLoader ?? (async () => await import("@cursor/sdk"));
|
|
905
|
+
try {
|
|
906
|
+
await loader();
|
|
907
|
+
return { kind: "cursor", sdk: "@cursor/sdk", wired: true, via: "sdk (local mode)" };
|
|
908
|
+
} catch {
|
|
909
|
+
if (binaryOnPath(deps.cliPath ?? "cursor-agent")) {
|
|
910
|
+
return { kind: "cursor", sdk: "@cursor/sdk", wired: true, via: "cli" };
|
|
911
|
+
}
|
|
912
|
+
return {
|
|
913
|
+
kind: "cursor",
|
|
914
|
+
sdk: "@cursor/sdk",
|
|
915
|
+
wired: false,
|
|
916
|
+
reason: "neither @cursor/sdk nor the cursor-agent CLI is available on this machine"
|
|
917
|
+
};
|
|
1456
918
|
}
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
}
|
|
1462
|
-
const job = {
|
|
1463
|
-
lane: node.lane ?? "claude",
|
|
1464
|
-
prompt: node.prompt,
|
|
1465
|
-
command: node.command,
|
|
1466
|
-
timeoutMs: node.timeoutMs,
|
|
1467
|
-
env: this.env
|
|
1468
|
-
};
|
|
1469
|
-
let result;
|
|
1470
|
-
try {
|
|
1471
|
-
result = await this.laneRunner(job);
|
|
1472
|
-
} catch (err) {
|
|
1473
|
-
result = { ok: false, exitCode: 1, output: "", durationMs: 0, error: String(err instanceof Error ? err.message : err) };
|
|
1474
|
-
}
|
|
1475
|
-
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1476
|
-
if (!result.ok) {
|
|
1477
|
-
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1478
|
-
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1479
|
-
if (shouldRetry) {
|
|
1480
|
-
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1481
|
-
return { kind: "advanced", nodeId };
|
|
919
|
+
},
|
|
920
|
+
async run(job) {
|
|
921
|
+
if (!job.prompt) {
|
|
922
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "cursor lane requires a prompt" };
|
|
1482
923
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
this.store.setRunNodeStatus(nodeRow.id, "failed", { error: message });
|
|
1494
|
-
this.wal.append({ op: "node_finished", runId: run.id, nodeId, status: "failed", at: nowIso() });
|
|
1495
|
-
this.failRun(run, message);
|
|
1496
|
-
return { kind: "failed", error: message };
|
|
1497
|
-
}
|
|
1498
|
-
this.store.setRunNodeStatus(nodeRow.id, "completed", { exitCode: result.exitCode, output });
|
|
1499
|
-
this.wal.append({ op: "node_finished", runId: run.id, nodeId, status: "completed", at: nowIso() });
|
|
1500
|
-
if (memoInput !== undefined) {
|
|
1501
|
-
const memoInputAfter = node.memo ? memoWatchFingerprint(graph, this.store.dataDir) : undefined;
|
|
1502
|
-
const stable = memoWatch === undefined && memoInputAfter === undefined || memoWatch !== undefined && memoInputAfter !== undefined && JSON.stringify(memoWatch) === JSON.stringify(memoInputAfter);
|
|
1503
|
-
if (stable) {
|
|
1504
|
-
recordMemo(this.store, run.graphName, nodeId, memoInput, output);
|
|
1505
|
-
this.wal.append({ op: "memo_set", key: memoKey(run.graphName, nodeId, memoInput), at: nowIso() });
|
|
924
|
+
if (!deps.sdkLoader && binaryOnPath(deps.cliPath ?? "cursor-agent"))
|
|
925
|
+
return runCursorCli(job, deps.cliPath ?? "cursor-agent", Date.now(), job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS);
|
|
926
|
+
const started = Date.now();
|
|
927
|
+
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
928
|
+
const loader = deps.sdkLoader ?? (async () => await import("@cursor/sdk"));
|
|
929
|
+
let sdk;
|
|
930
|
+
try {
|
|
931
|
+
sdk = await loader();
|
|
932
|
+
} catch {
|
|
933
|
+
return runCursorCli(job, deps.cliPath ?? "cursor-agent", started, timeoutMs);
|
|
1506
934
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
this.persistContext(run, context, wf);
|
|
1525
|
-
return { kind: "advanced", nodeId: wf.cursor };
|
|
935
|
+
let cancel;
|
|
936
|
+
try {
|
|
937
|
+
subscriptionEnvironment(job.env);
|
|
938
|
+
if (!deps.sdkLoader && process.env.CURSOR_API_KEY)
|
|
939
|
+
throw new Error("cursor lane refuses ambient API billing; use the native subscription CLI");
|
|
940
|
+
const run = await withTimeout(runLocalAgent(sdk, job, (callback) => {
|
|
941
|
+
cancel = callback;
|
|
942
|
+
}), timeoutMs, "cursor lane", () => cancel?.());
|
|
943
|
+
const durationMs = Date.now() - started;
|
|
944
|
+
const status = run.status;
|
|
945
|
+
const ok = status === "finished";
|
|
946
|
+
return { ok, exitCode: ok ? 0 : 1, output: run.result, durationMs };
|
|
947
|
+
} catch (err) {
|
|
948
|
+
if (err instanceof LaneAdapterShapeError)
|
|
949
|
+
throw err;
|
|
950
|
+
const durationMs = Date.now() - started;
|
|
951
|
+
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err), retryable: !(err instanceof LaneTimeoutError) };
|
|
1526
952
|
}
|
|
1527
|
-
wf.loops[whileNode.id] = (wf.loops[whileNode.id] ?? 0) + 1;
|
|
1528
|
-
wf.cursor = whileNode.id;
|
|
1529
|
-
this.persistContext(run, context, wf);
|
|
1530
|
-
return { kind: "advanced", nodeId: whileNode.id };
|
|
1531
953
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
async function runLocalAgent(sdk, job, onCancel) {
|
|
957
|
+
const AgentClass = sdk.Agent;
|
|
958
|
+
const CursorClass = sdk.Cursor;
|
|
959
|
+
if (typeof AgentClass !== "function" && typeof CursorClass !== "function") {
|
|
960
|
+
throw new LaneAdapterShapeError("@cursor/sdk exports no Agent/Cursor class");
|
|
1534
961
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
this.failRun(run, error);
|
|
1540
|
-
return { kind: "failed", error };
|
|
1541
|
-
}
|
|
1542
|
-
const scope = { ...this.conditionScope(context), i: iteration };
|
|
1543
|
-
const keepGoing = evaluateExpr(node.condition, scope);
|
|
1544
|
-
if (!keepGoing) {
|
|
1545
|
-
wf.completedLoops[node.id] = iteration;
|
|
1546
|
-
delete wf.loops[node.id];
|
|
1547
|
-
wf.cursor = node.next;
|
|
1548
|
-
this.persistContext(run, context, wf);
|
|
1549
|
-
return { kind: "advanced", nodeId: node.id };
|
|
1550
|
-
}
|
|
1551
|
-
wf.cursor = node.body[0];
|
|
1552
|
-
this.persistContext(run, context, wf);
|
|
1553
|
-
return { kind: "advanced", nodeId: node.id };
|
|
962
|
+
const create = AgentClass.create;
|
|
963
|
+
const agent = typeof create === "function" ? await create.call(AgentClass, { local: { cwd: job.cwd, enableAgentRetries: false } }) : typeof AgentClass === "function" ? new AgentClass : undefined;
|
|
964
|
+
if (!agent || typeof agent.send !== "function") {
|
|
965
|
+
throw new LaneAdapterShapeError("@cursor/sdk Agent has no send()");
|
|
1554
966
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
967
|
+
let run = await agent.send(job.prompt, { model: job.model });
|
|
968
|
+
if (run && typeof run === "object") {
|
|
969
|
+
const handle = run;
|
|
970
|
+
if (typeof handle.cancel === "function")
|
|
971
|
+
onCancel(() => {
|
|
972
|
+
handle.cancel().catch(() => {});
|
|
973
|
+
});
|
|
974
|
+
if (typeof handle.wait === "function")
|
|
975
|
+
run = await handle.wait();
|
|
1559
976
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
steps: context.steps ?? {},
|
|
1563
|
-
iterations: context.__wf?.completedLoops ?? {}
|
|
1564
|
-
};
|
|
1565
|
-
try {
|
|
1566
|
-
assertNoSecrets(result, "run result");
|
|
1567
|
-
} catch (err) {
|
|
1568
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1569
|
-
this.failRun(run, message);
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
this.store.setRunStatus(run.id, "completed", { result, finishedAt: nowIso(), error: null });
|
|
1573
|
-
this.wal.append({ op: "run_finished", runId: run.id, status: "completed", at: nowIso() });
|
|
1574
|
-
const lease = this.leases.get(run.id);
|
|
1575
|
-
if (lease)
|
|
1576
|
-
this.release(run.id, lease.fencing);
|
|
977
|
+
if (!run || typeof run !== "object") {
|
|
978
|
+
throw new LaneAdapterShapeError("@cursor/sdk Agent.send() returned no Run");
|
|
1577
979
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
const lease = this.leases.get(run.id);
|
|
1582
|
-
if (lease)
|
|
1583
|
-
this.release(run.id, lease.fencing);
|
|
980
|
+
const candidate = run;
|
|
981
|
+
if (typeof candidate.status !== "string" || !["finished", "error", "cancelled"].includes(candidate.status)) {
|
|
982
|
+
throw new LaneAdapterShapeError(`@cursor/sdk Agent.send() returned a Run without a terminal status (got ${JSON.stringify(candidate.status)}; expected finished|error|cancelled)`);
|
|
1584
983
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
if (job.command)
|
|
1588
|
-
return runCommand(job);
|
|
1589
|
-
const { runLaneJob: runLaneJob2 } = await Promise.resolve().then(() => (init_lanes(), exports_lanes));
|
|
1590
|
-
return runLaneJob2(job);
|
|
1591
|
-
};
|
|
1592
|
-
async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
1593
|
-
const daemon = new WorkflowsDaemon(store, wal, {
|
|
1594
|
-
worker: "local",
|
|
1595
|
-
laneRunner: options.laneRunner,
|
|
1596
|
-
time: options.time,
|
|
1597
|
-
env: options.env,
|
|
1598
|
-
isolatedRunId: options.isolatedRunId
|
|
1599
|
-
});
|
|
1600
|
-
const run = daemon.startRun(graph, context);
|
|
1601
|
-
options.onRunCreated?.(run);
|
|
1602
|
-
daemon.isolateRun(run.id);
|
|
1603
|
-
const maxCycles = options.maxCycles ?? 500;
|
|
1604
|
-
for (let cycle = 0;cycle < maxCycles; cycle++) {
|
|
1605
|
-
const report = await daemon.reap();
|
|
1606
|
-
const current = store.getRun(run.id);
|
|
1607
|
-
if (current.status !== "running")
|
|
1608
|
-
return current;
|
|
1609
|
-
if (report.advanced === 0 && report.dispatched === 0) {
|
|
1610
|
-
return store.getRun(run.id);
|
|
1611
|
-
}
|
|
984
|
+
if (typeof candidate.result !== "string") {
|
|
985
|
+
throw new LaneAdapterShapeError("@cursor/sdk Agent.send() returned a Run without a string result");
|
|
1612
986
|
}
|
|
1613
|
-
return
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
// src/agent-tools.ts
|
|
1617
|
-
init_lanes();
|
|
1618
|
-
|
|
1619
|
-
// src/store.ts
|
|
1620
|
-
import { Database } from "bun:sqlite";
|
|
1621
|
-
import { mkdirSync } from "fs";
|
|
1622
|
-
import { join as join2 } from "path";
|
|
1623
|
-
import { randomUUID } from "crypto";
|
|
1624
|
-
function nowIso2() {
|
|
1625
|
-
return new Date().toISOString();
|
|
1626
|
-
}
|
|
1627
|
-
var BUSY_TIMEOUT_MS = 3000;
|
|
1628
|
-
var BUSY_RETRIES = 2;
|
|
1629
|
-
var BUSY_RETRY_SLEEP_MS = 50;
|
|
1630
|
-
function isBusyError(err) {
|
|
1631
|
-
if (!(err instanceof Error))
|
|
1632
|
-
return false;
|
|
1633
|
-
const code = String(err.code ?? "");
|
|
1634
|
-
return code.includes("BUSY") || /\bdatabase is locked\b/i.test(err.message);
|
|
987
|
+
return { status: candidate.status, result: candidate.result };
|
|
1635
988
|
}
|
|
1636
|
-
function
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
989
|
+
async function runCursorCli(job, cliPath, started, timeoutMs) {
|
|
990
|
+
try {
|
|
991
|
+
const result = await spawnCli([cliPath, "--print", job.prompt, ...job.model ? ["--model", job.model] : []], {
|
|
992
|
+
binaryName: cliPath,
|
|
993
|
+
packageHint: "@cursor/sdk (local mode) or the cursor-agent CLI",
|
|
994
|
+
timeoutMs,
|
|
995
|
+
env: job.env,
|
|
996
|
+
cwd: job.cwd
|
|
997
|
+
});
|
|
998
|
+
return { ...result, durationMs: Date.now() - started };
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
if (err instanceof LaneDependencyMissingError) {
|
|
1001
|
+
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
1646
1002
|
}
|
|
1003
|
+
throw err;
|
|
1647
1004
|
}
|
|
1648
|
-
throw last;
|
|
1649
1005
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
retryBusy(() => db.exec(`
|
|
1657
|
-
CREATE TABLE IF NOT EXISTS runs (
|
|
1658
|
-
id TEXT PRIMARY KEY,
|
|
1659
|
-
graph_name TEXT NOT NULL,
|
|
1660
|
-
graph_version TEXT NOT NULL,
|
|
1661
|
-
status TEXT NOT NULL,
|
|
1662
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
1663
|
-
context_json TEXT NOT NULL,
|
|
1664
|
-
result_json TEXT,
|
|
1665
|
-
error TEXT,
|
|
1666
|
-
started_at TEXT,
|
|
1667
|
-
finished_at TEXT,
|
|
1668
|
-
created_at TEXT NOT NULL,
|
|
1669
|
-
updated_at TEXT NOT NULL
|
|
1670
|
-
);
|
|
1671
|
-
CREATE TABLE IF NOT EXISTS run_nodes (
|
|
1672
|
-
id TEXT PRIMARY KEY,
|
|
1673
|
-
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
1674
|
-
node_id TEXT NOT NULL,
|
|
1675
|
-
status TEXT NOT NULL,
|
|
1676
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
1677
|
-
lane TEXT,
|
|
1678
|
-
exit_code INTEGER,
|
|
1679
|
-
output_json TEXT,
|
|
1680
|
-
error TEXT,
|
|
1681
|
-
started_at TEXT,
|
|
1682
|
-
finished_at TEXT
|
|
1683
|
-
);
|
|
1684
|
-
CREATE TABLE IF NOT EXISTS memos (
|
|
1685
|
-
key TEXT PRIMARY KEY,
|
|
1686
|
-
graph_name TEXT NOT NULL,
|
|
1687
|
-
node_id TEXT NOT NULL,
|
|
1688
|
-
input_hash TEXT NOT NULL,
|
|
1689
|
-
output_json TEXT NOT NULL,
|
|
1690
|
-
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
1691
|
-
created_at TEXT NOT NULL
|
|
1692
|
-
);
|
|
1693
|
-
CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);
|
|
1694
|
-
CREATE INDEX IF NOT EXISTS idx_runs_created ON runs(created_at);
|
|
1695
|
-
CREATE INDEX IF NOT EXISTS idx_run_nodes_run ON run_nodes(run_id);
|
|
1696
|
-
`));
|
|
1697
|
-
const rowToRun = (r) => ({
|
|
1698
|
-
id: String(r.id),
|
|
1699
|
-
graphName: String(r.graph_name),
|
|
1700
|
-
graphVersion: String(r.graph_version),
|
|
1701
|
-
status: r.status,
|
|
1702
|
-
attempts: Number(r.attempts),
|
|
1703
|
-
contextJson: String(r.context_json),
|
|
1704
|
-
resultJson: r.result_json === null ? null : String(r.result_json),
|
|
1705
|
-
error: r.error === null ? null : String(r.error),
|
|
1706
|
-
startedAt: r.started_at === null ? null : String(r.started_at),
|
|
1707
|
-
finishedAt: r.finished_at === null ? null : String(r.finished_at),
|
|
1708
|
-
createdAt: String(r.created_at),
|
|
1709
|
-
updatedAt: String(r.updated_at)
|
|
1710
|
-
});
|
|
1711
|
-
const rowToNode = (r) => ({
|
|
1712
|
-
id: String(r.id),
|
|
1713
|
-
runId: String(r.run_id),
|
|
1714
|
-
nodeId: String(r.node_id),
|
|
1715
|
-
status: r.status,
|
|
1716
|
-
attempts: Number(r.attempts),
|
|
1717
|
-
lane: r.lane === null ? null : String(r.lane),
|
|
1718
|
-
exitCode: r.exit_code === null ? null : Number(r.exit_code),
|
|
1719
|
-
outputJson: r.output_json === null ? null : String(r.output_json),
|
|
1720
|
-
error: r.error === null ? null : String(r.error),
|
|
1721
|
-
startedAt: r.started_at === null ? null : String(r.started_at),
|
|
1722
|
-
finishedAt: r.finished_at === null ? null : String(r.finished_at)
|
|
1723
|
-
});
|
|
1724
|
-
const rowToMemo = (r) => ({
|
|
1725
|
-
key: String(r.key),
|
|
1726
|
-
graphName: String(r.graph_name),
|
|
1727
|
-
nodeId: String(r.node_id),
|
|
1728
|
-
inputHash: String(r.input_hash),
|
|
1729
|
-
outputJson: String(r.output_json),
|
|
1730
|
-
hitCount: Number(r.hit_count),
|
|
1731
|
-
createdAt: String(r.created_at)
|
|
1732
|
-
});
|
|
1006
|
+
var init_cursor = __esm(() => {
|
|
1007
|
+
init_types();
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
// src/lanes/grok.ts
|
|
1011
|
+
function createGrokAdapter(deps = {}) {
|
|
1733
1012
|
return {
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
const rows = retryBusy(() => db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all());
|
|
1740
|
-
return rows.map((r) => r.name);
|
|
1741
|
-
},
|
|
1742
|
-
createRun(input) {
|
|
1743
|
-
const id = randomUUID();
|
|
1744
|
-
const now = nowIso2();
|
|
1745
|
-
retryBusy(() => db.query(`INSERT INTO runs (id, graph_name, graph_version, status, attempts, context_json, created_at, updated_at)
|
|
1746
|
-
VALUES (?, ?, ?, 'pending', 0, ?, ?, ?)`).run(id, input.graphName, input.graphVersion, JSON.stringify(input.context ?? {}), now, now));
|
|
1747
|
-
return this.getRun(id);
|
|
1748
|
-
},
|
|
1749
|
-
getRun(id) {
|
|
1750
|
-
const row = retryBusy(() => db.query(`SELECT * FROM runs WHERE id = ?`).get(id));
|
|
1751
|
-
return row ? rowToRun(row) : undefined;
|
|
1752
|
-
},
|
|
1753
|
-
listRuns(opts = {}) {
|
|
1754
|
-
const limit = Math.min(opts.limit ?? 100, 1000);
|
|
1755
|
-
if (opts.status) {
|
|
1756
|
-
const status = opts.status;
|
|
1757
|
-
const rows2 = retryBusy(() => db.query(`SELECT * FROM runs WHERE status = ? ORDER BY created_at DESC, id DESC LIMIT ?`).all(status, limit));
|
|
1758
|
-
return rows2.map((r) => rowToRun(r));
|
|
1013
|
+
kind: "grok",
|
|
1014
|
+
async probe() {
|
|
1015
|
+
const cliPath = deps.cliPath ?? "grok";
|
|
1016
|
+
if (binaryOnPath(cliPath)) {
|
|
1017
|
+
return { kind: "grok", sdk: "no npm SDK (measured 2026-08-25)", wired: true, via: "cli" };
|
|
1759
1018
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
const resultJson = patch.result !== undefined ? JSON.stringify(patch.result) : existing?.resultJson ?? null;
|
|
1767
|
-
const error = patch.error !== undefined ? patch.error : existing?.error ?? null;
|
|
1768
|
-
const startedAt = patch.startedAt !== undefined ? patch.startedAt : status === "running" && existing?.startedAt === null ? now : existing?.startedAt ?? null;
|
|
1769
|
-
const terminal = status === "completed" || status === "failed" || status === "cancelled" || status === "interrupted";
|
|
1770
|
-
const finishedAt = patch.finishedAt !== undefined ? patch.finishedAt : terminal && existing?.finishedAt === null ? now : existing?.finishedAt ?? null;
|
|
1771
|
-
retryBusy(() => db.query(`UPDATE runs SET status = ?, result_json = ?, error = ?, started_at = ?, finished_at = ?, updated_at = ? WHERE id = ?`).run(status, resultJson, error, startedAt, finishedAt, now, id));
|
|
1772
|
-
},
|
|
1773
|
-
bumpAttempts(id) {
|
|
1774
|
-
retryBusy(() => db.query(`UPDATE runs SET attempts = attempts + 1, updated_at = ? WHERE id = ?`).run(nowIso2(), id));
|
|
1775
|
-
},
|
|
1776
|
-
setRunContext(id, context) {
|
|
1777
|
-
retryBusy(() => db.query(`UPDATE runs SET context_json = ?, updated_at = ? WHERE id = ?`).run(JSON.stringify(context), nowIso2(), id));
|
|
1778
|
-
},
|
|
1779
|
-
createRunNode(input) {
|
|
1780
|
-
const id = randomUUID();
|
|
1781
|
-
retryBusy(() => db.query(`INSERT INTO run_nodes (id, run_id, node_id, status, attempts, lane)
|
|
1782
|
-
VALUES (?, ?, ?, 'pending', 0, ?)`).run(id, input.runId, input.nodeId, input.lane ?? null));
|
|
1783
|
-
return this.getRunNode(id);
|
|
1784
|
-
},
|
|
1785
|
-
getRunNode(id) {
|
|
1786
|
-
const row = retryBusy(() => db.query(`SELECT * FROM run_nodes WHERE id = ?`).get(id));
|
|
1787
|
-
return row ? rowToNode(row) : undefined;
|
|
1788
|
-
},
|
|
1789
|
-
listRunNodes(runId) {
|
|
1790
|
-
const rows = retryBusy(() => db.query(`SELECT * FROM run_nodes WHERE run_id = ? ORDER BY rowid ASC`).all(runId));
|
|
1791
|
-
return rows.map((r) => rowToNode(r));
|
|
1792
|
-
},
|
|
1793
|
-
getLatestRunNode(runId, nodeId) {
|
|
1794
|
-
const row = retryBusy(() => db.query(`SELECT * FROM run_nodes WHERE run_id = ? AND node_id = ? ORDER BY rowid DESC LIMIT 1`).get(runId, nodeId));
|
|
1795
|
-
return row ? rowToNode(row) : undefined;
|
|
1796
|
-
},
|
|
1797
|
-
setRunNodeStatus(id, status, patch = {}) {
|
|
1798
|
-
const now = nowIso2();
|
|
1799
|
-
const existing = this.getRunNode(id);
|
|
1800
|
-
if (!existing)
|
|
1801
|
-
return;
|
|
1802
|
-
const exitCode = patch.exitCode !== undefined ? patch.exitCode : existing.exitCode;
|
|
1803
|
-
const outputJson = patch.output !== undefined ? JSON.stringify(patch.output) : existing.outputJson;
|
|
1804
|
-
const error = patch.error !== undefined ? patch.error : existing.error;
|
|
1805
|
-
const startedAt = patch.startedAt !== undefined ? patch.startedAt : status === "running" && existing.startedAt === null ? now : existing.startedAt;
|
|
1806
|
-
const terminal = status === "completed" || status === "failed" || status === "skipped";
|
|
1807
|
-
const finishedAt = patch.finishedAt !== undefined ? patch.finishedAt : terminal && existing.finishedAt === null ? now : existing.finishedAt;
|
|
1808
|
-
retryBusy(() => db.query(`UPDATE run_nodes SET status = ?, exit_code = ?, output_json = ?, error = ?, started_at = ?, finished_at = ? WHERE id = ?`).run(status, exitCode, outputJson, error, startedAt, finishedAt, id));
|
|
1809
|
-
},
|
|
1810
|
-
bumpAttemptsNode(id) {
|
|
1811
|
-
retryBusy(() => db.query(`UPDATE run_nodes SET attempts = attempts + 1 WHERE id = ?`).run(id));
|
|
1812
|
-
},
|
|
1813
|
-
memoPut(key, graphName, nodeId, inputHash2, outputJson) {
|
|
1814
|
-
retryBusy(() => db.query(`INSERT INTO memos (key, graph_name, node_id, input_hash, output_json, hit_count, created_at)
|
|
1815
|
-
VALUES (?, ?, ?, ?, ?, 0, ?)
|
|
1816
|
-
ON CONFLICT(key) DO UPDATE SET output_json = excluded.output_json`).run(key, graphName, nodeId, inputHash2, outputJson, nowIso2()));
|
|
1817
|
-
},
|
|
1818
|
-
memoGet(key) {
|
|
1819
|
-
const row = retryBusy(() => db.query(`SELECT * FROM memos WHERE key = ?`).get(key));
|
|
1820
|
-
return row ? rowToMemo(row) : undefined;
|
|
1821
|
-
},
|
|
1822
|
-
memoHit(key) {
|
|
1823
|
-
retryBusy(() => db.query(`UPDATE memos SET hit_count = hit_count + 1 WHERE key = ?`).run(key));
|
|
1824
|
-
},
|
|
1825
|
-
memoList() {
|
|
1826
|
-
const rows = retryBusy(() => db.query(`SELECT * FROM memos ORDER BY created_at DESC LIMIT 1000`).all());
|
|
1827
|
-
return rows.map((r) => rowToMemo(r));
|
|
1019
|
+
return {
|
|
1020
|
+
kind: "grok",
|
|
1021
|
+
sdk: "no npm SDK (measured 2026-08-25)",
|
|
1022
|
+
wired: false,
|
|
1023
|
+
reason: `no xAI Grok SDK exists on npm (measured 2026-08-25) and the grok CLI is not on PATH`
|
|
1024
|
+
};
|
|
1828
1025
|
},
|
|
1829
|
-
|
|
1830
|
-
|
|
1026
|
+
async run(job) {
|
|
1027
|
+
if (!job.prompt) {
|
|
1028
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "grok lane requires a prompt" };
|
|
1029
|
+
}
|
|
1030
|
+
const started = Date.now();
|
|
1031
|
+
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
1032
|
+
const cliPath = deps.cliPath ?? "grok";
|
|
1033
|
+
try {
|
|
1034
|
+
const result = await spawnCli([cliPath, job.prompt], {
|
|
1035
|
+
binaryName: cliPath,
|
|
1036
|
+
packageHint: "the grok CLI (no xAI Grok SDK exists on npm \u2014 measured 2026-08-25)",
|
|
1037
|
+
timeoutMs,
|
|
1038
|
+
env: job.env,
|
|
1039
|
+
cwd: job.cwd
|
|
1040
|
+
});
|
|
1041
|
+
return { ...result, durationMs: Date.now() - started };
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
if (err instanceof LaneDependencyMissingError) {
|
|
1044
|
+
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
1045
|
+
}
|
|
1046
|
+
throw err;
|
|
1047
|
+
}
|
|
1831
1048
|
}
|
|
1832
1049
|
};
|
|
1833
1050
|
}
|
|
1051
|
+
var init_grok = __esm(() => {
|
|
1052
|
+
init_types();
|
|
1053
|
+
});
|
|
1834
1054
|
|
|
1835
|
-
// src/
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1055
|
+
// src/lanes/index.ts
|
|
1056
|
+
var exports_lanes = {};
|
|
1057
|
+
__export(exports_lanes, {
|
|
1058
|
+
runLaneJob: () => runLaneJob,
|
|
1059
|
+
resolveLane: () => resolveLane,
|
|
1060
|
+
probeLane: () => probeLane,
|
|
1061
|
+
laneInventory: () => laneInventory,
|
|
1062
|
+
createGrokAdapter: () => createGrokAdapter,
|
|
1063
|
+
createCursorAdapter: () => createCursorAdapter,
|
|
1064
|
+
createCodexAdapter: () => createCodexAdapter,
|
|
1065
|
+
createClaudeAdapter: () => createClaudeAdapter,
|
|
1066
|
+
binaryOnPath: () => binaryOnPath,
|
|
1067
|
+
LaneDependencyMissingError: () => LaneDependencyMissingError,
|
|
1068
|
+
LaneAdapterShapeError: () => LaneAdapterShapeError,
|
|
1069
|
+
LANE_KINDS: () => LANE_KINDS2
|
|
1070
|
+
});
|
|
1071
|
+
function resolveLane(kind) {
|
|
1072
|
+
const adapter = adapters.get(kind);
|
|
1073
|
+
if (!adapter)
|
|
1074
|
+
throw new Error(`unknown lane ${kind} (expected one of ${LANE_KINDS2.join(", ")})`);
|
|
1075
|
+
return adapter;
|
|
1846
1076
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
filePath;
|
|
1850
|
-
constructor(filePath) {
|
|
1851
|
-
this.filePath = filePath;
|
|
1852
|
-
}
|
|
1853
|
-
static open(dataDir) {
|
|
1854
|
-
mkdirSync2(join3(dataDir, SESSIONS_DIR_NAME), { recursive: true });
|
|
1855
|
-
const canonical = join3(dataDir, SESSIONS_DIR_NAME, WAL_FILE_NAME);
|
|
1856
|
-
const legacy = join3(dataDir, WAL_FILE_NAME);
|
|
1857
|
-
if (existsSync(legacy) && !existsSync(canonical)) {
|
|
1858
|
-
try {
|
|
1859
|
-
renameSync(legacy, canonical);
|
|
1860
|
-
} catch {
|
|
1861
|
-
return new SessionWAL(legacy);
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
return new SessionWAL(canonical);
|
|
1865
|
-
}
|
|
1866
|
-
static openAt(filePath) {
|
|
1867
|
-
return new SessionWAL(filePath);
|
|
1868
|
-
}
|
|
1869
|
-
append(op) {
|
|
1870
|
-
const at = op.at ?? new Date().toISOString();
|
|
1871
|
-
const full = { ...op, at };
|
|
1872
|
-
const nextSeq = this.nextSeq();
|
|
1873
|
-
const entry = { seq: nextSeq, at, checksum: checksumOf(full), op: full };
|
|
1874
|
-
appendFileSync(this.filePath, JSON.stringify(entry) + `
|
|
1875
|
-
`, "utf8");
|
|
1876
|
-
return entry;
|
|
1877
|
-
}
|
|
1878
|
-
nextSeq() {
|
|
1879
|
-
const replay = this.replay();
|
|
1880
|
-
return replay.entries.length + 1;
|
|
1881
|
-
}
|
|
1882
|
-
replay() {
|
|
1883
|
-
const entries = [];
|
|
1884
|
-
let torn = false;
|
|
1885
|
-
let repaired = false;
|
|
1886
|
-
let content;
|
|
1887
|
-
try {
|
|
1888
|
-
content = readFileSync2(this.filePath, "utf8");
|
|
1889
|
-
} catch {
|
|
1890
|
-
return { entries, torn, repaired, liveClaims: () => new Map };
|
|
1891
|
-
}
|
|
1892
|
-
if (content.length > MAX_WAL_BYTES) {
|
|
1893
|
-
throw new Error(`session WAL exceeds ${MAX_WAL_BYTES} bytes; refusing to replay a truncated journal`);
|
|
1894
|
-
}
|
|
1895
|
-
if (content === "") {
|
|
1896
|
-
return { entries, torn, repaired, liveClaims: () => new Map };
|
|
1897
|
-
}
|
|
1898
|
-
const lines = content.split(`
|
|
1899
|
-
`);
|
|
1900
|
-
let badOffset = null;
|
|
1901
|
-
let expectedSeq = 1;
|
|
1902
|
-
for (let i = 0;i < lines.length; i++) {
|
|
1903
|
-
const line = lines[i];
|
|
1904
|
-
if (line === "")
|
|
1905
|
-
continue;
|
|
1906
|
-
if (badOffset !== null)
|
|
1907
|
-
break;
|
|
1908
|
-
const lineOffset = cumulativeOffset(lines, i);
|
|
1909
|
-
let entry;
|
|
1910
|
-
try {
|
|
1911
|
-
entry = JSON.parse(line);
|
|
1912
|
-
} catch {
|
|
1913
|
-
badOffset = lineOffset;
|
|
1914
|
-
break;
|
|
1915
|
-
}
|
|
1916
|
-
if (typeof entry?.seq !== "number" || entry.seq !== expectedSeq || entry.checksum !== checksumOf(entry.op)) {
|
|
1917
|
-
badOffset = lineOffset;
|
|
1918
|
-
break;
|
|
1919
|
-
}
|
|
1920
|
-
entries.push(entry);
|
|
1921
|
-
expectedSeq++;
|
|
1922
|
-
}
|
|
1923
|
-
if (badOffset !== null) {
|
|
1924
|
-
torn = true;
|
|
1925
|
-
try {
|
|
1926
|
-
truncateSync(this.filePath, badOffset);
|
|
1927
|
-
repaired = true;
|
|
1928
|
-
} catch {}
|
|
1929
|
-
}
|
|
1930
|
-
return {
|
|
1931
|
-
entries,
|
|
1932
|
-
torn,
|
|
1933
|
-
repaired,
|
|
1934
|
-
liveClaims: (nowMs) => reconstructLiveClaims(entries, nowMs)
|
|
1935
|
-
};
|
|
1936
|
-
}
|
|
1937
|
-
close() {}
|
|
1077
|
+
async function runLaneJob(job) {
|
|
1078
|
+
return resolveLane(job.lane).run(job);
|
|
1938
1079
|
}
|
|
1939
|
-
function
|
|
1940
|
-
|
|
1941
|
-
for (let i = 0;i < index; i++) {
|
|
1942
|
-
offset += Buffer.byteLength(lines[i]) + 1;
|
|
1943
|
-
}
|
|
1944
|
-
return offset;
|
|
1080
|
+
async function probeLane(kind) {
|
|
1081
|
+
return resolveLane(kind).probe();
|
|
1945
1082
|
}
|
|
1946
|
-
function
|
|
1947
|
-
|
|
1948
|
-
for (const entry of entries) {
|
|
1949
|
-
const op = entry.op;
|
|
1950
|
-
if (op.op === "claim_acquired") {
|
|
1951
|
-
claims.set(op.runId, { worker: op.worker, fencing: op.fencing, expiresAtMs: Date.parse(op.expiresAt) });
|
|
1952
|
-
} else if (op.op === "claim_released") {
|
|
1953
|
-
const existing = claims.get(op.runId);
|
|
1954
|
-
if (existing && existing.fencing === op.fencing) {
|
|
1955
|
-
claims.delete(op.runId);
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
for (const [runId, claim] of claims) {
|
|
1960
|
-
if (claim.expiresAtMs <= nowMs)
|
|
1961
|
-
claims.delete(runId);
|
|
1962
|
-
}
|
|
1963
|
-
return claims;
|
|
1083
|
+
async function laneInventory() {
|
|
1084
|
+
return Promise.all(LANE_KINDS2.map((kind) => probeLane(kind)));
|
|
1964
1085
|
}
|
|
1086
|
+
var adapters;
|
|
1087
|
+
var init_lanes = __esm(() => {
|
|
1088
|
+
init_claude();
|
|
1089
|
+
init_codex();
|
|
1090
|
+
init_cursor();
|
|
1091
|
+
init_grok();
|
|
1092
|
+
init_types();
|
|
1093
|
+
init_types();
|
|
1094
|
+
init_claude();
|
|
1095
|
+
init_codex();
|
|
1096
|
+
init_cursor();
|
|
1097
|
+
init_grok();
|
|
1098
|
+
adapters = new Map([
|
|
1099
|
+
["claude", createClaudeAdapter()],
|
|
1100
|
+
["codex", createCodexAdapter()],
|
|
1101
|
+
["cursor", createCursorAdapter()],
|
|
1102
|
+
["grok", createGrokAdapter()]
|
|
1103
|
+
]);
|
|
1104
|
+
});
|
|
1965
1105
|
|
|
1966
1106
|
// src/agent-tools.ts
|
|
1107
|
+
init_graph();
|
|
1108
|
+
init_lanes();
|
|
1109
|
+
|
|
1110
|
+
// src/tool-definitions.ts
|
|
1967
1111
|
function textResult(payload) {
|
|
1968
1112
|
return { content: [{ type: "text", text: JSON.stringify(payload) }], isError: false };
|
|
1969
1113
|
}
|
|
1970
1114
|
function workflowsTools(_service) {
|
|
1971
|
-
|
|
1115
|
+
const tools = [
|
|
1972
1116
|
{
|
|
1973
1117
|
name: "workflows_version",
|
|
1974
1118
|
description: "Report the installed @hasna/workflows version.",
|
|
@@ -1981,7 +1125,7 @@ function workflowsTools(_service) {
|
|
|
1981
1125
|
},
|
|
1982
1126
|
{
|
|
1983
1127
|
name: "workflows_ready",
|
|
1984
|
-
description: "Report the workflows service readiness
|
|
1128
|
+
description: "Report the hosted workflows service readiness.",
|
|
1985
1129
|
inputSchema: { type: "object", properties: {} }
|
|
1986
1130
|
},
|
|
1987
1131
|
{
|
|
@@ -2000,14 +1144,16 @@ function workflowsTools(_service) {
|
|
|
2000
1144
|
type: "object",
|
|
2001
1145
|
properties: {
|
|
2002
1146
|
graph: { type: "object", description: "the workflow graph" },
|
|
2003
|
-
context: { type: "object", description: "optional run context" }
|
|
1147
|
+
context: { type: "object", description: "optional run context" },
|
|
1148
|
+
idempotencyKey: { type: "string", description: "stable request key" },
|
|
1149
|
+
maxCycles: { type: "integer", minimum: 1, maximum: 2000 }
|
|
2004
1150
|
},
|
|
2005
|
-
required: ["graph"]
|
|
1151
|
+
required: ["graph", "idempotencyKey"]
|
|
2006
1152
|
}
|
|
2007
1153
|
},
|
|
2008
1154
|
{
|
|
2009
1155
|
name: "workflows_runs_list",
|
|
2010
|
-
description: "List recent runs from the
|
|
1156
|
+
description: "List recent runs from the configured hosted service.",
|
|
2011
1157
|
inputSchema: {
|
|
2012
1158
|
type: "object",
|
|
2013
1159
|
properties: {
|
|
@@ -2022,7 +1168,13 @@ function workflowsTools(_service) {
|
|
|
2022
1168
|
inputSchema: { type: "object", properties: {} }
|
|
2023
1169
|
}
|
|
2024
1170
|
];
|
|
1171
|
+
return tools.map((tool) => ({ ...tool, inputSchema: { ...tool.inputSchema, properties: {
|
|
1172
|
+
...tool.inputSchema.properties,
|
|
1173
|
+
agent: { type: "string", description: "calling agent identity" }
|
|
1174
|
+
} } }));
|
|
2025
1175
|
}
|
|
1176
|
+
|
|
1177
|
+
// src/agent-tools.ts
|
|
2026
1178
|
async function callWorkflowTool(service, name, args) {
|
|
2027
1179
|
switch (name) {
|
|
2028
1180
|
case "workflows_version":
|
|
@@ -2030,42 +1182,26 @@ async function callWorkflowTool(service, name, args) {
|
|
|
2030
1182
|
case "workflows_health":
|
|
2031
1183
|
return textResult(service.health());
|
|
2032
1184
|
case "workflows_ready":
|
|
2033
|
-
return textResult(service.ready());
|
|
1185
|
+
return textResult(await service.ready());
|
|
2034
1186
|
case "workflows_validate": {
|
|
2035
1187
|
const graph = args.graph;
|
|
2036
1188
|
const result = validateGraph(graph);
|
|
2037
1189
|
return textResult(result);
|
|
2038
1190
|
}
|
|
2039
|
-
case "workflows_run":
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
result: final.resultJson ? JSON.parse(final.resultJson) : null
|
|
2050
|
-
});
|
|
2051
|
-
} finally {
|
|
2052
|
-
store.close();
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
case "workflows_runs_list": {
|
|
2056
|
-
const store = openStore(service.config.dataDir);
|
|
2057
|
-
try {
|
|
2058
|
-
const status = typeof args.status === "string" ? args.status : undefined;
|
|
2059
|
-
const limit = typeof args.limit === "number" ? args.limit : 100;
|
|
2060
|
-
return textResult(store.listRuns({ status, limit }));
|
|
2061
|
-
} finally {
|
|
2062
|
-
store.close();
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
1191
|
+
case "workflows_run":
|
|
1192
|
+
return textResult(await service.triggerRun(args.graph, args.context ?? {}, {
|
|
1193
|
+
idempotencyKey: typeof args.idempotencyKey === "string" ? args.idempotencyKey : undefined,
|
|
1194
|
+
maxCycles: typeof args.maxCycles === "number" ? args.maxCycles : undefined
|
|
1195
|
+
}));
|
|
1196
|
+
case "workflows_runs_list":
|
|
1197
|
+
return textResult(await service.listRuns({
|
|
1198
|
+
status: typeof args.status === "string" ? args.status : undefined,
|
|
1199
|
+
limit: typeof args.limit === "number" ? args.limit : 100
|
|
1200
|
+
}));
|
|
2065
1201
|
case "workflows_lanes_list":
|
|
2066
1202
|
return textResult(await laneInventory());
|
|
2067
1203
|
default:
|
|
2068
|
-
throw new Error(
|
|
1204
|
+
throw new Error("Unknown workflows tool");
|
|
2069
1205
|
}
|
|
2070
1206
|
}
|
|
2071
1207
|
export {
|