@hasna/workflows 0.2.0 → 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 +28 -0
- package/dist/agent-tools.js +995 -954
- package/dist/cli/index.js +23 -12
- package/dist/handlers.js +830 -778
- package/dist/index.js +831 -778
- package/dist/mcp/index.js +15 -4
- package/dist/sdk.js +4 -4
- package/dist/serve/admin.js +56 -13
- package/dist/serve/index.js +56 -13
- package/dist/service.js +828 -777
- package/package.json +3 -3
- package/types/graph.d.ts +6 -0
- package/types/lanes/codex.d.ts +0 -6
- package/types/lanes/types.d.ts +4 -0
- package/types/sdk/generated-api.d.ts +2 -0
package/dist/agent-tools.js
CHANGED
|
@@ -16,1054 +16,1095 @@ 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/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
function subscriptionEnvironment(overrides = {}) {
|
|
33
|
-
const paid = /^(OPENAI_API_KEY|ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|CODEX_API_KEY|CURSOR_API_KEY)$/;
|
|
34
|
-
const sensitive = /(?:API[_-]?KEY|SECRET|PASSWORD|TOKEN|DATABASE_URL|CREDENTIAL)/i;
|
|
35
|
-
const env = {};
|
|
36
|
-
for (const [name, value] of Object.entries(process.env)) {
|
|
37
|
-
if (value !== undefined && !paid.test(name) && (!sensitive.test(name) || name === "CLAUDE_CODE_OAUTH_TOKEN"))
|
|
38
|
-
env[name] = value;
|
|
39
|
-
}
|
|
40
|
-
for (const [name, value] of Object.entries(overrides)) {
|
|
41
|
-
if (paid.test(name) || sensitive.test(name))
|
|
42
|
-
throw new Error(`lane environment field ${name} requires a supported scoped credential consumer`);
|
|
43
|
-
env[name] = value;
|
|
44
|
-
}
|
|
45
|
-
return env;
|
|
46
|
-
}
|
|
47
|
-
function subscriptionSdkEnvironment(overrides = {}) {
|
|
48
|
-
const safe = subscriptionEnvironment(overrides);
|
|
49
|
-
return { ...Object.fromEntries(Object.keys(process.env).map((name) => [name, undefined])), ...safe };
|
|
50
|
-
}
|
|
51
|
-
function withTimeout(promise, timeoutMs, what, cancel) {
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
const timer = setTimeout(() => {
|
|
54
|
-
cancel?.();
|
|
55
|
-
reject(new LaneTimeoutError(what, timeoutMs));
|
|
56
|
-
}, timeoutMs);
|
|
57
|
-
promise.then((value) => {
|
|
58
|
-
clearTimeout(timer);
|
|
59
|
-
resolve(value);
|
|
60
|
-
}, (err) => {
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
reject(err);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
async function spawnCli(cmd, opts) {
|
|
67
|
-
const started = Date.now();
|
|
68
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
69
|
-
let proc;
|
|
70
|
-
try {
|
|
71
|
-
proc = Bun.spawn({
|
|
72
|
-
cmd,
|
|
73
|
-
cwd: opts.cwd,
|
|
74
|
-
env: subscriptionEnvironment(opts.env),
|
|
75
|
-
stdin: "ignore",
|
|
76
|
-
stdout: "pipe",
|
|
77
|
-
stderr: "pipe",
|
|
78
|
-
timeout: timeoutMs
|
|
79
|
-
});
|
|
80
|
-
} catch (err) {
|
|
81
|
-
const message = String(err instanceof Error ? err.message : err);
|
|
82
|
-
if (message.includes("ENOENT") || /not found/i.test(message)) {
|
|
83
|
-
throw new LaneDependencyMissingError(`lane binary ${JSON.stringify(opts.binaryName)} not found on PATH; install it or provide ${opts.packageHint}`);
|
|
84
|
-
}
|
|
85
|
-
return { ok: false, exitCode: 1, output: "", durationMs: Date.now() - started, error: message };
|
|
86
|
-
}
|
|
87
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
88
|
-
new Response(proc.stdout).text(),
|
|
89
|
-
new Response(proc.stderr).text(),
|
|
90
|
-
proc.exited
|
|
91
|
-
]);
|
|
92
|
-
const output = stderr ? `${stdout}
|
|
93
|
-
${stderr}`.trim() : stdout.trim();
|
|
94
|
-
return { ok: exitCode === 0, exitCode, output, durationMs: Date.now() - started };
|
|
95
|
-
}
|
|
96
|
-
var LANE_KINDS2, LaneDependencyMissingError, LaneAdapterShapeError, DEFAULT_LANE_TIMEOUT_MS = 120000, LaneTimeoutError;
|
|
97
|
-
var init_types = __esm(() => {
|
|
98
|
-
LANE_KINDS2 = ["claude", "codex", "cursor", "grok"];
|
|
99
|
-
LaneDependencyMissingError = class LaneDependencyMissingError extends Error {
|
|
100
|
-
constructor(message) {
|
|
101
|
-
super(message);
|
|
102
|
-
this.name = "LaneDependencyMissingError";
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
LaneAdapterShapeError = class LaneAdapterShapeError extends Error {
|
|
106
|
-
constructor(message) {
|
|
107
|
-
super(message);
|
|
108
|
-
this.name = "LaneAdapterShapeError";
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
LaneTimeoutError = class LaneTimeoutError extends Error {
|
|
112
|
-
constructor(what, timeoutMs) {
|
|
113
|
-
super(`${what} exceeded the ${timeoutMs}ms budget`);
|
|
114
|
-
this.name = "LaneTimeoutError";
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// src/lanes/claude.ts
|
|
120
|
-
function createClaudeAdapter(deps = {}) {
|
|
121
|
-
return {
|
|
122
|
-
kind: "claude",
|
|
123
|
-
async probe() {
|
|
124
|
-
const loader = deps.sdkLoader ?? (async () => await import("@anthropic-ai/claude-agent-sdk"));
|
|
125
|
-
try {
|
|
126
|
-
await loader();
|
|
127
|
-
return { kind: "claude", sdk: "@anthropic-ai/claude-agent-sdk", wired: true, via: "sdk" };
|
|
128
|
-
} catch {
|
|
129
|
-
if (binaryOnPath(deps.cliPath ?? "claude")) {
|
|
130
|
-
return { kind: "claude", sdk: "@anthropic-ai/claude-agent-sdk", wired: true, via: "cli" };
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
kind: "claude",
|
|
134
|
-
sdk: "@anthropic-ai/claude-agent-sdk",
|
|
135
|
-
wired: false,
|
|
136
|
-
reason: "neither @anthropic-ai/claude-agent-sdk nor the claude CLI is available on this machine"
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
async run(job) {
|
|
141
|
-
if (!job.prompt) {
|
|
142
|
-
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "claude lane requires a prompt" };
|
|
143
|
-
}
|
|
144
|
-
const started = Date.now();
|
|
145
|
-
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
146
|
-
const loader = deps.sdkLoader ?? (async () => await import("@anthropic-ai/claude-agent-sdk"));
|
|
147
|
-
let sdk;
|
|
148
|
-
try {
|
|
149
|
-
sdk = await loader();
|
|
150
|
-
} catch (err) {
|
|
151
|
-
return runClaudeCli(job, deps.cliPath ?? "claude", started, timeoutMs);
|
|
152
|
-
}
|
|
153
|
-
const query = sdk.query;
|
|
154
|
-
if (typeof query !== "function") {
|
|
155
|
-
throw new LaneAdapterShapeError("@anthropic-ai/claude-agent-sdk exports no query() function");
|
|
156
|
-
}
|
|
157
|
-
const prompt = job.prompt;
|
|
158
|
-
const abortController = new AbortController;
|
|
159
|
-
try {
|
|
160
|
-
const { output, isError } = await withTimeout((async () => {
|
|
161
|
-
const generator = await query({
|
|
162
|
-
prompt,
|
|
163
|
-
options: { model: job.model, cwd: job.cwd, maxTurns: 32, abortController, env: subscriptionSdkEnvironment(job.env) }
|
|
164
|
-
});
|
|
165
|
-
let output2 = "";
|
|
166
|
-
let isError2 = false;
|
|
167
|
-
for await (const message of generator) {
|
|
168
|
-
if (message.type === "assistant") {
|
|
169
|
-
const content = message.message?.content;
|
|
170
|
-
if (Array.isArray(content)) {
|
|
171
|
-
for (const block of content) {
|
|
172
|
-
if (block && typeof block === "object" && "text" in block && typeof block.text === "string") {
|
|
173
|
-
output2 += block.text;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (message.type === "error")
|
|
179
|
-
isError2 = true;
|
|
180
|
-
}
|
|
181
|
-
return { output: output2, isError: isError2 };
|
|
182
|
-
})(), timeoutMs, "claude lane", () => abortController.abort());
|
|
183
|
-
const durationMs = Date.now() - started;
|
|
184
|
-
return { ok: !isError, exitCode: isError ? 1 : 0, output: output.trim(), durationMs };
|
|
185
|
-
} catch (err) {
|
|
186
|
-
const durationMs = Date.now() - started;
|
|
187
|
-
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err), retryable: !(err instanceof LaneTimeoutError) };
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
async function runClaudeCli(job, cliPath, started, timeoutMs) {
|
|
193
|
-
try {
|
|
194
|
-
const result = await spawnCli([cliPath, "-p", job.prompt], {
|
|
195
|
-
binaryName: cliPath,
|
|
196
|
-
packageHint: "@anthropic-ai/claude-agent-sdk or the claude CLI",
|
|
197
|
-
timeoutMs,
|
|
198
|
-
env: job.env,
|
|
199
|
-
cwd: job.cwd
|
|
200
|
-
});
|
|
201
|
-
return { ...result, durationMs: Date.now() - started };
|
|
202
|
-
} catch (err) {
|
|
203
|
-
if (err instanceof LaneDependencyMissingError) {
|
|
204
|
-
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
19
|
+
// src/expr.ts
|
|
20
|
+
function tokenize(input) {
|
|
21
|
+
const tokens = [];
|
|
22
|
+
let i = 0;
|
|
23
|
+
while (i < input.length) {
|
|
24
|
+
const ch = input[i];
|
|
25
|
+
if (ch === " " || ch === "\t" || ch === `
|
|
26
|
+
` || ch === "\r") {
|
|
27
|
+
i++;
|
|
28
|
+
continue;
|
|
205
29
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
init_types();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// src/lanes/codex.ts
|
|
214
|
-
function createCodexAdapter(deps = {}) {
|
|
215
|
-
return {
|
|
216
|
-
kind: "codex",
|
|
217
|
-
async probe() {
|
|
218
|
-
const loader = deps.sdkLoader ?? (async () => await import("@openai/codex-sdk"));
|
|
219
|
-
try {
|
|
220
|
-
await loader();
|
|
221
|
-
return { kind: "codex", sdk: "@openai/codex-sdk", wired: true, via: "sdk" };
|
|
222
|
-
} catch {
|
|
223
|
-
if (binaryOnPath(deps.cliPath ?? "codex")) {
|
|
224
|
-
return { kind: "codex", sdk: "@openai/codex-sdk", wired: true, via: "cli" };
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
kind: "codex",
|
|
228
|
-
sdk: "@openai/codex-sdk",
|
|
229
|
-
wired: false,
|
|
230
|
-
reason: "neither @openai/codex-sdk nor the codex CLI is available on this machine"
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
},
|
|
234
|
-
async run(job) {
|
|
235
|
-
if (!job.prompt) {
|
|
236
|
-
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "codex lane requires a prompt" };
|
|
237
|
-
}
|
|
238
|
-
const started = Date.now();
|
|
239
|
-
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
240
|
-
const loader = deps.sdkLoader ?? (async () => await import("@openai/codex-sdk"));
|
|
241
|
-
let sdk;
|
|
242
|
-
try {
|
|
243
|
-
sdk = await loader();
|
|
244
|
-
} catch {
|
|
245
|
-
return runCodexCli(job, deps.cliPath ?? "codex", started, timeoutMs);
|
|
246
|
-
}
|
|
247
|
-
const CodexClass = sdk.Codex;
|
|
248
|
-
if (typeof CodexClass !== "function") {
|
|
249
|
-
throw new LaneAdapterShapeError("@openai/codex-sdk exports no Codex class");
|
|
250
|
-
}
|
|
251
|
-
const abortController = new AbortController;
|
|
252
|
-
try {
|
|
253
|
-
const client = await withTimeout(Promise.resolve(new CodexClass({
|
|
254
|
-
config: { model: job.model },
|
|
255
|
-
env: subscriptionEnvironment(job.env)
|
|
256
|
-
})), timeoutMs, "codex lane");
|
|
257
|
-
const thread = client.startThread?.({ model: job.model, workingDirectory: job.cwd });
|
|
258
|
-
if (!thread || typeof thread.run !== "function") {
|
|
259
|
-
throw new LaneAdapterShapeError("@openai/codex-sdk Codex.startThread() returned no run()");
|
|
260
|
-
}
|
|
261
|
-
const turn = await withTimeout(thread.run(job.prompt, { signal: abortController.signal }), timeoutMs, "codex lane", () => abortController.abort());
|
|
262
|
-
const output = turn?.finalResponse ?? "";
|
|
263
|
-
const durationMs = Date.now() - started;
|
|
264
|
-
return { ok: true, exitCode: 0, output, durationMs };
|
|
265
|
-
} catch (err) {
|
|
266
|
-
const durationMs = Date.now() - started;
|
|
267
|
-
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err), retryable: !(err instanceof LaneTimeoutError) };
|
|
268
|
-
}
|
|
30
|
+
if (ch === "(") {
|
|
31
|
+
tokens.push({ kind: "lparen", value: "(" });
|
|
32
|
+
i++;
|
|
33
|
+
continue;
|
|
269
34
|
}
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const result = await spawnCli([cliPath, "exec", "--", job.prompt], {
|
|
275
|
-
binaryName: cliPath,
|
|
276
|
-
packageHint: "@openai/codex-sdk or the codex CLI",
|
|
277
|
-
timeoutMs,
|
|
278
|
-
env: job.env,
|
|
279
|
-
cwd: job.cwd
|
|
280
|
-
});
|
|
281
|
-
return { ...result, durationMs: Date.now() - started };
|
|
282
|
-
} catch (err) {
|
|
283
|
-
if (err instanceof LaneDependencyMissingError) {
|
|
284
|
-
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
35
|
+
if (ch === ")") {
|
|
36
|
+
tokens.push({ kind: "rparen", value: ")" });
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
285
39
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
kind: "cursor",
|
|
297
|
-
async probe() {
|
|
298
|
-
const loader = deps.sdkLoader ?? (async () => await import("@cursor/sdk"));
|
|
299
|
-
try {
|
|
300
|
-
await loader();
|
|
301
|
-
return { kind: "cursor", sdk: "@cursor/sdk", wired: true, via: "sdk (local mode)" };
|
|
302
|
-
} catch {
|
|
303
|
-
if (binaryOnPath(deps.cliPath ?? "cursor-agent")) {
|
|
304
|
-
return { kind: "cursor", sdk: "@cursor/sdk", wired: true, via: "cli" };
|
|
40
|
+
if (ch === '"' || ch === "'") {
|
|
41
|
+
const quote = ch;
|
|
42
|
+
let j = i + 1;
|
|
43
|
+
let out = "";
|
|
44
|
+
let closed = false;
|
|
45
|
+
while (j < input.length) {
|
|
46
|
+
if (input[j] === quote) {
|
|
47
|
+
closed = true;
|
|
48
|
+
break;
|
|
305
49
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
sdk: "@cursor/sdk",
|
|
309
|
-
wired: false,
|
|
310
|
-
reason: "neither @cursor/sdk nor the cursor-agent CLI is available on this machine"
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
async run(job) {
|
|
315
|
-
if (!job.prompt) {
|
|
316
|
-
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "cursor lane requires a prompt" };
|
|
317
|
-
}
|
|
318
|
-
if (!deps.sdkLoader && binaryOnPath(deps.cliPath ?? "cursor-agent"))
|
|
319
|
-
return runCursorCli(job, deps.cliPath ?? "cursor-agent", Date.now(), job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS);
|
|
320
|
-
const started = Date.now();
|
|
321
|
-
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
322
|
-
const loader = deps.sdkLoader ?? (async () => await import("@cursor/sdk"));
|
|
323
|
-
let sdk;
|
|
324
|
-
try {
|
|
325
|
-
sdk = await loader();
|
|
326
|
-
} catch {
|
|
327
|
-
return runCursorCli(job, deps.cliPath ?? "cursor-agent", started, timeoutMs);
|
|
328
|
-
}
|
|
329
|
-
let cancel;
|
|
330
|
-
try {
|
|
331
|
-
subscriptionEnvironment(job.env);
|
|
332
|
-
if (!deps.sdkLoader && process.env.CURSOR_API_KEY)
|
|
333
|
-
throw new Error("cursor lane refuses ambient API billing; use the native subscription CLI");
|
|
334
|
-
const run = await withTimeout(runLocalAgent(sdk, job, (callback) => {
|
|
335
|
-
cancel = callback;
|
|
336
|
-
}), timeoutMs, "cursor lane", () => cancel?.());
|
|
337
|
-
const durationMs = Date.now() - started;
|
|
338
|
-
const status = run.status;
|
|
339
|
-
const ok = status === "finished";
|
|
340
|
-
return { ok, exitCode: ok ? 0 : 1, output: run.result, durationMs };
|
|
341
|
-
} catch (err) {
|
|
342
|
-
if (err instanceof LaneAdapterShapeError)
|
|
343
|
-
throw err;
|
|
344
|
-
const durationMs = Date.now() - started;
|
|
345
|
-
return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err), retryable: !(err instanceof LaneTimeoutError) };
|
|
50
|
+
out += input[j];
|
|
51
|
+
j++;
|
|
346
52
|
}
|
|
53
|
+
if (!closed)
|
|
54
|
+
throw new ExprSyntaxError(`unterminated string literal at offset ${i}`);
|
|
55
|
+
tokens.push({ kind: "string", value: out });
|
|
56
|
+
i = j + 1;
|
|
57
|
+
continue;
|
|
347
58
|
}
|
|
348
|
-
|
|
59
|
+
const two = input.slice(i, i + 2);
|
|
60
|
+
if (COMPARISON_OPS.has(two)) {
|
|
61
|
+
tokens.push({ kind: "op", value: two });
|
|
62
|
+
i += 2;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (ch === "<" || ch === ">" || ch === "=" || ch === "!") {
|
|
66
|
+
tokens.push({ kind: "op", value: ch });
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (/[0-9]/.test(ch)) {
|
|
71
|
+
let j = i;
|
|
72
|
+
while (j < input.length && /[0-9.]/.test(input[j]))
|
|
73
|
+
j++;
|
|
74
|
+
tokens.push({ kind: "number", value: input.slice(i, j) });
|
|
75
|
+
i = j;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
79
|
+
let j = i;
|
|
80
|
+
while (j < input.length && /[A-Za-z0-9_.]/.test(input[j]))
|
|
81
|
+
j++;
|
|
82
|
+
tokens.push({ kind: "ident", value: input.slice(i, j) });
|
|
83
|
+
i = j;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
throw new ExprSyntaxError(`unexpected character ${JSON.stringify(ch)} at offset ${i}`);
|
|
87
|
+
}
|
|
88
|
+
return tokens;
|
|
349
89
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
90
|
+
|
|
91
|
+
class Parser {
|
|
92
|
+
tokens;
|
|
93
|
+
pos = 0;
|
|
94
|
+
constructor(tokens) {
|
|
95
|
+
this.tokens = tokens;
|
|
355
96
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (!agent || typeof agent.send !== "function") {
|
|
359
|
-
throw new LaneAdapterShapeError("@cursor/sdk Agent has no send()");
|
|
97
|
+
peek() {
|
|
98
|
+
return this.tokens[this.pos];
|
|
360
99
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const handle = run;
|
|
364
|
-
if (typeof handle.cancel === "function")
|
|
365
|
-
onCancel(() => {
|
|
366
|
-
handle.cancel().catch(() => {});
|
|
367
|
-
});
|
|
368
|
-
if (typeof handle.wait === "function")
|
|
369
|
-
run = await handle.wait();
|
|
100
|
+
next() {
|
|
101
|
+
return this.tokens[this.pos++];
|
|
370
102
|
}
|
|
371
|
-
|
|
372
|
-
|
|
103
|
+
expectOp(value) {
|
|
104
|
+
const t = this.next();
|
|
105
|
+
if (!t || t.kind !== "op" || t.value !== value) {
|
|
106
|
+
throw new ExprSyntaxError(`expected ${JSON.stringify(value)}`);
|
|
107
|
+
}
|
|
373
108
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
109
|
+
parse() {
|
|
110
|
+
if (this.tokens.length === 0)
|
|
111
|
+
throw new ExprSyntaxError("empty expression");
|
|
112
|
+
const node = this.parseOr();
|
|
113
|
+
if (this.pos < this.tokens.length) {
|
|
114
|
+
throw new ExprSyntaxError(`unexpected trailing token ${JSON.stringify(this.peek().value)}`);
|
|
115
|
+
}
|
|
116
|
+
return node;
|
|
377
117
|
}
|
|
378
|
-
|
|
379
|
-
|
|
118
|
+
parseOr() {
|
|
119
|
+
let left = this.parseAnd();
|
|
120
|
+
while (this.peek()?.kind === "ident" && this.peek().value === "or") {
|
|
121
|
+
this.next();
|
|
122
|
+
const right = this.parseAnd();
|
|
123
|
+
left = { kind: "or", left, right };
|
|
124
|
+
}
|
|
125
|
+
return left;
|
|
380
126
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
packageHint: "@cursor/sdk (local mode) or the cursor-agent CLI",
|
|
388
|
-
timeoutMs,
|
|
389
|
-
env: job.env,
|
|
390
|
-
cwd: job.cwd
|
|
391
|
-
});
|
|
392
|
-
return { ...result, durationMs: Date.now() - started };
|
|
393
|
-
} catch (err) {
|
|
394
|
-
if (err instanceof LaneDependencyMissingError) {
|
|
395
|
-
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
127
|
+
parseAnd() {
|
|
128
|
+
let left = this.parseNot();
|
|
129
|
+
while (this.peek()?.kind === "ident" && this.peek().value === "and") {
|
|
130
|
+
this.next();
|
|
131
|
+
const right = this.parseNot();
|
|
132
|
+
left = { kind: "and", left, right };
|
|
396
133
|
}
|
|
397
|
-
|
|
134
|
+
return left;
|
|
135
|
+
}
|
|
136
|
+
parseNot() {
|
|
137
|
+
if (this.peek()?.kind === "ident" && this.peek().value === "not") {
|
|
138
|
+
this.next();
|
|
139
|
+
return { kind: "not", operand: this.parseNot() };
|
|
140
|
+
}
|
|
141
|
+
return this.parseComparison();
|
|
142
|
+
}
|
|
143
|
+
parseComparison() {
|
|
144
|
+
const left = this.parsePrimary();
|
|
145
|
+
const t = this.peek();
|
|
146
|
+
if (t && t.kind === "op" && COMPARISON_OPS.has(t.value)) {
|
|
147
|
+
this.next();
|
|
148
|
+
const right = this.parsePrimary();
|
|
149
|
+
return { kind: "comparison", op: t.value, left, right };
|
|
150
|
+
}
|
|
151
|
+
return left;
|
|
152
|
+
}
|
|
153
|
+
parsePrimary() {
|
|
154
|
+
const t = this.next();
|
|
155
|
+
if (!t)
|
|
156
|
+
throw new ExprSyntaxError("unexpected end of expression");
|
|
157
|
+
if (t.kind === "lparen") {
|
|
158
|
+
const inner = this.parseOr();
|
|
159
|
+
this.expectCloseParen();
|
|
160
|
+
return inner;
|
|
161
|
+
}
|
|
162
|
+
if (t.kind === "number") {
|
|
163
|
+
const value = Number(t.value);
|
|
164
|
+
if (!Number.isFinite(value))
|
|
165
|
+
throw new ExprSyntaxError(`invalid number ${JSON.stringify(t.value)}`);
|
|
166
|
+
return { kind: "literal", value };
|
|
167
|
+
}
|
|
168
|
+
if (t.kind === "string")
|
|
169
|
+
return { kind: "literal", value: t.value };
|
|
170
|
+
if (t.kind === "ident") {
|
|
171
|
+
if (t.value === "true")
|
|
172
|
+
return { kind: "literal", value: true };
|
|
173
|
+
if (t.value === "false")
|
|
174
|
+
return { kind: "literal", value: false };
|
|
175
|
+
return { kind: "path", segments: t.value.split(".") };
|
|
176
|
+
}
|
|
177
|
+
throw new ExprSyntaxError(`unexpected token ${JSON.stringify(t.value)}`);
|
|
178
|
+
}
|
|
179
|
+
expectCloseParen() {
|
|
180
|
+
const t = this.next();
|
|
181
|
+
if (!t || t.kind !== "rparen")
|
|
182
|
+
throw new ExprSyntaxError("expected )");
|
|
398
183
|
}
|
|
399
184
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
185
|
+
function parseExpr(input) {
|
|
186
|
+
return new Parser(tokenize(input)).parse();
|
|
187
|
+
}
|
|
188
|
+
function resolvePath(segments, context) {
|
|
189
|
+
let current = context;
|
|
190
|
+
for (const segment of segments) {
|
|
191
|
+
if (current === null || typeof current !== "object")
|
|
192
|
+
return;
|
|
193
|
+
const record = current;
|
|
194
|
+
if (!(segment in record))
|
|
195
|
+
return;
|
|
196
|
+
current = record[segment];
|
|
197
|
+
}
|
|
198
|
+
if (typeof current === "number" || typeof current === "string" || typeof current === "boolean") {
|
|
199
|
+
return current;
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
function evalNode(node, context) {
|
|
204
|
+
switch (node.kind) {
|
|
205
|
+
case "literal":
|
|
206
|
+
return node.value;
|
|
207
|
+
case "path":
|
|
208
|
+
return resolvePath(node.segments, context);
|
|
209
|
+
case "comparison": {
|
|
210
|
+
const left = evalNode(node.left, context);
|
|
211
|
+
const right = evalNode(node.right, context);
|
|
212
|
+
if (left === undefined || right === undefined) {
|
|
213
|
+
if (node.op === "==")
|
|
214
|
+
return left === undefined && right === undefined;
|
|
215
|
+
if (node.op === "!=")
|
|
216
|
+
return left !== right;
|
|
217
|
+
throw new ExprEvalError("cannot order-compare an unknown path");
|
|
412
218
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
sdk: "no npm SDK (measured 2026-08-25)",
|
|
416
|
-
wired: false,
|
|
417
|
-
reason: `no xAI Grok SDK exists on npm (measured 2026-08-25) and the grok CLI is not on PATH`
|
|
418
|
-
};
|
|
419
|
-
},
|
|
420
|
-
async run(job) {
|
|
421
|
-
if (!job.prompt) {
|
|
422
|
-
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "grok lane requires a prompt" };
|
|
219
|
+
if (typeof left !== typeof right) {
|
|
220
|
+
throw new ExprEvalError(`cannot compare ${JSON.stringify(left)} (${typeof left}) with ${JSON.stringify(right)} (${typeof right})`);
|
|
423
221
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
222
|
+
switch (node.op) {
|
|
223
|
+
case "==":
|
|
224
|
+
return left === right;
|
|
225
|
+
case "!=":
|
|
226
|
+
return left !== right;
|
|
227
|
+
case "<":
|
|
228
|
+
return left < right;
|
|
229
|
+
case "<=":
|
|
230
|
+
return left <= right;
|
|
231
|
+
case ">":
|
|
232
|
+
return left > right;
|
|
233
|
+
case ">=":
|
|
234
|
+
return left >= right;
|
|
235
|
+
default:
|
|
236
|
+
throw new ExprEvalError(`unknown operator ${node.op}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
case "and": {
|
|
240
|
+
const left = evalNode(node.left, context);
|
|
241
|
+
const right = evalNode(node.right, context);
|
|
242
|
+
if (typeof left !== "boolean" || typeof right !== "boolean") {
|
|
243
|
+
throw new ExprEvalError("and requires boolean operands");
|
|
441
244
|
}
|
|
245
|
+
return left && right;
|
|
442
246
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
createCodexAdapter: () => createCodexAdapter,
|
|
459
|
-
createClaudeAdapter: () => createClaudeAdapter,
|
|
460
|
-
binaryOnPath: () => binaryOnPath,
|
|
461
|
-
LaneDependencyMissingError: () => LaneDependencyMissingError,
|
|
462
|
-
LaneAdapterShapeError: () => LaneAdapterShapeError,
|
|
463
|
-
LANE_KINDS: () => LANE_KINDS2
|
|
464
|
-
});
|
|
465
|
-
function resolveLane(kind) {
|
|
466
|
-
const adapter = adapters.get(kind);
|
|
467
|
-
if (!adapter)
|
|
468
|
-
throw new Error(`unknown lane ${kind} (expected one of ${LANE_KINDS2.join(", ")})`);
|
|
469
|
-
return adapter;
|
|
470
|
-
}
|
|
471
|
-
async function runLaneJob(job) {
|
|
472
|
-
return resolveLane(job.lane).run(job);
|
|
473
|
-
}
|
|
474
|
-
async function probeLane(kind) {
|
|
475
|
-
return resolveLane(kind).probe();
|
|
247
|
+
case "or": {
|
|
248
|
+
const left = evalNode(node.left, context);
|
|
249
|
+
const right = evalNode(node.right, context);
|
|
250
|
+
if (typeof left !== "boolean" || typeof right !== "boolean") {
|
|
251
|
+
throw new ExprEvalError("or requires boolean operands");
|
|
252
|
+
}
|
|
253
|
+
return left || right;
|
|
254
|
+
}
|
|
255
|
+
case "not": {
|
|
256
|
+
const operand = evalNode(node.operand, context);
|
|
257
|
+
if (typeof operand !== "boolean")
|
|
258
|
+
throw new ExprEvalError("not requires a boolean operand");
|
|
259
|
+
return !operand;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
476
262
|
}
|
|
477
|
-
|
|
478
|
-
|
|
263
|
+
function evaluateExpr(input, context) {
|
|
264
|
+
const ast = parseExpr(input);
|
|
265
|
+
const value = evalNode(ast, context);
|
|
266
|
+
if (typeof value !== "boolean") {
|
|
267
|
+
throw new ExprEvalError(`condition must evaluate to a boolean, got ${typeof value}`);
|
|
268
|
+
}
|
|
269
|
+
return value;
|
|
479
270
|
}
|
|
480
|
-
var
|
|
481
|
-
var
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
["cursor", createCursorAdapter()],
|
|
496
|
-
["grok", createGrokAdapter()]
|
|
497
|
-
]);
|
|
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(["==", "!=", "<", "<=", ">", ">="]);
|
|
498
286
|
});
|
|
499
287
|
|
|
500
|
-
// src/
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
super(message);
|
|
504
|
-
this.name = "ExprSyntaxError";
|
|
505
|
-
}
|
|
288
|
+
// src/graph.ts
|
|
289
|
+
function issue(issues, path, message) {
|
|
290
|
+
issues.push({ path, message });
|
|
506
291
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
constructor(message) {
|
|
510
|
-
super(message);
|
|
511
|
-
this.name = "ExprEvalError";
|
|
512
|
-
}
|
|
292
|
+
function findNode(graph, id) {
|
|
293
|
+
return graph.nodes.find((n) => n.id === id);
|
|
513
294
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
295
|
+
function collectEdges(graph) {
|
|
296
|
+
const edges = [];
|
|
297
|
+
for (const node of graph.nodes) {
|
|
298
|
+
switch (node.type) {
|
|
299
|
+
case "start":
|
|
300
|
+
case "step":
|
|
301
|
+
if (node.next)
|
|
302
|
+
edges.push({ from: node.id, to: node.next });
|
|
303
|
+
break;
|
|
304
|
+
case "decision":
|
|
305
|
+
if (node.then)
|
|
306
|
+
edges.push({ from: node.id, to: node.then });
|
|
307
|
+
if (node.else)
|
|
308
|
+
edges.push({ from: node.id, to: node.else });
|
|
309
|
+
break;
|
|
310
|
+
case "while":
|
|
311
|
+
if (node.next)
|
|
312
|
+
edges.push({ from: node.id, to: node.next });
|
|
313
|
+
break;
|
|
314
|
+
case "end":
|
|
315
|
+
break;
|
|
524
316
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
317
|
+
}
|
|
318
|
+
return edges;
|
|
319
|
+
}
|
|
320
|
+
function reachableNodes(graph) {
|
|
321
|
+
const start = graph.nodes.find((n) => n.type === "start");
|
|
322
|
+
if (!start)
|
|
323
|
+
return [];
|
|
324
|
+
const seen = new Set;
|
|
325
|
+
const stack = [start.id];
|
|
326
|
+
while (stack.length > 0) {
|
|
327
|
+
const id = stack.pop();
|
|
328
|
+
if (seen.has(id))
|
|
528
329
|
continue;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
i++;
|
|
330
|
+
seen.add(id);
|
|
331
|
+
const node = findNode(graph, id);
|
|
332
|
+
if (!node)
|
|
533
333
|
continue;
|
|
334
|
+
if (node.type === "start" || node.type === "step") {
|
|
335
|
+
if (node.next)
|
|
336
|
+
stack.push(node.next);
|
|
337
|
+
} else if (node.type === "decision") {
|
|
338
|
+
if (node.then)
|
|
339
|
+
stack.push(node.then);
|
|
340
|
+
if (node.else)
|
|
341
|
+
stack.push(node.else);
|
|
342
|
+
} else if (node.type === "while") {
|
|
343
|
+
for (const bodyId of node.body)
|
|
344
|
+
stack.push(bodyId);
|
|
345
|
+
if (node.next)
|
|
346
|
+
stack.push(node.next);
|
|
534
347
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
348
|
+
}
|
|
349
|
+
return [...seen];
|
|
350
|
+
}
|
|
351
|
+
function findCycles(graph) {
|
|
352
|
+
const edges = collectEdges(graph);
|
|
353
|
+
const adj = new Map;
|
|
354
|
+
for (const e of edges) {
|
|
355
|
+
const list = adj.get(e.from) ?? [];
|
|
356
|
+
list.push(e.to);
|
|
357
|
+
adj.set(e.from, list);
|
|
358
|
+
}
|
|
359
|
+
const color = new Map;
|
|
360
|
+
const stack = [];
|
|
361
|
+
const cycles = [];
|
|
362
|
+
function visit(id) {
|
|
363
|
+
color.set(id, "grey");
|
|
364
|
+
stack.push(id);
|
|
365
|
+
for (const to of adj.get(id) ?? []) {
|
|
366
|
+
const c = color.get(to);
|
|
367
|
+
if (c === "grey") {
|
|
368
|
+
const idx = stack.indexOf(to);
|
|
369
|
+
cycles.push([...stack.slice(idx), to]);
|
|
370
|
+
} else if (c === undefined) {
|
|
371
|
+
visit(to);
|
|
547
372
|
}
|
|
548
|
-
if (!closed)
|
|
549
|
-
throw new ExprSyntaxError(`unterminated string literal at offset ${i}`);
|
|
550
|
-
tokens.push({ kind: "string", value: out });
|
|
551
|
-
i = j + 1;
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
const two = input.slice(i, i + 2);
|
|
555
|
-
if (COMPARISON_OPS.has(two)) {
|
|
556
|
-
tokens.push({ kind: "op", value: two });
|
|
557
|
-
i += 2;
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
if (ch === "<" || ch === ">" || ch === "=" || ch === "!") {
|
|
561
|
-
tokens.push({ kind: "op", value: ch });
|
|
562
|
-
i++;
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
if (/[0-9]/.test(ch)) {
|
|
566
|
-
let j = i;
|
|
567
|
-
while (j < input.length && /[0-9.]/.test(input[j]))
|
|
568
|
-
j++;
|
|
569
|
-
tokens.push({ kind: "number", value: input.slice(i, j) });
|
|
570
|
-
i = j;
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
if (/[A-Za-z_]/.test(ch)) {
|
|
574
|
-
let j = i;
|
|
575
|
-
while (j < input.length && /[A-Za-z0-9_.]/.test(input[j]))
|
|
576
|
-
j++;
|
|
577
|
-
tokens.push({ kind: "ident", value: input.slice(i, j) });
|
|
578
|
-
i = j;
|
|
579
|
-
continue;
|
|
580
373
|
}
|
|
581
|
-
|
|
374
|
+
stack.pop();
|
|
375
|
+
color.set(id, "black");
|
|
582
376
|
}
|
|
583
|
-
|
|
377
|
+
for (const node of graph.nodes) {
|
|
378
|
+
if (color.get(node.id) === undefined)
|
|
379
|
+
visit(node.id);
|
|
380
|
+
}
|
|
381
|
+
return cycles;
|
|
584
382
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
constructor(tokens) {
|
|
590
|
-
this.tokens = tokens;
|
|
383
|
+
function validateGraph(graph) {
|
|
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" }] };
|
|
591
387
|
}
|
|
592
|
-
|
|
593
|
-
|
|
388
|
+
if (typeof graph.name !== "string" || graph.name.trim() === "") {
|
|
389
|
+
issue(issues, "name", "graph requires a non-empty name");
|
|
390
|
+
}
|
|
391
|
+
if (typeof graph.version !== "string" || graph.version.trim() === "") {
|
|
392
|
+
issue(issues, "version", "graph requires a version string");
|
|
393
|
+
}
|
|
394
|
+
if (graph.memoWatch !== undefined) {
|
|
395
|
+
if (!Array.isArray(graph.memoWatch) || graph.memoWatch.length === 0) {
|
|
396
|
+
issue(issues, "memoWatch", "memoWatch must be a non-empty array of file paths or globs");
|
|
397
|
+
} else {
|
|
398
|
+
for (const entry of graph.memoWatch) {
|
|
399
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
400
|
+
issue(issues, "memoWatch", "each memoWatch entry must be a non-empty path or glob string");
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
594
405
|
}
|
|
595
|
-
|
|
596
|
-
|
|
406
|
+
if (!Array.isArray(graph.nodes) || graph.nodes.length === 0) {
|
|
407
|
+
issue(issues, "nodes", "graph requires at least one node");
|
|
408
|
+
return { ok: false, issues };
|
|
597
409
|
}
|
|
598
|
-
|
|
599
|
-
const
|
|
600
|
-
if (
|
|
601
|
-
|
|
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;
|
|
602
415
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
throw new ExprSyntaxError(`unexpected trailing token ${JSON.stringify(this.peek().value)}`);
|
|
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`);
|
|
610
422
|
}
|
|
611
|
-
|
|
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");
|
|
612
425
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
426
|
+
if (issues.length)
|
|
427
|
+
return { ok: false, issues };
|
|
428
|
+
const starts = graph.nodes.filter((n) => n.type === "start");
|
|
429
|
+
if (starts.length === 0)
|
|
430
|
+
issue(issues, "nodes", "graph requires exactly one start node (found none)");
|
|
431
|
+
if (starts.length > 1)
|
|
432
|
+
issue(issues, "nodes", "graph requires exactly one start node (found multiple)");
|
|
433
|
+
const ends = graph.nodes.filter((n) => n.type === "end");
|
|
434
|
+
if (ends.length === 0)
|
|
435
|
+
issue(issues, "nodes", "graph requires at least one end node");
|
|
436
|
+
const seenIds = new Map;
|
|
437
|
+
for (const node of graph.nodes) {
|
|
438
|
+
if (typeof node.id !== "string" || !NODE_ID_RE.test(node.id)) {
|
|
439
|
+
issue(issues, "nodes", `node id ${JSON.stringify(node.id)} must match ${String(NODE_ID_RE)}`);
|
|
440
|
+
continue;
|
|
619
441
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
let left = this.parseNot();
|
|
624
|
-
while (this.peek()?.kind === "ident" && this.peek().value === "and") {
|
|
625
|
-
this.next();
|
|
626
|
-
const right = this.parseNot();
|
|
627
|
-
left = { kind: "and", left, right };
|
|
442
|
+
if (seenIds.has(node.id)) {
|
|
443
|
+
issue(issues, `nodes.${node.id}`, `duplicate node id ${node.id}`);
|
|
444
|
+
continue;
|
|
628
445
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
this.next();
|
|
634
|
-
return { kind: "not", operand: this.parseNot() };
|
|
446
|
+
seenIds.set(node.id, node);
|
|
447
|
+
if (!GRAPH_NODE_TYPES.includes(node.type)) {
|
|
448
|
+
issue(issues, `nodes.${node.id}`, `unknown node type ${JSON.stringify(node.type)}`);
|
|
449
|
+
continue;
|
|
635
450
|
}
|
|
636
|
-
|
|
451
|
+
validateNode(graph, node, issues);
|
|
637
452
|
}
|
|
638
|
-
|
|
639
|
-
const
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
return { kind: "comparison", op: t.value, left, right };
|
|
453
|
+
for (const node of graph.nodes) {
|
|
454
|
+
const edges = nodeEdges(node);
|
|
455
|
+
for (const { kind, target } of edges) {
|
|
456
|
+
if (!seenIds.has(target)) {
|
|
457
|
+
issue(issues, `nodes.${node.id}`, `${kind} edge targets nonexistent node ${target}`);
|
|
458
|
+
}
|
|
645
459
|
}
|
|
646
|
-
return left;
|
|
647
460
|
}
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
this.expectCloseParen();
|
|
655
|
-
return inner;
|
|
656
|
-
}
|
|
657
|
-
if (t.kind === "number") {
|
|
658
|
-
const value = Number(t.value);
|
|
659
|
-
if (!Number.isFinite(value))
|
|
660
|
-
throw new ExprSyntaxError(`invalid number ${JSON.stringify(t.value)}`);
|
|
661
|
-
return { kind: "literal", value };
|
|
662
|
-
}
|
|
663
|
-
if (t.kind === "string")
|
|
664
|
-
return { kind: "literal", value: t.value };
|
|
665
|
-
if (t.kind === "ident") {
|
|
666
|
-
if (t.value === "true")
|
|
667
|
-
return { kind: "literal", value: true };
|
|
668
|
-
if (t.value === "false")
|
|
669
|
-
return { kind: "literal", value: false };
|
|
670
|
-
return { kind: "path", segments: t.value.split(".") };
|
|
461
|
+
if (starts.length === 1) {
|
|
462
|
+
const reachable = new Set(reachableNodes(graph));
|
|
463
|
+
for (const node of graph.nodes) {
|
|
464
|
+
if (!reachable.has(node.id)) {
|
|
465
|
+
issue(issues, `nodes.${node.id}`, `node ${node.id} is unreachable from start`);
|
|
466
|
+
}
|
|
671
467
|
}
|
|
672
|
-
throw new ExprSyntaxError(`unexpected token ${JSON.stringify(t.value)}`);
|
|
673
468
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
throw new ExprSyntaxError("expected )");
|
|
469
|
+
const cycles = findCycles(graph);
|
|
470
|
+
for (const cycle of cycles) {
|
|
471
|
+
issue(issues, "nodes", `explicit cycle detected: ${cycle.join(" -> ")} (loop only via a while node)`);
|
|
678
472
|
}
|
|
473
|
+
return { ok: issues.length === 0, issues };
|
|
679
474
|
}
|
|
680
|
-
function
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
475
|
+
function nodeEdges(node) {
|
|
476
|
+
switch (node.type) {
|
|
477
|
+
case "start":
|
|
478
|
+
case "step":
|
|
479
|
+
return node.next ? [{ kind: "next", target: node.next }] : [];
|
|
480
|
+
case "decision":
|
|
481
|
+
return [
|
|
482
|
+
...node.then ? [{ kind: "then", target: node.then }] : [],
|
|
483
|
+
...node.else ? [{ kind: "else", target: node.else }] : []
|
|
484
|
+
];
|
|
485
|
+
case "while":
|
|
486
|
+
return node.next ? [{ kind: "next", target: node.next }] : [];
|
|
487
|
+
case "end":
|
|
488
|
+
return [];
|
|
489
|
+
default:
|
|
490
|
+
return [];
|
|
695
491
|
}
|
|
696
|
-
return;
|
|
697
492
|
}
|
|
698
|
-
function
|
|
699
|
-
switch (node.
|
|
700
|
-
case "
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
case "
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
if (left === undefined || right === undefined) {
|
|
708
|
-
if (node.op === "==")
|
|
709
|
-
return left === undefined && right === undefined;
|
|
710
|
-
if (node.op === "!=")
|
|
711
|
-
return left !== right;
|
|
712
|
-
throw new ExprEvalError("cannot order-compare an unknown path");
|
|
493
|
+
function validateNode(graph, node, issues) {
|
|
494
|
+
switch (node.type) {
|
|
495
|
+
case "start":
|
|
496
|
+
if (!node.next)
|
|
497
|
+
issue(issues, `nodes.${node.id}`, "start node requires a next edge");
|
|
498
|
+
break;
|
|
499
|
+
case "step": {
|
|
500
|
+
if (!node.prompt && !node.command) {
|
|
501
|
+
issue(issues, `nodes.${node.id}`, "step node requires prompt or command");
|
|
713
502
|
}
|
|
714
|
-
if (
|
|
715
|
-
|
|
503
|
+
if (node.lane !== undefined && !LANE_KINDS.includes(node.lane)) {
|
|
504
|
+
issue(issues, `nodes.${node.id}`, `unknown lane ${JSON.stringify(node.lane)} (expected one of ${LANE_KINDS.join(", ")})`);
|
|
716
505
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
+
}
|
|
516
|
+
if (node.maxRetries !== undefined && (!Number.isInteger(node.maxRetries) || node.maxRetries < 0)) {
|
|
517
|
+
issue(issues, `nodes.${node.id}`, "maxRetries must be a non-negative integer");
|
|
518
|
+
}
|
|
519
|
+
if (node.timeoutMs !== undefined && (!Number.isInteger(node.timeoutMs) || node.timeoutMs < 1)) {
|
|
520
|
+
issue(issues, `nodes.${node.id}`, "timeoutMs must be a positive integer");
|
|
732
521
|
}
|
|
522
|
+
break;
|
|
733
523
|
}
|
|
734
|
-
case "
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
524
|
+
case "decision": {
|
|
525
|
+
if (typeof node.condition !== "string" || node.condition.trim() === "") {
|
|
526
|
+
issue(issues, `nodes.${node.id}`, "decision node requires a condition expression");
|
|
527
|
+
} else {
|
|
528
|
+
try {
|
|
529
|
+
parseExpr(node.condition);
|
|
530
|
+
} catch (err) {
|
|
531
|
+
if (err instanceof ExprSyntaxError) {
|
|
532
|
+
issue(issues, `nodes.${node.id}.condition`, `invalid condition expression: ${err.message}`);
|
|
533
|
+
} else {
|
|
534
|
+
throw err;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (!node.then && !node.else) {
|
|
539
|
+
issue(issues, `nodes.${node.id}`, "decision node requires then and/or else edge");
|
|
540
|
+
}
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
case "while": {
|
|
544
|
+
if (typeof node.condition !== "string" || node.condition.trim() === "") {
|
|
545
|
+
issue(issues, `nodes.${node.id}`, "while node requires a condition expression");
|
|
546
|
+
} else {
|
|
547
|
+
try {
|
|
548
|
+
parseExpr(node.condition);
|
|
549
|
+
} catch (err) {
|
|
550
|
+
if (err instanceof ExprSyntaxError) {
|
|
551
|
+
issue(issues, `nodes.${node.id}.condition`, `invalid condition expression: ${err.message}`);
|
|
552
|
+
} else {
|
|
553
|
+
throw err;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (node.maxIterations === undefined) {
|
|
558
|
+
issue(issues, `nodes.${node.id}`, "while node requires maxIterations (finite loop bound)");
|
|
559
|
+
} else if (!Number.isInteger(node.maxIterations) || node.maxIterations < 1) {
|
|
560
|
+
issue(issues, `nodes.${node.id}`, "maxIterations must be a positive integer");
|
|
739
561
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
562
|
+
if (!Array.isArray(node.body) || node.body.length === 0) {
|
|
563
|
+
issue(issues, `nodes.${node.id}`, "while node requires a non-empty body");
|
|
564
|
+
} else {
|
|
565
|
+
for (const bodyId of node.body) {
|
|
566
|
+
const bodyNode = findNode(graph, bodyId);
|
|
567
|
+
if (!bodyNode) {
|
|
568
|
+
issue(issues, `nodes.${node.id}`, `while body references nonexistent node ${bodyId}`);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (bodyNode.type === "start" || bodyNode.type === "end" || bodyNode.type === "while") {
|
|
572
|
+
issue(issues, `nodes.${node.id}`, `while body may only contain step/decision nodes in v1 (found ${bodyNode.type})`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
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
|
+
}
|
|
585
|
+
}
|
|
747
586
|
}
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
case "not": {
|
|
751
|
-
const operand = evalNode(node.operand, context);
|
|
752
|
-
if (typeof operand !== "boolean")
|
|
753
|
-
throw new ExprEvalError("not requires a boolean operand");
|
|
754
|
-
return !operand;
|
|
587
|
+
break;
|
|
755
588
|
}
|
|
589
|
+
case "end":
|
|
590
|
+
break;
|
|
756
591
|
}
|
|
757
592
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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) {
|
|
607
|
+
try {
|
|
608
|
+
const stat = statSync(`${entry}/${binary}`);
|
|
609
|
+
if (stat.isFile())
|
|
610
|
+
return true;
|
|
611
|
+
} catch {}
|
|
763
612
|
}
|
|
764
|
-
return
|
|
613
|
+
return false;
|
|
765
614
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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;
|
|
773
629
|
}
|
|
774
|
-
function
|
|
775
|
-
|
|
630
|
+
function subscriptionSdkEnvironment(overrides = {}) {
|
|
631
|
+
const safe = subscriptionEnvironment(overrides);
|
|
632
|
+
return { ...Object.fromEntries(Object.keys(process.env).map((name) => [name, undefined])), ...safe };
|
|
776
633
|
}
|
|
777
|
-
function
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
+
});
|
|
647
|
+
});
|
|
648
|
+
}
|
|
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}`);
|
|
798
667
|
}
|
|
668
|
+
return { ok: false, exitCode: 1, output: "", durationMs: Date.now() - started, error: message };
|
|
799
669
|
}
|
|
800
|
-
|
|
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
|
+
};
|
|
801
684
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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";
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
LaneAdapterShapeError = class LaneAdapterShapeError extends Error {
|
|
695
|
+
constructor(message) {
|
|
696
|
+
super(message);
|
|
697
|
+
this.name = "LaneAdapterShapeError";
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
LaneTimeoutError = class LaneTimeoutError extends Error {
|
|
701
|
+
constructor(what, timeoutMs) {
|
|
702
|
+
super(`${what} exceeded the ${timeoutMs}ms budget`);
|
|
703
|
+
this.name = "LaneTimeoutError";
|
|
704
|
+
}
|
|
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) };
|
|
777
|
+
}
|
|
829
778
|
}
|
|
830
|
-
}
|
|
831
|
-
return [...seen];
|
|
779
|
+
};
|
|
832
780
|
}
|
|
833
|
-
function
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
stack.push(id);
|
|
847
|
-
for (const to of adj.get(id) ?? []) {
|
|
848
|
-
const c = color.get(to);
|
|
849
|
-
if (c === "grey") {
|
|
850
|
-
const idx = stack.indexOf(to);
|
|
851
|
-
cycles.push([...stack.slice(idx), to]);
|
|
852
|
-
} else if (c === undefined) {
|
|
853
|
-
visit(to);
|
|
854
|
-
}
|
|
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 };
|
|
855
794
|
}
|
|
856
|
-
|
|
857
|
-
color.set(id, "black");
|
|
858
|
-
}
|
|
859
|
-
for (const node of graph.nodes) {
|
|
860
|
-
if (color.get(node.id) === undefined)
|
|
861
|
-
visit(node.id);
|
|
795
|
+
throw err;
|
|
862
796
|
}
|
|
863
|
-
return cycles;
|
|
864
797
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
if (typeof entry !== "string" || entry.trim() === "") {
|
|
882
|
-
issue(issues, "memoWatch", "each memoWatch entry must be a non-empty path or glob string");
|
|
883
|
-
break;
|
|
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" };
|
|
884
814
|
}
|
|
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()");
|
|
856
|
+
}
|
|
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);
|
|
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) };
|
|
885
867
|
}
|
|
886
868
|
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (node.type === "while" && (!Array.isArray(node.body) || node.body.some((id) => typeof id !== "string")))
|
|
906
|
-
issue(issues, `nodes.${index}.body`, "body must be an array of node ids");
|
|
907
|
-
}
|
|
908
|
-
if (issues.length)
|
|
909
|
-
return { ok: false, issues };
|
|
910
|
-
const starts = graph.nodes.filter((n) => n.type === "start");
|
|
911
|
-
if (starts.length === 0)
|
|
912
|
-
issue(issues, "nodes", "graph requires exactly one start node (found none)");
|
|
913
|
-
if (starts.length > 1)
|
|
914
|
-
issue(issues, "nodes", "graph requires exactly one start node (found multiple)");
|
|
915
|
-
const ends = graph.nodes.filter((n) => n.type === "end");
|
|
916
|
-
if (ends.length === 0)
|
|
917
|
-
issue(issues, "nodes", "graph requires at least one end node");
|
|
918
|
-
const seenIds = new Map;
|
|
919
|
-
for (const node of graph.nodes) {
|
|
920
|
-
if (typeof node.id !== "string" || !NODE_ID_RE.test(node.id)) {
|
|
921
|
-
issue(issues, "nodes", `node id ${JSON.stringify(node.id)} must match ${String(NODE_ID_RE)}`);
|
|
922
|
-
continue;
|
|
923
|
-
}
|
|
924
|
-
if (seenIds.has(node.id)) {
|
|
925
|
-
issue(issues, `nodes.${node.id}`, `duplicate node id ${node.id}`);
|
|
926
|
-
continue;
|
|
927
|
-
}
|
|
928
|
-
seenIds.set(node.id, node);
|
|
929
|
-
if (!GRAPH_NODE_TYPES.includes(node.type)) {
|
|
930
|
-
issue(issues, `nodes.${node.id}`, `unknown node type ${JSON.stringify(node.type)}`);
|
|
931
|
-
continue;
|
|
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 };
|
|
932
887
|
}
|
|
933
|
-
|
|
888
|
+
throw err;
|
|
934
889
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
+
};
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
async run(job) {
|
|
921
|
+
if (!job.prompt) {
|
|
922
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "cursor lane requires a prompt" };
|
|
923
|
+
}
|
|
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);
|
|
934
|
+
}
|
|
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) };
|
|
940
952
|
}
|
|
941
953
|
}
|
|
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");
|
|
942
961
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
issue(issues, `nodes.${node.id}`, `node ${node.id} is unreachable from start`);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
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()");
|
|
950
966
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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();
|
|
954
976
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
function nodeEdges(node) {
|
|
958
|
-
switch (node.type) {
|
|
959
|
-
case "start":
|
|
960
|
-
case "step":
|
|
961
|
-
return node.next ? [{ kind: "next", target: node.next }] : [];
|
|
962
|
-
case "decision":
|
|
963
|
-
return [
|
|
964
|
-
...node.then ? [{ kind: "then", target: node.then }] : [],
|
|
965
|
-
...node.else ? [{ kind: "else", target: node.else }] : []
|
|
966
|
-
];
|
|
967
|
-
case "while":
|
|
968
|
-
return node.next ? [{ kind: "next", target: node.next }] : [];
|
|
969
|
-
case "end":
|
|
970
|
-
return [];
|
|
971
|
-
default:
|
|
972
|
-
return [];
|
|
977
|
+
if (!run || typeof run !== "object") {
|
|
978
|
+
throw new LaneAdapterShapeError("@cursor/sdk Agent.send() returned no Run");
|
|
973
979
|
}
|
|
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)`);
|
|
983
|
+
}
|
|
984
|
+
if (typeof candidate.result !== "string") {
|
|
985
|
+
throw new LaneAdapterShapeError("@cursor/sdk Agent.send() returned a Run without a string result");
|
|
986
|
+
}
|
|
987
|
+
return { status: candidate.status, result: candidate.result };
|
|
974
988
|
}
|
|
975
|
-
function
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
988
|
-
if (node.maxRetries !== undefined && (!Number.isInteger(node.maxRetries) || node.maxRetries < 0)) {
|
|
989
|
-
issue(issues, `nodes.${node.id}`, "maxRetries must be a non-negative integer");
|
|
990
|
-
}
|
|
991
|
-
if (node.timeoutMs !== undefined && (!Number.isInteger(node.timeoutMs) || node.timeoutMs < 1)) {
|
|
992
|
-
issue(issues, `nodes.${node.id}`, "timeoutMs must be a positive integer");
|
|
993
|
-
}
|
|
994
|
-
break;
|
|
995
|
-
}
|
|
996
|
-
case "decision": {
|
|
997
|
-
if (typeof node.condition !== "string" || node.condition.trim() === "") {
|
|
998
|
-
issue(issues, `nodes.${node.id}`, "decision node requires a condition expression");
|
|
999
|
-
} else {
|
|
1000
|
-
try {
|
|
1001
|
-
parseExpr(node.condition);
|
|
1002
|
-
} catch (err) {
|
|
1003
|
-
if (err instanceof ExprSyntaxError) {
|
|
1004
|
-
issue(issues, `nodes.${node.id}.condition`, `invalid condition expression: ${err.message}`);
|
|
1005
|
-
} else {
|
|
1006
|
-
throw err;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
if (!node.then && !node.else) {
|
|
1011
|
-
issue(issues, `nodes.${node.id}`, "decision node requires then and/or else edge");
|
|
1012
|
-
}
|
|
1013
|
-
break;
|
|
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 };
|
|
1014
1002
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1003
|
+
throw err;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
var init_cursor = __esm(() => {
|
|
1007
|
+
init_types();
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
// src/lanes/grok.ts
|
|
1011
|
+
function createGrokAdapter(deps = {}) {
|
|
1012
|
+
return {
|
|
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" };
|
|
1028
1018
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
+
};
|
|
1025
|
+
},
|
|
1026
|
+
async run(job) {
|
|
1027
|
+
if (!job.prompt) {
|
|
1028
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "grok lane requires a prompt" };
|
|
1033
1029
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const bodyNode = findNode(graph, bodyId);
|
|
1050
|
-
if (!bodyNode)
|
|
1051
|
-
continue;
|
|
1052
|
-
for (const { kind, target } of nodeEdges(bodyNode)) {
|
|
1053
|
-
if (!bodySet.has(target)) {
|
|
1054
|
-
issue(issues, `nodes.${node.id}`, `while body node ${bodyId} ${kind}-edge leaves the body (target ${target})`);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
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 };
|
|
1057
1045
|
}
|
|
1046
|
+
throw err;
|
|
1058
1047
|
}
|
|
1059
|
-
break;
|
|
1060
1048
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
var init_grok = __esm(() => {
|
|
1052
|
+
init_types();
|
|
1053
|
+
});
|
|
1054
|
+
|
|
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;
|
|
1076
|
+
}
|
|
1077
|
+
async function runLaneJob(job) {
|
|
1078
|
+
return resolveLane(job.lane).run(job);
|
|
1079
|
+
}
|
|
1080
|
+
async function probeLane(kind) {
|
|
1081
|
+
return resolveLane(kind).probe();
|
|
1082
|
+
}
|
|
1083
|
+
async function laneInventory() {
|
|
1084
|
+
return Promise.all(LANE_KINDS2.map((kind) => probeLane(kind)));
|
|
1064
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
|
+
});
|
|
1065
1105
|
|
|
1066
1106
|
// src/agent-tools.ts
|
|
1107
|
+
init_graph();
|
|
1067
1108
|
init_lanes();
|
|
1068
1109
|
|
|
1069
1110
|
// src/tool-definitions.ts
|