orcastrator 0.2.3 → 0.2.5
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 +20 -0
- package/dist/cli/commands/run.js +5 -0
- package/dist/core/codex-config.js +58 -0
- package/dist/utils/gh.js +27 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,10 +82,30 @@ export default {
|
|
|
82
82
|
onTaskComplete: "echo task done: $ORCA_TASK_NAME",
|
|
83
83
|
onComplete: "echo run complete",
|
|
84
84
|
onError: "echo run failed"
|
|
85
|
+
},
|
|
86
|
+
codex: {
|
|
87
|
+
model: "gpt-5.3-codex", // override the codex model
|
|
88
|
+
multiAgent: true, // enable codex multi-agent (see below)
|
|
85
89
|
}
|
|
86
90
|
};
|
|
87
91
|
```
|
|
88
92
|
|
|
93
|
+
### Multi-agent mode
|
|
94
|
+
|
|
95
|
+
Codex supports experimental [multi-agent workflows](https://developers.openai.com/codex/multi-agent) where it can spawn parallel sub-agents for complex tasks.
|
|
96
|
+
|
|
97
|
+
To enable it in orca, set `codex.multiAgent: true` in your config:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
export default {
|
|
101
|
+
codex: { multiAgent: true }
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
When enabled, orca adds `multi_agent = true` to your global `~/.codex/config.toml`. If you already have multi-agent enabled in your Codex config, it will work automatically without setting anything in orca.
|
|
106
|
+
|
|
107
|
+
> **Note:** Multi-agent is off by default because enabling it modifies your global Codex configuration. It is currently an experimental Codex feature.
|
|
108
|
+
|
|
89
109
|
## Reference
|
|
90
110
|
|
|
91
111
|
### Flags
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import { createCodexSession } from "../../agents/codex/session.js";
|
|
7
|
+
import { ensureCodexMultiAgent } from "../../core/codex-config.js";
|
|
7
8
|
import { resolveConfig } from "../../core/config-loader.js";
|
|
8
9
|
import { runPlanner } from "../../core/planner.js";
|
|
9
10
|
import { runTaskRunner } from "../../core/task-runner.js";
|
|
@@ -124,6 +125,10 @@ export async function runCommandHandler(options) {
|
|
|
124
125
|
await dispatcher.dispatch(event);
|
|
125
126
|
};
|
|
126
127
|
const cwd = process.cwd();
|
|
128
|
+
const multiAgentResult = await ensureCodexMultiAgent(orcaConfig ?? undefined);
|
|
129
|
+
if (multiAgentResult.action === "created" || multiAgentResult.action === "appended") {
|
|
130
|
+
console.log(`Multi-agent: enabled (updated ${multiAgentResult.path})`);
|
|
131
|
+
}
|
|
127
132
|
const codexSession = await createCodexSession(cwd, orcaConfig ?? undefined);
|
|
128
133
|
try {
|
|
129
134
|
// Phase 4: Codex consults the task graph before execution begins.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const CODEX_HOME = path.join(os.homedir(), ".codex");
|
|
6
|
+
const GLOBAL_CONFIG_FILE = path.join(CODEX_HOME, "config.toml");
|
|
7
|
+
const ORCA_MULTI_AGENT_BLOCK = `# Added by orca — remove or set multi_agent = false to disable
|
|
8
|
+
[features]
|
|
9
|
+
multi_agent = true
|
|
10
|
+
`;
|
|
11
|
+
function isMultiAgentEnabled(config) {
|
|
12
|
+
// Default: off. Only enable if explicitly set to true.
|
|
13
|
+
return config?.codex?.multiAgent === true;
|
|
14
|
+
}
|
|
15
|
+
function containsMultiAgentSetting(content) {
|
|
16
|
+
return /multi_agent\s*=/.test(content);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Ensures `~/.codex/config.toml` has `multi_agent = true` set.
|
|
20
|
+
*
|
|
21
|
+
* Uses the global config (not project-scoped) to avoid the "trusted projects"
|
|
22
|
+
* restriction that prevents project-level config from being loaded headlessly.
|
|
23
|
+
*
|
|
24
|
+
* - If the file doesn't exist: creates it with the multi_agent block.
|
|
25
|
+
* - If the file exists and already has `multi_agent`: leaves it alone.
|
|
26
|
+
* - If the file exists but has no `multi_agent`: appends the feature block.
|
|
27
|
+
* - If `multiAgent` is false in orca config: skips entirely.
|
|
28
|
+
*
|
|
29
|
+
* @param config - Orca config (checks codex.multiAgent flag)
|
|
30
|
+
* @param _configFile - Override config file path (for testing only)
|
|
31
|
+
*/
|
|
32
|
+
export async function ensureCodexMultiAgent(config, _configFile) {
|
|
33
|
+
const configFile = _configFile ?? GLOBAL_CONFIG_FILE;
|
|
34
|
+
if (!isMultiAgentEnabled(config)) {
|
|
35
|
+
return { action: "skipped", path: configFile };
|
|
36
|
+
}
|
|
37
|
+
let existingContent = null;
|
|
38
|
+
try {
|
|
39
|
+
await access(configFile, fsConstants.R_OK);
|
|
40
|
+
existingContent = await readFile(configFile, "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err.code !== "ENOENT") {
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (existingContent === null) {
|
|
48
|
+
await mkdir(path.dirname(configFile), { recursive: true });
|
|
49
|
+
await writeFile(configFile, ORCA_MULTI_AGENT_BLOCK, "utf8");
|
|
50
|
+
return { action: "created", path: configFile };
|
|
51
|
+
}
|
|
52
|
+
if (containsMultiAgentSetting(existingContent)) {
|
|
53
|
+
return { action: "already-set", path: configFile };
|
|
54
|
+
}
|
|
55
|
+
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
56
|
+
await writeFile(configFile, `${existingContent}${separator}${ORCA_MULTI_AGENT_BLOCK}`, "utf8");
|
|
57
|
+
return { action: "appended", path: configFile };
|
|
58
|
+
}
|
package/dist/utils/gh.js
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
|
+
function spawnProcess(cmd, args) {
|
|
3
|
+
// Use Bun.spawn when available, fall back to Node.js child_process
|
|
4
|
+
if (typeof globalThis.Bun !== "undefined") {
|
|
5
|
+
return (async () => {
|
|
6
|
+
const proc = globalThis.Bun.spawn([cmd, ...args], {
|
|
7
|
+
stdout: "pipe",
|
|
8
|
+
stderr: "pipe"
|
|
9
|
+
});
|
|
10
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
11
|
+
new Response(proc.stdout).text(),
|
|
12
|
+
new Response(proc.stderr).text(),
|
|
13
|
+
proc.exited
|
|
14
|
+
]);
|
|
15
|
+
return { stdout, stderr, exitCode };
|
|
16
|
+
})();
|
|
4
17
|
}
|
|
5
|
-
return new
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let stdout = "";
|
|
20
|
+
let stderr = "";
|
|
21
|
+
const child = nodeSpawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
22
|
+
child.stdout?.on("data", (d) => { stdout += d.toString(); });
|
|
23
|
+
child.stderr?.on("data", (d) => { stderr += d.toString(); });
|
|
24
|
+
child.on("close", (code) => resolve({ stdout, stderr, exitCode: code ?? 1 }));
|
|
25
|
+
child.on("error", () => resolve({ stdout, stderr, exitCode: 1 }));
|
|
26
|
+
});
|
|
6
27
|
}
|
|
7
28
|
export async function checkGhCli() {
|
|
8
29
|
try {
|
|
9
|
-
const
|
|
10
|
-
stdout: "pipe",
|
|
11
|
-
stderr: "pipe"
|
|
12
|
-
});
|
|
13
|
-
const exitCode = await proc.exited;
|
|
30
|
+
const { exitCode } = await spawnProcess("which", ["gh"]);
|
|
14
31
|
return exitCode === 0;
|
|
15
32
|
}
|
|
16
33
|
catch {
|
|
@@ -18,18 +35,5 @@ export async function checkGhCli() {
|
|
|
18
35
|
}
|
|
19
36
|
}
|
|
20
37
|
export async function runGh(args) {
|
|
21
|
-
|
|
22
|
-
stdout: "pipe",
|
|
23
|
-
stderr: "pipe"
|
|
24
|
-
});
|
|
25
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
26
|
-
decodeStream(proc.stdout),
|
|
27
|
-
decodeStream(proc.stderr),
|
|
28
|
-
proc.exited
|
|
29
|
-
]);
|
|
30
|
-
return {
|
|
31
|
-
stdout,
|
|
32
|
-
stderr,
|
|
33
|
-
exitCode
|
|
34
|
-
};
|
|
38
|
+
return spawnProcess("gh", args);
|
|
35
39
|
}
|