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 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
@@ -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
- function decodeStream(stream) {
2
- if (!stream) {
3
- return Promise.resolve("");
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 Response(stream).text();
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 proc = Bun.spawn(["which", "gh"], {
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
- const proc = Bun.spawn(["gh", ...args], {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orcastrator",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "orca": "dist/cli/index.js"