@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/mcp/index.js
CHANGED
|
@@ -73,7 +73,7 @@ function workflowsTools(_service) {
|
|
|
73
73
|
// package.json
|
|
74
74
|
var package_default = {
|
|
75
75
|
name: "@hasna/workflows",
|
|
76
|
-
version: "0.2.
|
|
76
|
+
version: "0.2.1",
|
|
77
77
|
description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
78
78
|
type: "module",
|
|
79
79
|
main: "./dist/index.js",
|
|
@@ -149,9 +149,9 @@ var package_default = {
|
|
|
149
149
|
dependencies: {
|
|
150
150
|
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
151
151
|
"@cursor/sdk": "^1.0.28",
|
|
152
|
-
"@openai/codex-sdk": "0.147.0",
|
|
153
|
-
commander: "^13.1.0",
|
|
154
152
|
"@hasna/contracts": "1.3.3",
|
|
153
|
+
"@openai/codex-sdk": "0.160.0",
|
|
154
|
+
commander: "^13.1.0",
|
|
155
155
|
pg: "^8.16.3"
|
|
156
156
|
},
|
|
157
157
|
devDependencies: {
|
|
@@ -346,6 +346,7 @@ function parseExpr(input) {
|
|
|
346
346
|
// src/graph.ts
|
|
347
347
|
var GRAPH_NODE_TYPES = ["start", "step", "decision", "while", "end"];
|
|
348
348
|
var LANE_KINDS = ["claude", "codex", "cursor", "grok"];
|
|
349
|
+
var CODEX_EXECUTION_MODES = ["repository", "read-only"];
|
|
349
350
|
var NODE_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
350
351
|
function issue(issues, path, message) {
|
|
351
352
|
issues.push({ path, message });
|
|
@@ -564,6 +565,16 @@ function validateNode(graph, node, issues) {
|
|
|
564
565
|
if (node.lane !== undefined && !LANE_KINDS.includes(node.lane)) {
|
|
565
566
|
issue(issues, `nodes.${node.id}`, `unknown lane ${JSON.stringify(node.lane)} (expected one of ${LANE_KINDS.join(", ")})`);
|
|
566
567
|
}
|
|
568
|
+
if (node.cwd !== undefined && (typeof node.cwd !== "string" || !node.cwd.trim() || node.cwd.includes("\x00"))) {
|
|
569
|
+
issue(issues, `nodes.${node.id}.cwd`, "cwd must be a non-empty path string without NUL characters on the executor");
|
|
570
|
+
}
|
|
571
|
+
if (node.codexMode !== undefined) {
|
|
572
|
+
if (!CODEX_EXECUTION_MODES.includes(node.codexMode)) {
|
|
573
|
+
issue(issues, `nodes.${node.id}.codexMode`, "codexMode must be repository or read-only");
|
|
574
|
+
} else if (node.lane !== "codex" || !node.prompt || node.command !== undefined) {
|
|
575
|
+
issue(issues, `nodes.${node.id}.codexMode`, "codexMode is supported only for a codex prompt step without command");
|
|
576
|
+
}
|
|
577
|
+
}
|
|
567
578
|
if (node.maxRetries !== undefined && (!Number.isInteger(node.maxRetries) || node.maxRetries < 0)) {
|
|
568
579
|
issue(issues, `nodes.${node.id}`, "maxRetries must be a non-negative integer");
|
|
569
580
|
}
|
|
@@ -642,7 +653,7 @@ function validateNode(graph, node, issues) {
|
|
|
642
653
|
}
|
|
643
654
|
}
|
|
644
655
|
|
|
645
|
-
//
|
|
656
|
+
// ../../../../workflows-codex-execution-repair-1/dependency-update-1/standalone/workflows/node_modules/@hasna/contracts/dist/client/transport.js
|
|
646
657
|
import { isIP } from "net";
|
|
647
658
|
import { spawnSync } from "child_process";
|
|
648
659
|
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync2 } from "fs";
|
package/dist/sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// ../../../../workflows-codex-execution-repair-1/dependency-update-1/standalone/workflows/node_modules/@hasna/contracts/dist/client/transport.js
|
|
2
2
|
import { isIP } from "net";
|
|
3
3
|
import { spawnSync } from "child_process";
|
|
4
4
|
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync2 } from "fs";
|
|
@@ -1448,7 +1448,7 @@ function workflowAgent(env = process.env, explicit) {
|
|
|
1448
1448
|
// package.json
|
|
1449
1449
|
var package_default = {
|
|
1450
1450
|
name: "@hasna/workflows",
|
|
1451
|
-
version: "0.2.
|
|
1451
|
+
version: "0.2.1",
|
|
1452
1452
|
description: "Universal graph workflow app — graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
1453
1453
|
type: "module",
|
|
1454
1454
|
main: "./dist/index.js",
|
|
@@ -1524,9 +1524,9 @@ var package_default = {
|
|
|
1524
1524
|
dependencies: {
|
|
1525
1525
|
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
1526
1526
|
"@cursor/sdk": "^1.0.28",
|
|
1527
|
-
"@openai/codex-sdk": "0.147.0",
|
|
1528
|
-
commander: "^13.1.0",
|
|
1529
1527
|
"@hasna/contracts": "1.3.3",
|
|
1528
|
+
"@openai/codex-sdk": "0.160.0",
|
|
1529
|
+
commander: "^13.1.0",
|
|
1530
1530
|
pg: "^8.16.3"
|
|
1531
1531
|
},
|
|
1532
1532
|
devDependencies: {
|
package/dist/serve/admin.js
CHANGED
|
@@ -22,7 +22,7 @@ var package_default;
|
|
|
22
22
|
var init_package = __esm(() => {
|
|
23
23
|
package_default = {
|
|
24
24
|
name: "@hasna/workflows",
|
|
25
|
-
version: "0.2.
|
|
25
|
+
version: "0.2.1",
|
|
26
26
|
description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
27
27
|
type: "module",
|
|
28
28
|
main: "./dist/index.js",
|
|
@@ -98,9 +98,9 @@ var init_package = __esm(() => {
|
|
|
98
98
|
dependencies: {
|
|
99
99
|
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
100
100
|
"@cursor/sdk": "^1.0.28",
|
|
101
|
-
"@openai/codex-sdk": "0.147.0",
|
|
102
|
-
commander: "^13.1.0",
|
|
103
101
|
"@hasna/contracts": "1.3.3",
|
|
102
|
+
"@openai/codex-sdk": "0.160.0",
|
|
103
|
+
commander: "^13.1.0",
|
|
104
104
|
pg: "^8.16.3"
|
|
105
105
|
},
|
|
106
106
|
devDependencies: {
|
|
@@ -606,6 +606,16 @@ function validateNode(graph, node, issues) {
|
|
|
606
606
|
if (node.lane !== undefined && !LANE_KINDS.includes(node.lane)) {
|
|
607
607
|
issue(issues, `nodes.${node.id}`, `unknown lane ${JSON.stringify(node.lane)} (expected one of ${LANE_KINDS.join(", ")})`);
|
|
608
608
|
}
|
|
609
|
+
if (node.cwd !== undefined && (typeof node.cwd !== "string" || !node.cwd.trim() || node.cwd.includes("\x00"))) {
|
|
610
|
+
issue(issues, `nodes.${node.id}.cwd`, "cwd must be a non-empty path string without NUL characters on the executor");
|
|
611
|
+
}
|
|
612
|
+
if (node.codexMode !== undefined) {
|
|
613
|
+
if (!CODEX_EXECUTION_MODES.includes(node.codexMode)) {
|
|
614
|
+
issue(issues, `nodes.${node.id}.codexMode`, "codexMode must be repository or read-only");
|
|
615
|
+
} else if (node.lane !== "codex" || !node.prompt || node.command !== undefined) {
|
|
616
|
+
issue(issues, `nodes.${node.id}.codexMode`, "codexMode is supported only for a codex prompt step without command");
|
|
617
|
+
}
|
|
618
|
+
}
|
|
609
619
|
if (node.maxRetries !== undefined && (!Number.isInteger(node.maxRetries) || node.maxRetries < 0)) {
|
|
610
620
|
issue(issues, `nodes.${node.id}`, "maxRetries must be a non-negative integer");
|
|
611
621
|
}
|
|
@@ -683,11 +693,12 @@ function validateNode(graph, node, issues) {
|
|
|
683
693
|
break;
|
|
684
694
|
}
|
|
685
695
|
}
|
|
686
|
-
var GRAPH_NODE_TYPES, LANE_KINDS, NODE_ID_RE;
|
|
696
|
+
var GRAPH_NODE_TYPES, LANE_KINDS, CODEX_EXECUTION_MODES, NODE_ID_RE;
|
|
687
697
|
var init_graph = __esm(() => {
|
|
688
698
|
init_expr();
|
|
689
699
|
GRAPH_NODE_TYPES = ["start", "step", "decision", "while", "end"];
|
|
690
700
|
LANE_KINDS = ["claude", "codex", "cursor", "grok"];
|
|
701
|
+
CODEX_EXECUTION_MODES = ["repository", "read-only"];
|
|
691
702
|
NODE_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
692
703
|
});
|
|
693
704
|
|
|
@@ -1355,9 +1366,15 @@ async function spawnCli(cmd, opts) {
|
|
|
1355
1366
|
new Response(proc.stderr).text(),
|
|
1356
1367
|
proc.exited
|
|
1357
1368
|
]);
|
|
1358
|
-
const output = stderr ? `${stdout}
|
|
1369
|
+
const output = opts.stdoutOnly ? stdout.trim() : stderr ? `${stdout}
|
|
1359
1370
|
${stderr}`.trim() : stdout.trim();
|
|
1360
|
-
return {
|
|
1371
|
+
return {
|
|
1372
|
+
ok: exitCode === 0,
|
|
1373
|
+
exitCode,
|
|
1374
|
+
output,
|
|
1375
|
+
durationMs: Date.now() - started,
|
|
1376
|
+
...opts.stdoutOnly && exitCode !== 0 && stderr.trim() ? { error: stderr.trim() } : {}
|
|
1377
|
+
};
|
|
1361
1378
|
}
|
|
1362
1379
|
var LANE_KINDS2, LaneDependencyMissingError, LaneAdapterShapeError, DEFAULT_LANE_TIMEOUT_MS = 120000, LaneTimeoutError;
|
|
1363
1380
|
var init_types = __esm(() => {
|
|
@@ -1501,6 +1518,9 @@ function createCodexAdapter(deps = {}) {
|
|
|
1501
1518
|
if (!job.prompt) {
|
|
1502
1519
|
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "codex lane requires a prompt" };
|
|
1503
1520
|
}
|
|
1521
|
+
if (job.codexMode !== undefined && (!CODEX_EXECUTION_MODES.includes(job.codexMode) || job.lane !== "codex" || job.command !== undefined)) {
|
|
1522
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "codexMode must be repository or read-only on a codex prompt without command", retryable: false };
|
|
1523
|
+
}
|
|
1504
1524
|
const started = Date.now();
|
|
1505
1525
|
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
1506
1526
|
const loader = deps.sdkLoader ?? (async () => await import("@openai/codex-sdk"));
|
|
@@ -1520,13 +1540,20 @@ function createCodexAdapter(deps = {}) {
|
|
|
1520
1540
|
config: { model: job.model },
|
|
1521
1541
|
env: subscriptionEnvironment(job.env)
|
|
1522
1542
|
})), timeoutMs, "codex lane");
|
|
1523
|
-
const thread = client.startThread?.({
|
|
1543
|
+
const thread = client.startThread?.({
|
|
1544
|
+
model: job.model,
|
|
1545
|
+
workingDirectory: job.cwd,
|
|
1546
|
+
...job.codexMode === "read-only" ? { sandboxMode: "read-only", skipGitRepoCheck: true, approvalPolicy: "never" } : {}
|
|
1547
|
+
});
|
|
1524
1548
|
if (!thread || typeof thread.run !== "function") {
|
|
1525
1549
|
throw new LaneAdapterShapeError("@openai/codex-sdk Codex.startThread() returned no run()");
|
|
1526
1550
|
}
|
|
1527
1551
|
const turn = await withTimeout(thread.run(job.prompt, { signal: abortController.signal }), timeoutMs, "codex lane", () => abortController.abort());
|
|
1528
|
-
const output = turn?.finalResponse ?? "";
|
|
1529
1552
|
const durationMs = Date.now() - started;
|
|
1553
|
+
const output = turn?.finalResponse;
|
|
1554
|
+
if (typeof output !== "string" || !output.trim()) {
|
|
1555
|
+
return missingResponse(durationMs);
|
|
1556
|
+
}
|
|
1530
1557
|
return { ok: true, exitCode: 0, output, durationMs };
|
|
1531
1558
|
} catch (err) {
|
|
1532
1559
|
const durationMs = Date.now() - started;
|
|
@@ -1537,14 +1564,17 @@ function createCodexAdapter(deps = {}) {
|
|
|
1537
1564
|
}
|
|
1538
1565
|
async function runCodexCli(job, cliPath, started, timeoutMs) {
|
|
1539
1566
|
try {
|
|
1540
|
-
const
|
|
1567
|
+
const controls = job.codexMode === "read-only" ? ["--sandbox", "read-only", "--skip-git-repo-check", "--config", 'approval_policy="never"'] : [];
|
|
1568
|
+
const result = await spawnCli([cliPath, "exec", ...controls, "--", job.prompt], {
|
|
1541
1569
|
binaryName: cliPath,
|
|
1542
1570
|
packageHint: "@openai/codex-sdk or the codex CLI",
|
|
1543
1571
|
timeoutMs,
|
|
1544
1572
|
env: job.env,
|
|
1545
|
-
cwd: job.cwd
|
|
1573
|
+
cwd: job.cwd,
|
|
1574
|
+
stdoutOnly: true
|
|
1546
1575
|
});
|
|
1547
|
-
|
|
1576
|
+
const durationMs = Date.now() - started;
|
|
1577
|
+
return result.ok && !result.output.trim() ? missingResponse(durationMs) : { ...result, durationMs };
|
|
1548
1578
|
} catch (err) {
|
|
1549
1579
|
if (err instanceof LaneDependencyMissingError) {
|
|
1550
1580
|
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
@@ -1552,7 +1582,11 @@ async function runCodexCli(job, cliPath, started, timeoutMs) {
|
|
|
1552
1582
|
throw err;
|
|
1553
1583
|
}
|
|
1554
1584
|
}
|
|
1585
|
+
function missingResponse(durationMs) {
|
|
1586
|
+
return { ok: false, exitCode: 1, output: "", durationMs, error: "Codex completed without an agent response; verify the native subscription and model route", retryable: false };
|
|
1587
|
+
}
|
|
1555
1588
|
var init_codex = __esm(() => {
|
|
1589
|
+
init_graph();
|
|
1556
1590
|
init_types();
|
|
1557
1591
|
});
|
|
1558
1592
|
|
|
@@ -2069,7 +2103,14 @@ class WorkflowsDaemon {
|
|
|
2069
2103
|
const isLoopBody = graph.nodes.some((candidate) => candidate.type === "while" && candidate.body.includes(nodeId));
|
|
2070
2104
|
const nodeRow = latest === undefined || isLoopBody && latest.status === "completed" ? await this.store.createRunNode({ runId: run2.id, nodeId, lane: node2.lane ?? "claude" }) : latest;
|
|
2071
2105
|
const memoWatch = node2.memo ? memoWatchFingerprint(graph, this.store.dataDir) : undefined;
|
|
2072
|
-
const memoSource = {
|
|
2106
|
+
const memoSource = {
|
|
2107
|
+
command: node2.command,
|
|
2108
|
+
prompt: node2.prompt,
|
|
2109
|
+
context: stripRunState(context),
|
|
2110
|
+
lane: node2.lane ?? "claude",
|
|
2111
|
+
cwd: node2.cwd,
|
|
2112
|
+
codexMode: node2.lane === "codex" ? node2.codexMode ?? "repository" : undefined
|
|
2113
|
+
};
|
|
2073
2114
|
const memoInput = node2.memo ? memoWatch !== undefined ? { ...memoSource, memoWatch } : memoSource : undefined;
|
|
2074
2115
|
if (memoInput !== undefined) {
|
|
2075
2116
|
const hit = await tryMemoHit(this.store, run2.graphName, nodeId, memoInput);
|
|
@@ -2094,6 +2135,8 @@ class WorkflowsDaemon {
|
|
|
2094
2135
|
lane: node2.lane ?? "claude",
|
|
2095
2136
|
prompt: node2.prompt,
|
|
2096
2137
|
command: node2.command,
|
|
2138
|
+
cwd: node2.cwd,
|
|
2139
|
+
codexMode: node2.codexMode,
|
|
2097
2140
|
timeoutMs: node2.timeoutMs,
|
|
2098
2141
|
env: this.env
|
|
2099
2142
|
};
|
|
@@ -3283,7 +3326,7 @@ function buildOpenApi(version) {
|
|
|
3283
3326
|
ReadinessReport: object({ ok: { type: "boolean" }, service: string, version: string, checks: { type: "object", additionalProperties: string } }),
|
|
3284
3327
|
GraphValidation: object({ ok: { type: "boolean" }, issues: { type: "array", items: object({ path: string, message: string }) } }),
|
|
3285
3328
|
StartNode: object({ id: string, type: { type: "string", enum: ["start"] }, next: string }, ["id", "type"]),
|
|
3286
|
-
StepNode: object({ id: string, type: { type: "string", enum: ["step"] }, next: string, lane: { type: "string", enum: ["claude", "codex", "cursor", "grok"] }, prompt: string, command: string, maxRetries: { type: "integer", minimum: 0 }, memo: { type: "boolean" }, timeoutMs: { type: "integer", minimum: 1 } }, ["id", "type"]),
|
|
3329
|
+
StepNode: object({ id: string, type: { type: "string", enum: ["step"] }, next: string, lane: { type: "string", enum: ["claude", "codex", "cursor", "grok"] }, prompt: string, command: string, cwd: { type: "string", minLength: 1 }, codexMode: { type: "string", enum: ["repository", "read-only"], default: "repository", description: "Codex prompt steps only. Read-only permits nonrepository execution with a read-only sandbox and no approvals. Repository retains native trust, sandbox and approval defaults." }, maxRetries: { type: "integer", minimum: 0 }, memo: { type: "boolean" }, timeoutMs: { type: "integer", minimum: 1 } }, ["id", "type"]),
|
|
3287
3330
|
DecisionNode: object({ id: string, type: { type: "string", enum: ["decision"] }, condition: string, then: string, else: string }, ["id", "type", "condition"]),
|
|
3288
3331
|
WhileNode: object({ id: string, type: { type: "string", enum: ["while"] }, condition: string, body: { type: "array", items: string }, maxIterations: { type: "integer", minimum: 1 }, next: string }, ["id", "type", "condition", "body", "maxIterations"]),
|
|
3289
3332
|
EndNode: object({ id: string, type: { type: "string", enum: ["end"] } }),
|
package/dist/serve/index.js
CHANGED
|
@@ -22,7 +22,7 @@ var package_default;
|
|
|
22
22
|
var init_package = __esm(() => {
|
|
23
23
|
package_default = {
|
|
24
24
|
name: "@hasna/workflows",
|
|
25
|
-
version: "0.2.
|
|
25
|
+
version: "0.2.1",
|
|
26
26
|
description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
27
27
|
type: "module",
|
|
28
28
|
main: "./dist/index.js",
|
|
@@ -98,9 +98,9 @@ var init_package = __esm(() => {
|
|
|
98
98
|
dependencies: {
|
|
99
99
|
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
100
100
|
"@cursor/sdk": "^1.0.28",
|
|
101
|
-
"@openai/codex-sdk": "0.147.0",
|
|
102
|
-
commander: "^13.1.0",
|
|
103
101
|
"@hasna/contracts": "1.3.3",
|
|
102
|
+
"@openai/codex-sdk": "0.160.0",
|
|
103
|
+
commander: "^13.1.0",
|
|
104
104
|
pg: "^8.16.3"
|
|
105
105
|
},
|
|
106
106
|
devDependencies: {
|
|
@@ -606,6 +606,16 @@ function validateNode(graph, node, issues) {
|
|
|
606
606
|
if (node.lane !== undefined && !LANE_KINDS.includes(node.lane)) {
|
|
607
607
|
issue(issues, `nodes.${node.id}`, `unknown lane ${JSON.stringify(node.lane)} (expected one of ${LANE_KINDS.join(", ")})`);
|
|
608
608
|
}
|
|
609
|
+
if (node.cwd !== undefined && (typeof node.cwd !== "string" || !node.cwd.trim() || node.cwd.includes("\x00"))) {
|
|
610
|
+
issue(issues, `nodes.${node.id}.cwd`, "cwd must be a non-empty path string without NUL characters on the executor");
|
|
611
|
+
}
|
|
612
|
+
if (node.codexMode !== undefined) {
|
|
613
|
+
if (!CODEX_EXECUTION_MODES.includes(node.codexMode)) {
|
|
614
|
+
issue(issues, `nodes.${node.id}.codexMode`, "codexMode must be repository or read-only");
|
|
615
|
+
} else if (node.lane !== "codex" || !node.prompt || node.command !== undefined) {
|
|
616
|
+
issue(issues, `nodes.${node.id}.codexMode`, "codexMode is supported only for a codex prompt step without command");
|
|
617
|
+
}
|
|
618
|
+
}
|
|
609
619
|
if (node.maxRetries !== undefined && (!Number.isInteger(node.maxRetries) || node.maxRetries < 0)) {
|
|
610
620
|
issue(issues, `nodes.${node.id}`, "maxRetries must be a non-negative integer");
|
|
611
621
|
}
|
|
@@ -683,11 +693,12 @@ function validateNode(graph, node, issues) {
|
|
|
683
693
|
break;
|
|
684
694
|
}
|
|
685
695
|
}
|
|
686
|
-
var GRAPH_NODE_TYPES, LANE_KINDS, NODE_ID_RE;
|
|
696
|
+
var GRAPH_NODE_TYPES, LANE_KINDS, CODEX_EXECUTION_MODES, NODE_ID_RE;
|
|
687
697
|
var init_graph = __esm(() => {
|
|
688
698
|
init_expr();
|
|
689
699
|
GRAPH_NODE_TYPES = ["start", "step", "decision", "while", "end"];
|
|
690
700
|
LANE_KINDS = ["claude", "codex", "cursor", "grok"];
|
|
701
|
+
CODEX_EXECUTION_MODES = ["repository", "read-only"];
|
|
691
702
|
NODE_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
692
703
|
});
|
|
693
704
|
|
|
@@ -1355,9 +1366,15 @@ async function spawnCli(cmd, opts) {
|
|
|
1355
1366
|
new Response(proc.stderr).text(),
|
|
1356
1367
|
proc.exited
|
|
1357
1368
|
]);
|
|
1358
|
-
const output = stderr ? `${stdout}
|
|
1369
|
+
const output = opts.stdoutOnly ? stdout.trim() : stderr ? `${stdout}
|
|
1359
1370
|
${stderr}`.trim() : stdout.trim();
|
|
1360
|
-
return {
|
|
1371
|
+
return {
|
|
1372
|
+
ok: exitCode === 0,
|
|
1373
|
+
exitCode,
|
|
1374
|
+
output,
|
|
1375
|
+
durationMs: Date.now() - started,
|
|
1376
|
+
...opts.stdoutOnly && exitCode !== 0 && stderr.trim() ? { error: stderr.trim() } : {}
|
|
1377
|
+
};
|
|
1361
1378
|
}
|
|
1362
1379
|
var LANE_KINDS2, LaneDependencyMissingError, LaneAdapterShapeError, DEFAULT_LANE_TIMEOUT_MS = 120000, LaneTimeoutError;
|
|
1363
1380
|
var init_types = __esm(() => {
|
|
@@ -1501,6 +1518,9 @@ function createCodexAdapter(deps = {}) {
|
|
|
1501
1518
|
if (!job.prompt) {
|
|
1502
1519
|
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "codex lane requires a prompt" };
|
|
1503
1520
|
}
|
|
1521
|
+
if (job.codexMode !== undefined && (!CODEX_EXECUTION_MODES.includes(job.codexMode) || job.lane !== "codex" || job.command !== undefined)) {
|
|
1522
|
+
return { ok: false, exitCode: 2, output: "", durationMs: 0, error: "codexMode must be repository or read-only on a codex prompt without command", retryable: false };
|
|
1523
|
+
}
|
|
1504
1524
|
const started = Date.now();
|
|
1505
1525
|
const timeoutMs = job.timeoutMs ?? DEFAULT_LANE_TIMEOUT_MS;
|
|
1506
1526
|
const loader = deps.sdkLoader ?? (async () => await import("@openai/codex-sdk"));
|
|
@@ -1520,13 +1540,20 @@ function createCodexAdapter(deps = {}) {
|
|
|
1520
1540
|
config: { model: job.model },
|
|
1521
1541
|
env: subscriptionEnvironment(job.env)
|
|
1522
1542
|
})), timeoutMs, "codex lane");
|
|
1523
|
-
const thread = client.startThread?.({
|
|
1543
|
+
const thread = client.startThread?.({
|
|
1544
|
+
model: job.model,
|
|
1545
|
+
workingDirectory: job.cwd,
|
|
1546
|
+
...job.codexMode === "read-only" ? { sandboxMode: "read-only", skipGitRepoCheck: true, approvalPolicy: "never" } : {}
|
|
1547
|
+
});
|
|
1524
1548
|
if (!thread || typeof thread.run !== "function") {
|
|
1525
1549
|
throw new LaneAdapterShapeError("@openai/codex-sdk Codex.startThread() returned no run()");
|
|
1526
1550
|
}
|
|
1527
1551
|
const turn = await withTimeout(thread.run(job.prompt, { signal: abortController.signal }), timeoutMs, "codex lane", () => abortController.abort());
|
|
1528
|
-
const output = turn?.finalResponse ?? "";
|
|
1529
1552
|
const durationMs = Date.now() - started;
|
|
1553
|
+
const output = turn?.finalResponse;
|
|
1554
|
+
if (typeof output !== "string" || !output.trim()) {
|
|
1555
|
+
return missingResponse(durationMs);
|
|
1556
|
+
}
|
|
1530
1557
|
return { ok: true, exitCode: 0, output, durationMs };
|
|
1531
1558
|
} catch (err) {
|
|
1532
1559
|
const durationMs = Date.now() - started;
|
|
@@ -1537,14 +1564,17 @@ function createCodexAdapter(deps = {}) {
|
|
|
1537
1564
|
}
|
|
1538
1565
|
async function runCodexCli(job, cliPath, started, timeoutMs) {
|
|
1539
1566
|
try {
|
|
1540
|
-
const
|
|
1567
|
+
const controls = job.codexMode === "read-only" ? ["--sandbox", "read-only", "--skip-git-repo-check", "--config", 'approval_policy="never"'] : [];
|
|
1568
|
+
const result = await spawnCli([cliPath, "exec", ...controls, "--", job.prompt], {
|
|
1541
1569
|
binaryName: cliPath,
|
|
1542
1570
|
packageHint: "@openai/codex-sdk or the codex CLI",
|
|
1543
1571
|
timeoutMs,
|
|
1544
1572
|
env: job.env,
|
|
1545
|
-
cwd: job.cwd
|
|
1573
|
+
cwd: job.cwd,
|
|
1574
|
+
stdoutOnly: true
|
|
1546
1575
|
});
|
|
1547
|
-
|
|
1576
|
+
const durationMs = Date.now() - started;
|
|
1577
|
+
return result.ok && !result.output.trim() ? missingResponse(durationMs) : { ...result, durationMs };
|
|
1548
1578
|
} catch (err) {
|
|
1549
1579
|
if (err instanceof LaneDependencyMissingError) {
|
|
1550
1580
|
return { ok: false, exitCode: 127, output: "", durationMs: Date.now() - started, error: err.message };
|
|
@@ -1552,7 +1582,11 @@ async function runCodexCli(job, cliPath, started, timeoutMs) {
|
|
|
1552
1582
|
throw err;
|
|
1553
1583
|
}
|
|
1554
1584
|
}
|
|
1585
|
+
function missingResponse(durationMs) {
|
|
1586
|
+
return { ok: false, exitCode: 1, output: "", durationMs, error: "Codex completed without an agent response; verify the native subscription and model route", retryable: false };
|
|
1587
|
+
}
|
|
1555
1588
|
var init_codex = __esm(() => {
|
|
1589
|
+
init_graph();
|
|
1556
1590
|
init_types();
|
|
1557
1591
|
});
|
|
1558
1592
|
|
|
@@ -2069,7 +2103,14 @@ class WorkflowsDaemon {
|
|
|
2069
2103
|
const isLoopBody = graph.nodes.some((candidate) => candidate.type === "while" && candidate.body.includes(nodeId));
|
|
2070
2104
|
const nodeRow = latest === undefined || isLoopBody && latest.status === "completed" ? await this.store.createRunNode({ runId: run2.id, nodeId, lane: node2.lane ?? "claude" }) : latest;
|
|
2071
2105
|
const memoWatch = node2.memo ? memoWatchFingerprint(graph, this.store.dataDir) : undefined;
|
|
2072
|
-
const memoSource = {
|
|
2106
|
+
const memoSource = {
|
|
2107
|
+
command: node2.command,
|
|
2108
|
+
prompt: node2.prompt,
|
|
2109
|
+
context: stripRunState(context),
|
|
2110
|
+
lane: node2.lane ?? "claude",
|
|
2111
|
+
cwd: node2.cwd,
|
|
2112
|
+
codexMode: node2.lane === "codex" ? node2.codexMode ?? "repository" : undefined
|
|
2113
|
+
};
|
|
2073
2114
|
const memoInput = node2.memo ? memoWatch !== undefined ? { ...memoSource, memoWatch } : memoSource : undefined;
|
|
2074
2115
|
if (memoInput !== undefined) {
|
|
2075
2116
|
const hit = await tryMemoHit(this.store, run2.graphName, nodeId, memoInput);
|
|
@@ -2094,6 +2135,8 @@ class WorkflowsDaemon {
|
|
|
2094
2135
|
lane: node2.lane ?? "claude",
|
|
2095
2136
|
prompt: node2.prompt,
|
|
2096
2137
|
command: node2.command,
|
|
2138
|
+
cwd: node2.cwd,
|
|
2139
|
+
codexMode: node2.codexMode,
|
|
2097
2140
|
timeoutMs: node2.timeoutMs,
|
|
2098
2141
|
env: this.env
|
|
2099
2142
|
};
|
|
@@ -3283,7 +3326,7 @@ function buildOpenApi(version) {
|
|
|
3283
3326
|
ReadinessReport: object({ ok: { type: "boolean" }, service: string, version: string, checks: { type: "object", additionalProperties: string } }),
|
|
3284
3327
|
GraphValidation: object({ ok: { type: "boolean" }, issues: { type: "array", items: object({ path: string, message: string }) } }),
|
|
3285
3328
|
StartNode: object({ id: string, type: { type: "string", enum: ["start"] }, next: string }, ["id", "type"]),
|
|
3286
|
-
StepNode: object({ id: string, type: { type: "string", enum: ["step"] }, next: string, lane: { type: "string", enum: ["claude", "codex", "cursor", "grok"] }, prompt: string, command: string, maxRetries: { type: "integer", minimum: 0 }, memo: { type: "boolean" }, timeoutMs: { type: "integer", minimum: 1 } }, ["id", "type"]),
|
|
3329
|
+
StepNode: object({ id: string, type: { type: "string", enum: ["step"] }, next: string, lane: { type: "string", enum: ["claude", "codex", "cursor", "grok"] }, prompt: string, command: string, cwd: { type: "string", minLength: 1 }, codexMode: { type: "string", enum: ["repository", "read-only"], default: "repository", description: "Codex prompt steps only. Read-only permits nonrepository execution with a read-only sandbox and no approvals. Repository retains native trust, sandbox and approval defaults." }, maxRetries: { type: "integer", minimum: 0 }, memo: { type: "boolean" }, timeoutMs: { type: "integer", minimum: 1 } }, ["id", "type"]),
|
|
3287
3330
|
DecisionNode: object({ id: string, type: { type: "string", enum: ["decision"] }, condition: string, then: string, else: string }, ["id", "type", "condition"]),
|
|
3288
3331
|
WhileNode: object({ id: string, type: { type: "string", enum: ["while"] }, condition: string, body: { type: "array", items: string }, maxIterations: { type: "integer", minimum: 1 }, next: string }, ["id", "type", "condition", "body", "maxIterations"]),
|
|
3289
3332
|
EndNode: object({ id: string, type: { type: "string", enum: ["end"] } }),
|