@open330/oac 2026.4.3 → 2026.220.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/CHANGELOG.md +115 -0
- package/README.md +2 -2
- package/dist/{chunk-YWIB3TRI.js → chunk-6A37SKAJ.js} +15 -2
- package/dist/chunk-6A37SKAJ.js.map +1 -0
- package/dist/{chunk-ZRYAHZQJ.js → chunk-7C7SC4TZ.js} +1 -1
- package/dist/{chunk-XUW3XWTX.js → chunk-7Y4LZUDP.js} +89 -121
- package/dist/chunk-7Y4LZUDP.js.map +1 -0
- package/dist/{chunk-CJAJ4MBO.js → chunk-OS3XDHOJ.js} +57 -18
- package/dist/chunk-OS3XDHOJ.js.map +1 -0
- package/dist/{chunk-HDMLNOND.js → chunk-OTPXGXO7.js} +44 -18
- package/dist/chunk-OTPXGXO7.js.map +1 -0
- package/dist/{chunk-7FWC3Z4W.js → chunk-QPVNC7S4.js} +479 -196
- package/dist/chunk-QPVNC7S4.js.map +1 -0
- package/dist/cli/cli.js +6 -6
- package/dist/cli/index.js +6 -6
- package/dist/completion/index.js +4 -8
- package/dist/completion/index.js.map +1 -1
- package/dist/core/index.d.ts +16 -1
- package/dist/core/index.js +8 -4
- package/dist/dashboard/index.js +33 -24
- package/dist/dashboard/index.js.map +1 -1
- package/dist/discovery/index.d.ts +18 -2
- package/dist/discovery/index.js +4 -2
- package/dist/execution/index.d.ts +45 -1
- package/dist/execution/index.js +7 -3
- package/dist/repo/index.d.ts +2 -2
- package/dist/repo/index.js +1 -1
- package/dist/{types-CYCwgojB.d.ts → types-3_IAAxh5.d.ts} +1 -0
- package/docs/config-reference.md +271 -0
- package/docs/multi-agent-support-technical-spec.md +312 -0
- package/package.json +23 -18
- package/dist/chunk-7FWC3Z4W.js.map +0 -1
- package/dist/chunk-CJAJ4MBO.js.map +0 -1
- package/dist/chunk-HDMLNOND.js.map +0 -1
- package/dist/chunk-XUW3XWTX.js.map +0 -1
- package/dist/chunk-YWIB3TRI.js.map +0 -1
- /package/dist/{chunk-ZRYAHZQJ.js.map → chunk-7C7SC4TZ.js.map} +0 -0
|
@@ -85,6 +85,50 @@ declare class CodexAdapter implements AgentProvider {
|
|
|
85
85
|
abort(executionId: string): Promise<void>;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
declare class OpenCodeAdapter implements AgentProvider {
|
|
89
|
+
readonly id: AgentProviderId;
|
|
90
|
+
readonly name = "OpenCode";
|
|
91
|
+
private readonly runningExecutions;
|
|
92
|
+
checkAvailability(): Promise<AgentAvailability>;
|
|
93
|
+
execute(params: AgentExecuteParams): AgentExecution;
|
|
94
|
+
estimateTokens(params: TokenEstimateParams): Promise<TokenEstimate>;
|
|
95
|
+
abort(executionId: string): Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Factory function that creates a new adapter instance.
|
|
100
|
+
*
|
|
101
|
+
* Using factories (rather than singleton instances) ensures each
|
|
102
|
+
* concurrent pipeline run gets its own `runningExecutions` map.
|
|
103
|
+
*/
|
|
104
|
+
type AdapterFactory = () => AgentProvider;
|
|
105
|
+
/**
|
|
106
|
+
* Maintains a registry of agent adapter factories keyed by provider ID.
|
|
107
|
+
*
|
|
108
|
+
* Built-in adapters (claude-code, codex, opencode) are registered at
|
|
109
|
+
* module load time. Custom adapters can be added at runtime with
|
|
110
|
+
* `adapterRegistry.register(id, factory)`.
|
|
111
|
+
*/
|
|
112
|
+
declare class AdapterRegistry {
|
|
113
|
+
private readonly factories;
|
|
114
|
+
/** Well-known aliases (e.g. legacy IDs) that map to canonical provider IDs. */
|
|
115
|
+
private readonly aliases;
|
|
116
|
+
/** Register a new adapter factory. Replaces any previous factory for the same ID. */
|
|
117
|
+
register(id: string, factory: AdapterFactory): void;
|
|
118
|
+
/** Add an alias that maps to an existing canonical ID. */
|
|
119
|
+
alias(alias: string, canonicalId: string): void;
|
|
120
|
+
/** Resolve an ID (including aliases) and return the factory, or `undefined`. */
|
|
121
|
+
get(rawId: string): AdapterFactory | undefined;
|
|
122
|
+
/** Canonical ID after alias resolution. */
|
|
123
|
+
resolveId(rawId: string): string;
|
|
124
|
+
/** All registered canonical provider IDs. */
|
|
125
|
+
registeredIds(): string[];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Global singleton registry with built-in adapters pre-registered.
|
|
129
|
+
*/
|
|
130
|
+
declare const adapterRegistry: AdapterRegistry;
|
|
131
|
+
|
|
88
132
|
interface SandboxContext {
|
|
89
133
|
path: string;
|
|
90
134
|
branchName: string;
|
|
@@ -168,4 +212,4 @@ declare class ExecutionEngine {
|
|
|
168
212
|
private buildRunResult;
|
|
169
213
|
}
|
|
170
214
|
|
|
171
|
-
export { type AgentAvailability, type AgentEvent, type AgentExecuteParams, type AgentExecution, type AgentProvider, type AgentResult, ClaudeCodeAdapter, CodexAdapter, type ExecuteTaskOptions, ExecutionEngine, type ExecutionEngineConfig, type Job, type JobStatus, type RunResult, type SandboxContext, type TokenEstimateParams, buildEpicPrompt, createSandbox, epicAsTask, executeTask, isTransientError };
|
|
215
|
+
export { type AdapterFactory, type AgentAvailability, type AgentEvent, type AgentExecuteParams, type AgentExecution, type AgentProvider, type AgentResult, ClaudeCodeAdapter, CodexAdapter, type ExecuteTaskOptions, ExecutionEngine, type ExecutionEngineConfig, type Job, type JobStatus, OpenCodeAdapter, type RunResult, type SandboxContext, type TokenEstimateParams, adapterRegistry, buildEpicPrompt, createSandbox, epicAsTask, executeTask, isTransientError };
|
package/dist/execution/index.js
CHANGED
|
@@ -2,18 +2,22 @@ import {
|
|
|
2
2
|
ClaudeCodeAdapter,
|
|
3
3
|
CodexAdapter,
|
|
4
4
|
ExecutionEngine,
|
|
5
|
+
OpenCodeAdapter,
|
|
6
|
+
adapterRegistry,
|
|
5
7
|
buildEpicPrompt,
|
|
6
8
|
createSandbox,
|
|
7
9
|
epicAsTask,
|
|
8
10
|
executeTask,
|
|
9
11
|
isTransientError
|
|
10
|
-
} from "../chunk-
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
12
|
+
} from "../chunk-QPVNC7S4.js";
|
|
13
|
+
import "../chunk-7C7SC4TZ.js";
|
|
14
|
+
import "../chunk-6A37SKAJ.js";
|
|
13
15
|
export {
|
|
14
16
|
ClaudeCodeAdapter,
|
|
15
17
|
CodexAdapter,
|
|
16
18
|
ExecutionEngine,
|
|
19
|
+
OpenCodeAdapter,
|
|
20
|
+
adapterRegistry,
|
|
17
21
|
buildEpicPrompt,
|
|
18
22
|
createSandbox,
|
|
19
23
|
epicAsTask,
|
package/dist/repo/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as ResolvedRepo } from '../types-
|
|
2
|
-
export { a as RepoPermissions, b as ResolvedRepoGitState, c as ResolvedRepoMeta } from '../types-
|
|
1
|
+
import { R as ResolvedRepo } from '../types-3_IAAxh5.js';
|
|
2
|
+
export { a as RepoPermissions, b as ResolvedRepoGitState, c as ResolvedRepoMeta } from '../types-3_IAAxh5.js';
|
|
3
3
|
|
|
4
4
|
type RepoResolutionErrorCode = "INVALID_INPUT" | "NOT_FOUND" | "FORBIDDEN" | "ARCHIVED" | "UNKNOWN";
|
|
5
5
|
declare class RepoResolutionError extends Error {
|
package/dist/repo/index.js
CHANGED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# OAC Configuration Reference
|
|
2
|
+
|
|
3
|
+
> Auto-generated from the Zod schema in `src/core/config.ts`.
|
|
4
|
+
> Version: **2026.2.5**
|
|
5
|
+
|
|
6
|
+
OAC is configured via an `oac.config.ts` (or `.js` / `.json`) file at the project root. Use `defineConfig()` for type-safe authoring:
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { defineConfig } from "@open330/oac";
|
|
10
|
+
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
repos: ["facebook/react"],
|
|
13
|
+
budget: { totalTokens: 50_000 },
|
|
14
|
+
execution: { concurrency: 3, mode: "new-pr" },
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Environment variables can be interpolated with `${VAR_NAME}` syntax anywhere a string value is accepted.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## `repos`
|
|
23
|
+
|
|
24
|
+
Target repositories to contribute to.
|
|
25
|
+
|
|
26
|
+
| Property | Type | Default | Description |
|
|
27
|
+
|----------|------|---------|-------------|
|
|
28
|
+
| `repos` | `Array<string \| { name, branch? }>` | `[]` | List of repos. Each entry is either a GitHub slug (`"owner/repo"`) or an object with `name` (required) and `branch` (optional). |
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
repos: [
|
|
32
|
+
"facebook/react",
|
|
33
|
+
{ name: "vercel/next.js", branch: "canary" },
|
|
34
|
+
]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## `provider`
|
|
40
|
+
|
|
41
|
+
AI agent provider configuration.
|
|
42
|
+
|
|
43
|
+
| Property | Type | Default | Description |
|
|
44
|
+
|----------|------|---------|-------------|
|
|
45
|
+
| `provider.id` | `string` | `"claude-code"` | Provider identifier. |
|
|
46
|
+
| `provider.options` | `Record<string, unknown>` | `{}` | Provider-specific options passed through to the agent. |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## `budget`
|
|
51
|
+
|
|
52
|
+
Token budget controls.
|
|
53
|
+
|
|
54
|
+
| Property | Type | Default | Description |
|
|
55
|
+
|----------|------|---------|-------------|
|
|
56
|
+
| `budget.totalTokens` | `integer` | `100000` | Maximum tokens to spend across all tasks. Must be positive. |
|
|
57
|
+
| `budget.reservePercent` | `number` | `0.1` | Fraction of budget to reserve for retries/overhead (0–1). |
|
|
58
|
+
| `budget.estimationPadding` | `number` | `1.2` | Multiplier applied to token estimates for safety margin. Must be positive. |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## `discovery`
|
|
63
|
+
|
|
64
|
+
Task discovery and scanning settings.
|
|
65
|
+
|
|
66
|
+
### `discovery.scanners`
|
|
67
|
+
|
|
68
|
+
Toggle individual scanners on or off.
|
|
69
|
+
|
|
70
|
+
| Property | Type | Default | Description |
|
|
71
|
+
|----------|------|---------|-------------|
|
|
72
|
+
| `discovery.scanners.lint` | `boolean` | `true` | Scan for lint warnings and errors. |
|
|
73
|
+
| `discovery.scanners.todo` | `boolean` | `true` | Scan for TODO/FIXME/HACK comments. |
|
|
74
|
+
| `discovery.scanners.testGap` | `boolean` | `true` | Scan for files missing test coverage. |
|
|
75
|
+
| `discovery.scanners.deadCode` | `boolean` | `false` | Scan for dead/unused code. |
|
|
76
|
+
| `discovery.scanners.githubIssues` | `boolean` | `true` | Fetch open GitHub issues matching labels. |
|
|
77
|
+
|
|
78
|
+
### Other discovery options
|
|
79
|
+
|
|
80
|
+
| Property | Type | Default | Description |
|
|
81
|
+
|----------|------|---------|-------------|
|
|
82
|
+
| `discovery.issueLabels` | `string[]` | `["good-first-issue", "help-wanted", "bug"]` | GitHub issue labels to match. |
|
|
83
|
+
| `discovery.minPriority` | `integer` | `20` | Minimum priority score (0–100) for a task to be included. |
|
|
84
|
+
| `discovery.maxTasks` | `integer` | `50` | Maximum number of tasks to discover. Must be positive. |
|
|
85
|
+
| `discovery.customScanners` | `string[]` | `[]` | Paths to custom scanner modules. |
|
|
86
|
+
| `discovery.exclude` | `string[]` | `["node_modules", "dist", "build", ".git", "*.min.js", "vendor/"]` | Glob patterns to exclude from scanning. |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## `execution`
|
|
91
|
+
|
|
92
|
+
Task execution settings.
|
|
93
|
+
|
|
94
|
+
| Property | Type | Default | Description |
|
|
95
|
+
|----------|------|---------|-------------|
|
|
96
|
+
| `execution.concurrency` | `integer` | `2` | Max parallel task executions. Must be positive. |
|
|
97
|
+
| `execution.taskTimeout` | `integer` | `300` | Timeout per task in seconds. Must be positive. |
|
|
98
|
+
| `execution.maxRetries` | `integer` | `2` | Max retry attempts for failed tasks (0 = no retries). |
|
|
99
|
+
| `execution.mode` | `"new-pr" \| "update-pr" \| "direct-commit"` | `"new-pr"` | How to submit completed work. |
|
|
100
|
+
| `execution.branchPattern` | `string` | `"oac/{date}/{task}"` | Branch naming pattern. `{date}` and `{task}` are interpolated. |
|
|
101
|
+
|
|
102
|
+
### `execution.validation`
|
|
103
|
+
|
|
104
|
+
Post-execution validation checks.
|
|
105
|
+
|
|
106
|
+
| Property | Type | Default | Description |
|
|
107
|
+
|----------|------|---------|-------------|
|
|
108
|
+
| `execution.validation.lint` | `boolean` | `true` | Run linter after task completion. |
|
|
109
|
+
| `execution.validation.test` | `boolean` | `true` | Run tests after task completion. |
|
|
110
|
+
| `execution.validation.typeCheck` | `boolean` | `true` | Run type checker after task completion. |
|
|
111
|
+
| `execution.validation.maxDiffLines` | `integer` | `500` | Reject diffs exceeding this line count. Must be positive. |
|
|
112
|
+
|
|
113
|
+
### `execution.pr`
|
|
114
|
+
|
|
115
|
+
Pull request settings.
|
|
116
|
+
|
|
117
|
+
| Property | Type | Default | Description |
|
|
118
|
+
|----------|------|---------|-------------|
|
|
119
|
+
| `execution.pr.draft` | `boolean` | `false` | Create PRs as drafts. |
|
|
120
|
+
| `execution.pr.labels` | `string[]` | `["oac-contribution"]` | Labels to apply to created PRs. |
|
|
121
|
+
| `execution.pr.reviewers` | `string[]` | `[]` | GitHub usernames to request as reviewers. |
|
|
122
|
+
| `execution.pr.assignees` | `string[]` | `[]` | GitHub usernames to assign to the PR. |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## `completion`
|
|
127
|
+
|
|
128
|
+
Post-PR completion and monitoring.
|
|
129
|
+
|
|
130
|
+
### `completion.integrations.linear`
|
|
131
|
+
|
|
132
|
+
Linear issue tracker integration.
|
|
133
|
+
|
|
134
|
+
| Property | Type | Default | Description |
|
|
135
|
+
|----------|------|---------|-------------|
|
|
136
|
+
| `completion.integrations.linear.enabled` | `boolean` | `false` | Enable Linear integration. |
|
|
137
|
+
| `completion.integrations.linear.apiKey` | `string` | — | Linear API key. **Required** when `enabled: true`. Supports `${ENV_VAR}` syntax. |
|
|
138
|
+
| `completion.integrations.linear.teamId` | `string` | — | Linear team ID. **Required** when `enabled: true`. |
|
|
139
|
+
|
|
140
|
+
### `completion.integrations.jira`
|
|
141
|
+
|
|
142
|
+
Jira issue tracker integration.
|
|
143
|
+
|
|
144
|
+
| Property | Type | Default | Description |
|
|
145
|
+
|----------|------|---------|-------------|
|
|
146
|
+
| `completion.integrations.jira.enabled` | `boolean` | `false` | Enable Jira integration. |
|
|
147
|
+
| `completion.integrations.jira.baseUrl` | `string (URL)` | — | Jira instance URL. **Required** when `enabled: true`. |
|
|
148
|
+
| `completion.integrations.jira.email` | `string` | — | Jira account email. **Required** when `enabled: true`. |
|
|
149
|
+
| `completion.integrations.jira.apiToken` | `string` | — | Jira API token. **Required** when `enabled: true`. Supports `${ENV_VAR}` syntax. |
|
|
150
|
+
| `completion.integrations.jira.projectKey` | `string` | — | Jira project key. **Required** when `enabled: true`. |
|
|
151
|
+
|
|
152
|
+
### `completion.monitor`
|
|
153
|
+
|
|
154
|
+
PR monitoring settings.
|
|
155
|
+
|
|
156
|
+
| Property | Type | Default | Description |
|
|
157
|
+
|----------|------|---------|-------------|
|
|
158
|
+
| `completion.monitor.enabled` | `boolean` | `false` | Enable post-PR monitoring. |
|
|
159
|
+
| `completion.monitor.pollInterval` | `integer` | `300` | Seconds between status checks. Must be positive. |
|
|
160
|
+
| `completion.monitor.autoRespondToReviews` | `boolean` | `false` | Automatically respond to review comments. |
|
|
161
|
+
| `completion.monitor.autoDeleteBranch` | `boolean` | `true` | Delete branch after PR is merged. |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## `tracking`
|
|
166
|
+
|
|
167
|
+
Local state tracking.
|
|
168
|
+
|
|
169
|
+
| Property | Type | Default | Description |
|
|
170
|
+
|----------|------|---------|-------------|
|
|
171
|
+
| `tracking.directory` | `string` | `".oac"` | Directory for local OAC state files. |
|
|
172
|
+
| `tracking.autoCommit` | `boolean` | `false` | Auto-commit tracking changes to git. |
|
|
173
|
+
| `tracking.gitTracked` | `boolean` | `true` | Include tracking directory in git. |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## `dashboard`
|
|
178
|
+
|
|
179
|
+
Local dashboard server.
|
|
180
|
+
|
|
181
|
+
| Property | Type | Default | Description |
|
|
182
|
+
|----------|------|---------|-------------|
|
|
183
|
+
| `dashboard.port` | `integer` | `3141` | Port for the dashboard server (1–65535). |
|
|
184
|
+
| `dashboard.openBrowser` | `boolean` | `true` | Automatically open the dashboard in a browser. |
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## `analyze`
|
|
189
|
+
|
|
190
|
+
Context analysis settings for `oac analyze` / auto-analysis before `oac run`.
|
|
191
|
+
|
|
192
|
+
| Property | Type | Default | Description |
|
|
193
|
+
|----------|------|---------|-------------|
|
|
194
|
+
| `analyze.autoAnalyze` | `boolean` | `true` | Auto-run analysis before `oac run` if context is stale or missing. |
|
|
195
|
+
| `analyze.staleAfterMs` | `integer` | `86400000` | Max age in milliseconds before context is considered stale (default: 24 hours). |
|
|
196
|
+
| `analyze.contextDir` | `string` | `".oac/context"` | Directory for persisted analysis context, relative to repo root. |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Environment Variable Interpolation
|
|
201
|
+
|
|
202
|
+
Any string value in the config supports `${VAR_NAME}` interpolation:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
export default defineConfig({
|
|
206
|
+
completion: {
|
|
207
|
+
integrations: {
|
|
208
|
+
linear: {
|
|
209
|
+
enabled: true,
|
|
210
|
+
apiKey: "${LINEAR_API_KEY}",
|
|
211
|
+
teamId: "${LINEAR_TEAM_ID}",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
If a referenced variable is not set, OAC throws a `CONFIG_SECRET_MISSING` error with the variable name and config path.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Minimal Example
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { defineConfig } from "@open330/oac";
|
|
226
|
+
|
|
227
|
+
export default defineConfig({
|
|
228
|
+
repos: ["my-org/my-repo"],
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
All other options use sensible defaults. See individual sections above for default values.
|
|
233
|
+
|
|
234
|
+
## Full Example
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { defineConfig } from "@open330/oac";
|
|
238
|
+
|
|
239
|
+
export default defineConfig({
|
|
240
|
+
repos: [
|
|
241
|
+
"facebook/react",
|
|
242
|
+
{ name: "vercel/next.js", branch: "canary" },
|
|
243
|
+
],
|
|
244
|
+
provider: { id: "claude-code" },
|
|
245
|
+
budget: {
|
|
246
|
+
totalTokens: 200_000,
|
|
247
|
+
reservePercent: 0.15,
|
|
248
|
+
estimationPadding: 1.3,
|
|
249
|
+
},
|
|
250
|
+
discovery: {
|
|
251
|
+
scanners: { lint: true, todo: true, testGap: true, deadCode: true, githubIssues: true },
|
|
252
|
+
issueLabels: ["good-first-issue", "help-wanted"],
|
|
253
|
+
minPriority: 30,
|
|
254
|
+
maxTasks: 25,
|
|
255
|
+
exclude: ["node_modules", "dist", "vendor/", "**/*.generated.ts"],
|
|
256
|
+
},
|
|
257
|
+
execution: {
|
|
258
|
+
concurrency: 4,
|
|
259
|
+
taskTimeout: 600,
|
|
260
|
+
maxRetries: 1,
|
|
261
|
+
mode: "new-pr",
|
|
262
|
+
branchPattern: "oac/{date}/{task}",
|
|
263
|
+
validation: { lint: true, test: true, typeCheck: true, maxDiffLines: 800 },
|
|
264
|
+
pr: { draft: true, labels: ["oac-contribution", "automated"], reviewers: ["maintainer"] },
|
|
265
|
+
},
|
|
266
|
+
tracking: { directory: ".oac", autoCommit: true },
|
|
267
|
+
dashboard: { port: 3141 },
|
|
268
|
+
analyze: { autoAnalyze: true, staleAfterMs: 43_200_000 },
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# OAC Multi-Agent Support Technical Specification
|
|
2
|
+
|
|
3
|
+
## 1. Objective
|
|
4
|
+
Add production-grade multi-agent execution to OAC with these outcomes:
|
|
5
|
+
1. Claude Code adapter runs real `claude` CLI subprocesses (non-interactive, stream parsing, abort support).
|
|
6
|
+
2. OpenCode adapter is added and runs real `opencode` CLI subprocesses.
|
|
7
|
+
3. Tasks are routed by complexity:
|
|
8
|
+
- `trivial`, `simple` -> `codex`
|
|
9
|
+
- `moderate`, `complex` -> `claude-code`
|
|
10
|
+
4. `oac run --provider` accepts comma-separated providers (example: `codex,claude-code`).
|
|
11
|
+
5. Agent health checks and automatic fallback retries are applied when an agent fails.
|
|
12
|
+
|
|
13
|
+
## 2. Current State Summary
|
|
14
|
+
1. `CodexAdapter` is implemented and functional in `packages/execution/src/agents/codex.adapter.ts`.
|
|
15
|
+
2. `ClaudeCodeAdapter` exists but is not integrated into `oac run` execution path.
|
|
16
|
+
3. `ExecutionEngine` currently round-robins agents and has no routing-by-complexity or provider health state.
|
|
17
|
+
4. `oac run` currently executes only Codex (or simulated execution) and accepts a single `--provider` string.
|
|
18
|
+
5. Provider IDs are inconsistent (`codex` vs `codex-cli`) across packages.
|
|
19
|
+
|
|
20
|
+
## 3. Design Decisions
|
|
21
|
+
1. Canonical runtime provider IDs for execution will be:
|
|
22
|
+
- `codex`
|
|
23
|
+
- `claude-code`
|
|
24
|
+
- `opencode`
|
|
25
|
+
2. Legacy alias `codex-cli` will remain accepted at CLI/config boundaries and normalized to `codex`.
|
|
26
|
+
3. Routing happens per task, using complexity-first preference and user-selected provider availability.
|
|
27
|
+
4. Fallback is provider-level: if provider A fails, retry task on provider B before final failure.
|
|
28
|
+
5. Health checking is run-level with lightweight in-memory state, not persisted.
|
|
29
|
+
|
|
30
|
+
## 4. External CLI Contracts
|
|
31
|
+
|
|
32
|
+
### 4.1 Claude Code subprocess contract
|
|
33
|
+
Use:
|
|
34
|
+
```bash
|
|
35
|
+
claude -p "<prompt>" --output-format stream-json --verbose
|
|
36
|
+
```
|
|
37
|
+
Process options:
|
|
38
|
+
- `cwd = params.workingDirectory`
|
|
39
|
+
- `env` includes inherited env + `params.env` + `OAC_TOKEN_BUDGET` + `OAC_ALLOW_COMMITS`
|
|
40
|
+
- `timeout = params.timeoutMs`
|
|
41
|
+
|
|
42
|
+
Parsing:
|
|
43
|
+
- Parse stream JSON lines when available.
|
|
44
|
+
- Emit `output`, `tokens`, `file_edit`, `tool_use`, and `error` events.
|
|
45
|
+
- Fall back to regex parsing for plain-text lines.
|
|
46
|
+
|
|
47
|
+
### 4.2 OpenCode subprocess contract
|
|
48
|
+
Use:
|
|
49
|
+
```bash
|
|
50
|
+
opencode run --format json "<prompt>"
|
|
51
|
+
```
|
|
52
|
+
Process options:
|
|
53
|
+
- `cwd = params.workingDirectory`
|
|
54
|
+
- `env` includes inherited env + `params.env` + `OAC_TOKEN_BUDGET` + `OAC_ALLOW_COMMITS`
|
|
55
|
+
- `timeout = params.timeoutMs`
|
|
56
|
+
|
|
57
|
+
Parsing:
|
|
58
|
+
- Primary: JSON event lines from `--format json`.
|
|
59
|
+
- Fallback: plain text line heuristics for token/file/error events.
|
|
60
|
+
|
|
61
|
+
## 5. File-by-File Changes
|
|
62
|
+
|
|
63
|
+
### 5.1 `packages/core/src/types.ts`
|
|
64
|
+
1. Update provider type for canonical + compatibility:
|
|
65
|
+
```ts
|
|
66
|
+
export type AgentProviderId =
|
|
67
|
+
| "claude-code"
|
|
68
|
+
| "codex"
|
|
69
|
+
| "codex-cli"
|
|
70
|
+
| "opencode"
|
|
71
|
+
| (string & {});
|
|
72
|
+
```
|
|
73
|
+
2. No runtime behavior change in core.
|
|
74
|
+
|
|
75
|
+
### 5.2 `packages/execution/src/agents/claude-code.adapter.ts`
|
|
76
|
+
1. Keep `id = "claude-code"`.
|
|
77
|
+
2. Ensure `execute()` uses `claude` CLI subprocess and structured output flags.
|
|
78
|
+
3. Keep error normalization pattern aligned with Codex adapter:
|
|
79
|
+
- timeout -> `AGENT_TIMEOUT`
|
|
80
|
+
- OOM -> `AGENT_OOM`
|
|
81
|
+
- network -> `NETWORK_ERROR`
|
|
82
|
+
- default -> `AGENT_EXECUTION_FAILED`
|
|
83
|
+
4. Keep `checkAvailability()` as `claude --version` with non-throwing availability object.
|
|
84
|
+
5. Keep `abort()` SIGTERM then SIGKILL escalation with timer.
|
|
85
|
+
|
|
86
|
+
### 5.3 `packages/execution/src/agents/opencode.adapter.ts` (new)
|
|
87
|
+
1. Add new class implementing `AgentProvider`:
|
|
88
|
+
```ts
|
|
89
|
+
export class OpenCodeAdapter implements AgentProvider {
|
|
90
|
+
public readonly id: AgentProviderId = "opencode";
|
|
91
|
+
public readonly name = "OpenCode CLI";
|
|
92
|
+
|
|
93
|
+
public async checkAvailability(): Promise<AgentAvailability>;
|
|
94
|
+
public execute(params: AgentExecuteParams): AgentExecution;
|
|
95
|
+
public async estimateTokens(params: TokenEstimateParams): Promise<TokenEstimate>;
|
|
96
|
+
public async abort(executionId: string): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
2. Implementation mirrors Codex/Claude adapter behavior:
|
|
100
|
+
- subprocess lifecycle map (`runningExecutions`)
|
|
101
|
+
- async event queue streaming
|
|
102
|
+
- structured + heuristic parsing
|
|
103
|
+
- normalized `AgentResult`
|
|
104
|
+
3. `checkAvailability()` command: `opencode --version`.
|
|
105
|
+
4. `execute()` command: `opencode run --format json <prompt>`.
|
|
106
|
+
|
|
107
|
+
### 5.4 `packages/execution/src/index.ts`
|
|
108
|
+
1. Export new adapter:
|
|
109
|
+
```ts
|
|
110
|
+
export * from "./agents/opencode.adapter.js";
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 5.5 `packages/execution/src/engine.ts`
|
|
114
|
+
Add routing + health + fallback behavior.
|
|
115
|
+
|
|
116
|
+
#### 5.5.1 Type updates
|
|
117
|
+
1. Extend `Job`:
|
|
118
|
+
```ts
|
|
119
|
+
attemptedProviders: AgentProviderId[];
|
|
120
|
+
preferredProviders: AgentProviderId[];
|
|
121
|
+
```
|
|
122
|
+
2. Add internal health state:
|
|
123
|
+
```ts
|
|
124
|
+
interface ProviderHealthState {
|
|
125
|
+
providerId: AgentProviderId;
|
|
126
|
+
available: boolean;
|
|
127
|
+
version?: string;
|
|
128
|
+
lastError?: string;
|
|
129
|
+
consecutiveFailures: number;
|
|
130
|
+
checkedAt: number;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### 5.5.2 New private members
|
|
135
|
+
```ts
|
|
136
|
+
private readonly providerById: Map<AgentProviderId, AgentProvider>;
|
|
137
|
+
private readonly providerHealth: Map<AgentProviderId, ProviderHealthState>;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### 5.5.3 New private methods
|
|
141
|
+
```ts
|
|
142
|
+
private async initializeProviderHealth(): Promise<void>;
|
|
143
|
+
private computePreferredProviders(task: Task): AgentProviderId[];
|
|
144
|
+
private selectAgentForJob(job: Job): AgentProvider;
|
|
145
|
+
private recordProviderSuccess(providerId: AgentProviderId): void;
|
|
146
|
+
private recordProviderFailure(providerId: AgentProviderId, error: OacError): void;
|
|
147
|
+
private canFallback(job: Job): boolean;
|
|
148
|
+
private scheduleFallback(job: Job): boolean;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### 5.5.4 Routing logic
|
|
152
|
+
1. `computePreferredProviders(task)` returns ordered list:
|
|
153
|
+
- `trivial/simple`: `["codex", "claude-code", "opencode"]`
|
|
154
|
+
- `moderate/complex`: `["claude-code", "codex", "opencode"]`
|
|
155
|
+
2. Order is filtered by providers passed into engine constructor and current health.
|
|
156
|
+
3. On each job attempt, `selectAgentForJob(job)` picks first provider not already in `job.attemptedProviders`.
|
|
157
|
+
|
|
158
|
+
#### 5.5.5 Fallback logic
|
|
159
|
+
1. If execution fails and any untried healthy provider remains, schedule immediate retry on fallback provider.
|
|
160
|
+
2. If no untried provider remains, apply existing transient retry rules.
|
|
161
|
+
3. Mark provider health failures on:
|
|
162
|
+
- `AGENT_TIMEOUT`
|
|
163
|
+
- `AGENT_OOM`
|
|
164
|
+
- `AGENT_RATE_LIMITED`
|
|
165
|
+
- `NETWORK_ERROR`
|
|
166
|
+
- `AGENT_EXECUTION_FAILED`
|
|
167
|
+
4. Reset consecutive failures after a successful task on that provider.
|
|
168
|
+
|
|
169
|
+
### 5.6 `packages/cli/src/commands/run.ts`
|
|
170
|
+
|
|
171
|
+
#### 5.6.1 Option parsing
|
|
172
|
+
1. Keep flag name, change semantics:
|
|
173
|
+
```ts
|
|
174
|
+
.option("--provider <ids>", "Comma-separated provider ids (e.g. codex,claude-code)")
|
|
175
|
+
```
|
|
176
|
+
2. Replace single-provider resolver:
|
|
177
|
+
```ts
|
|
178
|
+
function resolveProviderIds(providerOption: string | undefined, config: OacConfig | null): AgentProviderId[];
|
|
179
|
+
function parseProviderIds(input: string): AgentProviderId[];
|
|
180
|
+
function normalizeProviderId(input: string): AgentProviderId;
|
|
181
|
+
```
|
|
182
|
+
3. Supported user inputs:
|
|
183
|
+
- `codex`, `codex-cli` -> `codex`
|
|
184
|
+
- `claude-code`
|
|
185
|
+
- `opencode`
|
|
186
|
+
4. Deduplicate providers while preserving order.
|
|
187
|
+
5. Validation errors are user-facing, fail fast.
|
|
188
|
+
|
|
189
|
+
#### 5.6.2 Adapter bootstrap
|
|
190
|
+
1. Add provider factory:
|
|
191
|
+
```ts
|
|
192
|
+
function createRequestedAdapters(providerIds: AgentProviderId[]): AgentProvider[];
|
|
193
|
+
```
|
|
194
|
+
2. Instantiate only requested adapters.
|
|
195
|
+
3. Run `checkAvailability()` for all requested providers at start.
|
|
196
|
+
|
|
197
|
+
#### 5.6.3 Health-aware execution setup
|
|
198
|
+
1. Build `healthyProviders` list from availability checks.
|
|
199
|
+
2. Fail run if none are healthy.
|
|
200
|
+
3. If subset healthy, continue and print warning listing disabled providers.
|
|
201
|
+
|
|
202
|
+
#### 5.6.4 Routing during execution stage
|
|
203
|
+
1. Replace Codex-only path with provider-routed path:
|
|
204
|
+
```ts
|
|
205
|
+
async function executeWithRouting(input: {
|
|
206
|
+
task: Task;
|
|
207
|
+
estimate: TokenEstimate;
|
|
208
|
+
providerIds: AgentProviderId[];
|
|
209
|
+
adapters: Map<AgentProviderId, AgentProvider>;
|
|
210
|
+
repoPath: string;
|
|
211
|
+
baseBranch: string;
|
|
212
|
+
timeoutSeconds: number;
|
|
213
|
+
}): Promise<{ execution: ExecutionOutcome; sandbox: SandboxInfo }>;
|
|
214
|
+
```
|
|
215
|
+
2. `executeWithRouting()` chooses preferred provider by task complexity and fallbacks if needed.
|
|
216
|
+
3. Preserve existing sandbox + commit + PR flow.
|
|
217
|
+
|
|
218
|
+
#### 5.6.5 Token estimation changes
|
|
219
|
+
1. Replace `estimateTaskMap(tasks, providerId)` with provider-aware selection:
|
|
220
|
+
```ts
|
|
221
|
+
async function estimateTaskMap(
|
|
222
|
+
tasks: Task[],
|
|
223
|
+
providerSelector: (task: Task) => AgentProviderId,
|
|
224
|
+
): Promise<Map<string, TokenEstimate>>;
|
|
225
|
+
```
|
|
226
|
+
2. For each task, estimate using preferred provider selected by complexity and provider availability.
|
|
227
|
+
|
|
228
|
+
#### 5.6.6 CLI UX changes
|
|
229
|
+
1. Startup output includes:
|
|
230
|
+
- requested providers
|
|
231
|
+
- availability status per provider
|
|
232
|
+
- routing policy summary
|
|
233
|
+
2. Per-task verbose output includes selected provider.
|
|
234
|
+
3. On fallback, log one-line message:
|
|
235
|
+
- `Task <id>: <failed-provider> failed (<code>), retrying with <fallback-provider>`
|
|
236
|
+
4. Summary output changes:
|
|
237
|
+
- `provider` -> comma-joined provider list for text mode
|
|
238
|
+
- JSON summary adds `providers: string[]`
|
|
239
|
+
|
|
240
|
+
### 5.7 `packages/budget/src/estimator.ts`
|
|
241
|
+
1. Update local provider type alias to include `codex` and legacy alias.
|
|
242
|
+
2. Keep counter selection:
|
|
243
|
+
- `claude-code` -> Claude counter
|
|
244
|
+
- `codex`, `codex-cli`, `opencode` -> Codex counter (initially)
|
|
245
|
+
|
|
246
|
+
## 6. Error Handling Patterns
|
|
247
|
+
|
|
248
|
+
### 6.1 Adapter-level patterns
|
|
249
|
+
1. `checkAvailability()` never throws; returns `{ available: false, error }`.
|
|
250
|
+
2. `execute()` may reject with normalized `OacError` only.
|
|
251
|
+
3. Non-zero exit code without throw returns `success: false` result with extracted error text.
|
|
252
|
+
4. Timeouts map to `AGENT_TIMEOUT`.
|
|
253
|
+
|
|
254
|
+
### 6.2 Engine/CLI fallback patterns
|
|
255
|
+
1. Retry with alternate provider first when available.
|
|
256
|
+
2. Use backoff only when cycling retry attempts after fallback options are exhausted.
|
|
257
|
+
3. Fail final job with last normalized `OacError` and include context:
|
|
258
|
+
- `attempt`
|
|
259
|
+
- `providersTried`
|
|
260
|
+
- `taskId`
|
|
261
|
+
- `jobId`
|
|
262
|
+
|
|
263
|
+
### 6.3 Input validation errors (`oac run`)
|
|
264
|
+
1. Unknown provider ID -> throw with allowed values list.
|
|
265
|
+
2. Empty provider list after parsing -> throw.
|
|
266
|
+
3. Duplicate IDs are silently deduplicated.
|
|
267
|
+
|
|
268
|
+
## 7. Test Plan
|
|
269
|
+
|
|
270
|
+
### 7.1 New tests
|
|
271
|
+
1. `packages/execution/tests/claude-code-adapter.test.ts`
|
|
272
|
+
- availability success/failure
|
|
273
|
+
- subprocess args
|
|
274
|
+
- stream parsing
|
|
275
|
+
- timeout normalization
|
|
276
|
+
- abort behavior
|
|
277
|
+
2. `packages/execution/tests/opencode-adapter.test.ts`
|
|
278
|
+
- same coverage shape as Codex/Claude adapters
|
|
279
|
+
|
|
280
|
+
### 7.2 Engine tests update
|
|
281
|
+
File: `packages/execution/tests/engine.test.ts`
|
|
282
|
+
1. Routes trivial/simple tasks to Codex first.
|
|
283
|
+
2. Routes moderate/complex tasks to Claude first.
|
|
284
|
+
3. Falls back to alternate provider on provider failure.
|
|
285
|
+
4. Marks unavailable providers out of routing after failed health checks.
|
|
286
|
+
|
|
287
|
+
### 7.3 CLI tests
|
|
288
|
+
Add: `packages/cli/tests/run.test.ts`
|
|
289
|
+
1. Parses `--provider codex,claude-code` correctly.
|
|
290
|
+
2. Normalizes `codex-cli` to `codex`.
|
|
291
|
+
3. Rejects unknown providers.
|
|
292
|
+
4. Uses available provider when one is unhealthy.
|
|
293
|
+
5. Emits fallback message when first provider fails.
|
|
294
|
+
|
|
295
|
+
## 8. Migration and Compatibility
|
|
296
|
+
1. Backward compatibility:
|
|
297
|
+
- `codex-cli` accepted and normalized to `codex`.
|
|
298
|
+
2. No breaking change to existing single-provider usage:
|
|
299
|
+
- `--provider claude-code` still valid.
|
|
300
|
+
3. If config has `provider.id = "codex-cli"`, runtime behaves as `codex`.
|
|
301
|
+
|
|
302
|
+
## 9. Implementation Order
|
|
303
|
+
1. Add provider normalization and ID compatibility (`core`, `budget`, `cli`).
|
|
304
|
+
2. Implement `OpenCodeAdapter` and finalize Claude subprocess flags/parsing.
|
|
305
|
+
3. Add routing/fallback internals to `ExecutionEngine`.
|
|
306
|
+
4. Update `oac run` to multi-provider parse, health check, routing, and fallback.
|
|
307
|
+
5. Add tests and adjust README command examples.
|
|
308
|
+
|
|
309
|
+
## 10. Out of Scope
|
|
310
|
+
1. Dashboard multi-agent wiring (`packages/dashboard`) in this change set.
|
|
311
|
+
2. Persisted cross-run health state.
|
|
312
|
+
3. Cost-based dynamic routing (future enhancement).
|