task-while 0.0.1 → 0.0.3
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 +32 -34
- package/package.json +2 -2
- package/src/adapters/fs/harness-store.ts +84 -0
- package/src/agents/claude.ts +159 -9
- package/src/agents/codex.ts +68 -4
- package/src/agents/event-log.ts +195 -0
- package/src/batch/discovery.ts +1 -1
- package/src/batch/provider.ts +9 -0
- package/src/commands/batch.ts +69 -165
- package/src/commands/run-branch-helpers.ts +81 -0
- package/src/commands/run-providers.ts +77 -0
- package/src/commands/run.ts +117 -225
- package/src/core/create-runtime-ports.ts +118 -0
- package/src/core/runtime.ts +15 -36
- package/src/harness/in-memory-store.ts +45 -0
- package/src/harness/kernel.ts +226 -0
- package/src/harness/state.ts +47 -0
- package/src/harness/store.ts +26 -0
- package/src/harness/workflow-builders.ts +87 -0
- package/src/harness/workflow-program.ts +86 -0
- package/src/ports/agent.ts +17 -0
- package/src/ports/code-host.ts +23 -0
- package/src/programs/batch.ts +139 -0
- package/src/programs/run-direct.ts +209 -0
- package/src/programs/run-pr-transitions.ts +81 -0
- package/src/programs/run-pr.ts +290 -0
- package/src/programs/shared-steps.ts +252 -0
- package/src/schedulers/scheduler.ts +208 -0
- package/src/session/session.ts +127 -0
- package/src/workflow/config.ts +15 -0
- package/src/core/engine-helpers.ts +0 -114
- package/src/core/engine-outcomes.ts +0 -166
- package/src/core/engine.ts +0 -223
- package/src/core/orchestrator-helpers.ts +0 -52
- package/src/core/orchestrator-integrate-resume.ts +0 -149
- package/src/core/orchestrator-review-resume.ts +0 -228
- package/src/core/orchestrator-task-attempt.ts +0 -257
- package/src/core/orchestrator.ts +0 -99
- package/src/runtime/fs-runtime.ts +0 -209
- package/src/workflow/direct-preset.ts +0 -44
- package/src/workflow/preset.ts +0 -86
- package/src/workflow/pull-request-preset.ts +0 -312
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createClaudeProvider } from '../agents/claude'
|
|
2
|
+
import { createCodexProvider } from '../agents/codex'
|
|
3
|
+
import {
|
|
4
|
+
createClaudeEventHandler,
|
|
5
|
+
createCodexEventHandler,
|
|
6
|
+
} from '../agents/event-log'
|
|
7
|
+
import { createCodexRemoteReviewerProvider } from '../workflow/remote-reviewer'
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ImplementerProvider,
|
|
11
|
+
RemoteReviewerProvider,
|
|
12
|
+
ReviewerProvider,
|
|
13
|
+
} from '../agents/types'
|
|
14
|
+
import type { WorkspaceContext } from '../types'
|
|
15
|
+
import type { WorkflowProvider, WorkflowRoleConfig } from '../workflow/config'
|
|
16
|
+
|
|
17
|
+
export type ProviderResolver = (
|
|
18
|
+
role: WorkflowRoleConfig,
|
|
19
|
+
) => ImplementerProvider & ReviewerProvider
|
|
20
|
+
|
|
21
|
+
export type RemoteReviewerResolver = (
|
|
22
|
+
providerName: WorkflowProvider,
|
|
23
|
+
) => RemoteReviewerProvider
|
|
24
|
+
|
|
25
|
+
export function createProviderResolver(
|
|
26
|
+
context: WorkspaceContext,
|
|
27
|
+
verbose: boolean | undefined,
|
|
28
|
+
): ProviderResolver {
|
|
29
|
+
const cache = new Map<
|
|
30
|
+
WorkflowProvider,
|
|
31
|
+
ImplementerProvider & ReviewerProvider
|
|
32
|
+
>()
|
|
33
|
+
return (role: WorkflowRoleConfig) => {
|
|
34
|
+
const cached = cache.get(role.provider)
|
|
35
|
+
if (cached) {
|
|
36
|
+
return cached
|
|
37
|
+
}
|
|
38
|
+
let provider: ImplementerProvider & ReviewerProvider
|
|
39
|
+
if (role.provider === 'claude') {
|
|
40
|
+
const onEvent = createClaudeEventHandler(verbose)
|
|
41
|
+
provider = createClaudeProvider({
|
|
42
|
+
...(role.effort ? { effort: role.effort } : {}),
|
|
43
|
+
...(role.model ? { model: role.model } : {}),
|
|
44
|
+
workspaceRoot: context.workspaceRoot,
|
|
45
|
+
...(onEvent ? { onEvent } : {}),
|
|
46
|
+
})
|
|
47
|
+
} else {
|
|
48
|
+
const onEvent = createCodexEventHandler(verbose)
|
|
49
|
+
provider = createCodexProvider({
|
|
50
|
+
...(role.effort ? { effort: role.effort } : {}),
|
|
51
|
+
...(role.model ? { model: role.model } : {}),
|
|
52
|
+
workspaceRoot: context.workspaceRoot,
|
|
53
|
+
...(onEvent ? { onEvent } : {}),
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
cache.set(role.provider, provider)
|
|
57
|
+
return provider
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createRemoteReviewerResolver(): RemoteReviewerResolver {
|
|
62
|
+
const cache = new Map<WorkflowProvider, RemoteReviewerProvider>()
|
|
63
|
+
return (providerName: WorkflowProvider) => {
|
|
64
|
+
const cached = cache.get(providerName)
|
|
65
|
+
if (cached) {
|
|
66
|
+
return cached
|
|
67
|
+
}
|
|
68
|
+
if (providerName === 'claude') {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'claude remote reviewer is not implemented in pull-request mode',
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
const provider = createCodexRemoteReviewerProvider()
|
|
74
|
+
cache.set(providerName, provider)
|
|
75
|
+
return provider
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/commands/run.ts
CHANGED
|
@@ -1,37 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createClaudeProvider,
|
|
3
|
-
type ClaudeAgentEvent,
|
|
4
|
-
type ClaudeAgentEventHandler,
|
|
5
|
-
} from '../agents/claude'
|
|
6
|
-
import {
|
|
7
|
-
createCodexProvider,
|
|
8
|
-
type CodexThreadEvent,
|
|
9
|
-
type CodexThreadEventHandler,
|
|
10
|
-
} from '../agents/codex'
|
|
1
|
+
import { createFsHarnessStore } from '../adapters/fs/harness-store'
|
|
11
2
|
import { providerOptionsEqual } from '../agents/provider-options'
|
|
12
|
-
import {
|
|
3
|
+
import { createRuntimePorts } from '../core/create-runtime-ports'
|
|
13
4
|
import { buildTaskTopology } from '../core/task-topology'
|
|
14
|
-
import {
|
|
5
|
+
import { runKernel } from '../harness/kernel'
|
|
6
|
+
import { createRunDirectProgram } from '../programs/run-direct'
|
|
7
|
+
import { createRunPrProgram } from '../programs/run-pr'
|
|
8
|
+
import { createRuntimePaths } from '../runtime/path-layout'
|
|
9
|
+
import { createRunGraphScheduler } from '../schedulers/scheduler'
|
|
10
|
+
import { runSession } from '../session/session'
|
|
15
11
|
import { openTaskSource } from '../task-sources/registry'
|
|
16
|
-
import {
|
|
17
|
-
loadWorkflowConfig,
|
|
18
|
-
type WorkflowConfig,
|
|
19
|
-
type WorkflowProvider,
|
|
20
|
-
type WorkflowRoleConfig,
|
|
21
|
-
} from '../workflow/config'
|
|
22
|
-
import {
|
|
23
|
-
createDirectWorkflowPreset,
|
|
24
|
-
createPullRequestWorkflowPreset,
|
|
25
|
-
type WorkflowRuntime,
|
|
26
|
-
} from '../workflow/preset'
|
|
27
|
-
import { createCodexRemoteReviewerProvider } from '../workflow/remote-reviewer'
|
|
12
|
+
import { loadWorkflowConfig, type WorkflowConfig } from '../workflow/config'
|
|
28
13
|
|
|
29
|
-
import type {
|
|
30
|
-
ImplementerProvider,
|
|
31
|
-
RemoteReviewerProvider,
|
|
32
|
-
ReviewerProvider,
|
|
33
|
-
WorkflowRoleProviders,
|
|
34
|
-
} from '../agents/types'
|
|
35
14
|
import type { WorkspaceContext } from '../types'
|
|
36
15
|
|
|
37
16
|
export interface RunCommandOptions {
|
|
@@ -40,158 +19,56 @@ export interface RunCommandOptions {
|
|
|
40
19
|
verbose?: boolean
|
|
41
20
|
}
|
|
42
21
|
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
export interface ResolveWorkflowRuntimeInput {
|
|
52
|
-
config: WorkflowConfig
|
|
53
|
-
context: WorkspaceContext
|
|
54
|
-
options: RunCommandOptions
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export type ProviderResolver = (
|
|
58
|
-
role: WorkflowRoleConfig,
|
|
59
|
-
) => ImplementerProvider & ReviewerProvider
|
|
60
|
-
|
|
61
|
-
export type RemoteReviewerResolver = (
|
|
62
|
-
providerName: WorkflowProvider,
|
|
63
|
-
) => RemoteReviewerProvider
|
|
64
|
-
|
|
65
|
-
function writeCodexEvent(event: CodexThreadEvent) {
|
|
66
|
-
const itemType =
|
|
67
|
-
event.type === 'item.completed' ||
|
|
68
|
-
event.type === 'item.started' ||
|
|
69
|
-
event.type === 'item.updated'
|
|
70
|
-
? event.item.type
|
|
71
|
-
: null
|
|
72
|
-
process.stderr.write(
|
|
73
|
-
`[codex] ${event.type}${itemType ? ` ${itemType}` : ''}\n`,
|
|
74
|
-
)
|
|
75
|
-
if (
|
|
76
|
-
event.type === 'item.completed' &&
|
|
77
|
-
event.item.type === 'agent_message' &&
|
|
78
|
-
event.item.text?.trim()
|
|
79
|
-
) {
|
|
80
|
-
process.stderr.write(`[codex] message ${event.item.text.trim()}\n`)
|
|
22
|
+
export interface RunCommandResult {
|
|
23
|
+
summary: {
|
|
24
|
+
blockedTasks: number
|
|
25
|
+
completedTasks: number
|
|
26
|
+
finalStatus: 'blocked' | 'completed' | 'in_progress' | 'replan_required'
|
|
27
|
+
replanTasks: number
|
|
28
|
+
totalTasks: number
|
|
81
29
|
}
|
|
82
|
-
if (event.type === 'error') {
|
|
83
|
-
process.stderr.write(`[codex] error ${event.message}\n`)
|
|
84
|
-
}
|
|
85
|
-
if (event.type === 'turn.failed') {
|
|
86
|
-
process.stderr.write(`[codex] error ${event.error.message}\n`)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function createCodexEventHandler(
|
|
91
|
-
verbose: boolean | undefined,
|
|
92
|
-
): CodexThreadEventHandler | undefined {
|
|
93
|
-
if (!verbose) {
|
|
94
|
-
return undefined
|
|
95
|
-
}
|
|
96
|
-
return writeCodexEvent
|
|
97
30
|
}
|
|
98
31
|
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
32
|
+
export async function runCommand(
|
|
33
|
+
context: WorkspaceContext,
|
|
34
|
+
options: RunCommandOptions = {},
|
|
35
|
+
): Promise<RunCommandResult> {
|
|
36
|
+
const config =
|
|
37
|
+
options.config ?? (await loadWorkflowConfig(context.workspaceRoot))
|
|
103
38
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
return writeClaudeEvent
|
|
111
|
-
}
|
|
39
|
+
const taskSource = await openTaskSource(config.task.source, {
|
|
40
|
+
featureDir: context.featureDir,
|
|
41
|
+
featureId: context.featureId,
|
|
42
|
+
workspaceRoot: context.workspaceRoot,
|
|
43
|
+
})
|
|
112
44
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
ImplementerProvider & ReviewerProvider
|
|
120
|
-
>()
|
|
121
|
-
return (role: WorkflowRoleConfig) => {
|
|
122
|
-
const cached = cache.get(role.provider)
|
|
123
|
-
if (cached) {
|
|
124
|
-
return cached
|
|
125
|
-
}
|
|
126
|
-
let provider: ImplementerProvider & ReviewerProvider
|
|
127
|
-
if (role.provider === 'claude') {
|
|
128
|
-
const onEvent = createClaudeEventHandler(verbose)
|
|
129
|
-
provider = createClaudeProvider({
|
|
130
|
-
...(role.effort ? { effort: role.effort } : {}),
|
|
131
|
-
...(role.model ? { model: role.model } : {}),
|
|
132
|
-
workspaceRoot: context.workspaceRoot,
|
|
133
|
-
...(onEvent ? { onEvent } : {}),
|
|
134
|
-
})
|
|
135
|
-
} else {
|
|
136
|
-
const onEvent = createCodexEventHandler(verbose)
|
|
137
|
-
provider = createCodexProvider({
|
|
138
|
-
...(role.effort ? { effort: role.effort } : {}),
|
|
139
|
-
...(role.model ? { model: role.model } : {}),
|
|
140
|
-
workspaceRoot: context.workspaceRoot,
|
|
141
|
-
...(onEvent ? { onEvent } : {}),
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
cache.set(role.provider, provider)
|
|
145
|
-
return provider
|
|
146
|
-
}
|
|
147
|
-
}
|
|
45
|
+
const ports = createRuntimePorts({
|
|
46
|
+
config,
|
|
47
|
+
context,
|
|
48
|
+
taskSource,
|
|
49
|
+
verbose: options.verbose,
|
|
50
|
+
})
|
|
148
51
|
|
|
149
|
-
|
|
150
|
-
const cache = new Map<WorkflowProvider, RemoteReviewerProvider>()
|
|
151
|
-
return (providerName: WorkflowProvider) => {
|
|
152
|
-
const cached = cache.get(providerName)
|
|
153
|
-
if (cached) {
|
|
154
|
-
return cached
|
|
155
|
-
}
|
|
156
|
-
if (providerName === 'claude') {
|
|
157
|
-
throw new Error(
|
|
158
|
-
'claude remote reviewer is not implemented in pull-request mode',
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
const provider = createCodexRemoteReviewerProvider()
|
|
162
|
-
cache.set(providerName, provider)
|
|
163
|
-
return provider
|
|
164
|
-
}
|
|
165
|
-
}
|
|
52
|
+
await ports.git.requireCleanWorktree()
|
|
166
53
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
input.context,
|
|
172
|
-
input.options.verbose,
|
|
54
|
+
const topology = buildTaskTopology(
|
|
55
|
+
taskSource,
|
|
56
|
+
context.featureId,
|
|
57
|
+
config.task.maxIterations,
|
|
173
58
|
)
|
|
174
|
-
const implementerRole = input.config.workflow.roles.implementer
|
|
175
|
-
const reviewerRole = input.config.workflow.roles.reviewer
|
|
176
59
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const implementer = resolveProvider(implementerRole)
|
|
181
|
-
const roles: WorkflowRoleProviders = {
|
|
182
|
-
implementer,
|
|
183
|
-
reviewer,
|
|
184
|
-
}
|
|
60
|
+
const isPullRequest = config.workflow.mode === 'pull-request'
|
|
61
|
+
const implementerRole = config.workflow.roles.implementer
|
|
62
|
+
const reviewerRole = config.workflow.roles.reviewer
|
|
185
63
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}),
|
|
191
|
-
}
|
|
64
|
+
if (isPullRequest && reviewerRole.provider === 'claude') {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'claude remote reviewer is not implemented in pull-request mode',
|
|
67
|
+
)
|
|
192
68
|
}
|
|
193
69
|
|
|
194
70
|
if (
|
|
71
|
+
!isPullRequest &&
|
|
195
72
|
implementerRole.provider === reviewerRole.provider &&
|
|
196
73
|
!providerOptionsEqual(implementerRole, reviewerRole)
|
|
197
74
|
) {
|
|
@@ -200,71 +77,86 @@ function resolveWorkflowRuntime(
|
|
|
200
77
|
)
|
|
201
78
|
}
|
|
202
79
|
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
reviewer,
|
|
208
|
-
}
|
|
80
|
+
const protocol = isPullRequest ? 'run-pr' : 'run-direct'
|
|
81
|
+
const store = createFsHarnessStore(
|
|
82
|
+
createRuntimePaths(context.featureDir).runtimeDir,
|
|
83
|
+
)
|
|
209
84
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
85
|
+
const implementer = ports.resolveAgent(implementerRole)
|
|
86
|
+
const reviewer = ports.resolveAgent(reviewerRole)
|
|
87
|
+
|
|
88
|
+
const program =
|
|
89
|
+
protocol === 'run-pr'
|
|
90
|
+
? createRunPrProgram({
|
|
91
|
+
implementer,
|
|
92
|
+
maxIterations: config.task.maxIterations,
|
|
93
|
+
ports,
|
|
94
|
+
reviewer,
|
|
95
|
+
verifyCommands: config.verify.commands,
|
|
96
|
+
workspaceRoot: context.workspaceRoot,
|
|
97
|
+
})
|
|
98
|
+
: createRunDirectProgram({
|
|
99
|
+
implementer,
|
|
100
|
+
maxIterations: config.task.maxIterations,
|
|
101
|
+
ports,
|
|
102
|
+
reviewer,
|
|
103
|
+
verifyCommands: config.verify.commands,
|
|
104
|
+
workspaceRoot: context.workspaceRoot,
|
|
105
|
+
})
|
|
217
106
|
|
|
218
|
-
export async function loadWorkflowExecution(
|
|
219
|
-
context: WorkspaceContext,
|
|
220
|
-
options: RunCommandOptions = {},
|
|
221
|
-
): Promise<WorkflowExecution> {
|
|
222
|
-
const config =
|
|
223
|
-
options.config ?? (await loadWorkflowConfig(context.workspaceRoot))
|
|
224
|
-
const workflow = resolveWorkflowRuntime({
|
|
225
|
-
config,
|
|
226
|
-
context,
|
|
227
|
-
options,
|
|
228
|
-
})
|
|
229
|
-
const taskSource = await openTaskSource(config.task.source, {
|
|
230
|
-
featureDir: context.featureDir,
|
|
231
|
-
featureId: context.featureId,
|
|
232
|
-
workspaceRoot: context.workspaceRoot,
|
|
233
|
-
})
|
|
234
|
-
const runtime = createOrchestratorRuntime({
|
|
235
|
-
featureDir: context.featureDir,
|
|
236
|
-
taskSource,
|
|
237
|
-
workspaceRoot: context.workspaceRoot,
|
|
238
|
-
})
|
|
239
|
-
await runtime.git.requireCleanWorktree()
|
|
240
|
-
const graph = buildTaskTopology(
|
|
241
|
-
taskSource,
|
|
242
|
-
context.featureId,
|
|
243
|
-
config.task.maxIterations,
|
|
244
|
-
)
|
|
245
107
|
const untilTaskHandle = options.untilTaskId
|
|
246
108
|
? taskSource.resolveTaskSelector(options.untilTaskId)
|
|
247
109
|
: undefined
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
110
|
+
|
|
111
|
+
const scheduler = createRunGraphScheduler({
|
|
112
|
+
protocol,
|
|
113
|
+
store,
|
|
114
|
+
graph: topology.tasks.map((t) => ({
|
|
115
|
+
dependsOn: t.dependsOn,
|
|
116
|
+
subjectId: t.handle,
|
|
117
|
+
})),
|
|
252
118
|
...(untilTaskHandle ? { untilTaskHandle } : {}),
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
for await (const event of runSession({
|
|
122
|
+
config: {},
|
|
123
|
+
scheduler,
|
|
124
|
+
kernel: {
|
|
125
|
+
run: (subjectId) =>
|
|
126
|
+
runKernel({
|
|
127
|
+
config: { verify: config.verify, workflow: config.workflow },
|
|
128
|
+
program,
|
|
129
|
+
protocol,
|
|
130
|
+
store,
|
|
131
|
+
subjectId,
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
})) {
|
|
135
|
+
void event
|
|
253
136
|
}
|
|
254
137
|
|
|
138
|
+
const sets = await scheduler.rebuild()
|
|
139
|
+
const totalTasks = topology.tasks.length
|
|
140
|
+
const completedTasks = sets.done.size
|
|
141
|
+
const blockedTasks = sets.blocked.size
|
|
142
|
+
const replanTasks = sets.replan.size
|
|
143
|
+
|
|
144
|
+
const finalStatus =
|
|
145
|
+
replanTasks > 0
|
|
146
|
+
? ('replan_required' as const)
|
|
147
|
+
: blockedTasks > 0
|
|
148
|
+
? ('blocked' as const)
|
|
149
|
+
: completedTasks === totalTasks
|
|
150
|
+
? ('completed' as const)
|
|
151
|
+
: ('in_progress' as const)
|
|
152
|
+
|
|
255
153
|
return {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
154
|
+
summary: {
|
|
155
|
+
blockedTasks,
|
|
156
|
+
completedTasks,
|
|
157
|
+
finalStatus,
|
|
158
|
+
replanTasks,
|
|
159
|
+
totalTasks,
|
|
260
160
|
},
|
|
261
161
|
}
|
|
262
162
|
}
|
|
263
|
-
|
|
264
|
-
export async function runCommand(
|
|
265
|
-
context: WorkspaceContext,
|
|
266
|
-
options: RunCommandOptions = {},
|
|
267
|
-
) {
|
|
268
|
-
const execution = await loadWorkflowExecution(context, options)
|
|
269
|
-
return execution.execute()
|
|
270
|
-
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClaudeProvider,
|
|
3
|
+
type ClaudeAgentClientOptions,
|
|
4
|
+
} from '../agents/claude'
|
|
5
|
+
import {
|
|
6
|
+
createCodexProvider,
|
|
7
|
+
type CodexAgentClientOptions,
|
|
8
|
+
} from '../agents/codex'
|
|
9
|
+
import {
|
|
10
|
+
createClaudeEventHandler,
|
|
11
|
+
createCodexEventHandler,
|
|
12
|
+
} from '../agents/event-log'
|
|
13
|
+
import { GitRuntime } from '../runtime/git'
|
|
14
|
+
import { GitHubRuntime } from '../runtime/github'
|
|
15
|
+
import { createRuntimePaths } from '../runtime/path-layout'
|
|
16
|
+
|
|
17
|
+
import type { ImplementerProvider } from '../agents/types'
|
|
18
|
+
import type { AgentPort } from '../ports/agent'
|
|
19
|
+
import type { TaskSourceSession } from '../task-sources/types'
|
|
20
|
+
import type { WorkspaceContext } from '../types'
|
|
21
|
+
import type { WorkflowConfig } from '../workflow/config'
|
|
22
|
+
import type { AgentRoleConfig, RuntimePorts } from './runtime'
|
|
23
|
+
|
|
24
|
+
interface InvokeCapable {
|
|
25
|
+
invokeStructured: <T>(input: {
|
|
26
|
+
outputSchema: Record<string, unknown>
|
|
27
|
+
prompt: string
|
|
28
|
+
}) => Promise<T>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasInvokeStructured(
|
|
32
|
+
provider: ImplementerProvider,
|
|
33
|
+
): provider is ImplementerProvider & InvokeCapable {
|
|
34
|
+
return (
|
|
35
|
+
'invokeStructured' in provider &&
|
|
36
|
+
typeof provider.invokeStructured === 'function'
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CreateRuntimePortsInput {
|
|
41
|
+
config: WorkflowConfig
|
|
42
|
+
context: WorkspaceContext
|
|
43
|
+
taskSource: TaskSourceSession
|
|
44
|
+
verbose?: boolean | undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createRuntimePorts(
|
|
48
|
+
input: CreateRuntimePortsInput,
|
|
49
|
+
): RuntimePorts {
|
|
50
|
+
const { context, taskSource, verbose } = input
|
|
51
|
+
const runtimePaths = createRuntimePaths(context.featureDir)
|
|
52
|
+
const git = new GitRuntime(context.workspaceRoot, runtimePaths.runtimeDir)
|
|
53
|
+
const codeHost = new GitHubRuntime(context.workspaceRoot)
|
|
54
|
+
const agentCache = new Map<string, AgentPort>()
|
|
55
|
+
|
|
56
|
+
function resolveAgent(role: AgentRoleConfig): AgentPort {
|
|
57
|
+
const key = `${role.provider}:${role.model ?? ''}:${role.effort ?? ''}`
|
|
58
|
+
const cached = agentCache.get(key)
|
|
59
|
+
if (cached) {
|
|
60
|
+
return cached
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let provider: ImplementerProvider
|
|
64
|
+
if (role.provider === 'claude') {
|
|
65
|
+
const onEvent = createClaudeEventHandler(verbose)
|
|
66
|
+
provider = createClaudeProvider({
|
|
67
|
+
...(role.effort
|
|
68
|
+
? {
|
|
69
|
+
effort: role.effort as NonNullable<
|
|
70
|
+
ClaudeAgentClientOptions['effort']
|
|
71
|
+
>,
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
...(role.model ? { model: role.model } : {}),
|
|
75
|
+
workspaceRoot: context.workspaceRoot,
|
|
76
|
+
...(onEvent ? { onEvent } : {}),
|
|
77
|
+
})
|
|
78
|
+
} else {
|
|
79
|
+
const onEvent = createCodexEventHandler(verbose)
|
|
80
|
+
provider = createCodexProvider({
|
|
81
|
+
...(role.effort
|
|
82
|
+
? {
|
|
83
|
+
effort: role.effort as NonNullable<
|
|
84
|
+
CodexAgentClientOptions['effort']
|
|
85
|
+
>,
|
|
86
|
+
}
|
|
87
|
+
: {}),
|
|
88
|
+
...(role.model ? { model: role.model } : {}),
|
|
89
|
+
workspaceRoot: context.workspaceRoot,
|
|
90
|
+
...(onEvent ? { onEvent } : {}),
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!hasInvokeStructured(provider)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Provider ${role.provider} does not support invokeStructured`,
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const invoke = provider.invokeStructured.bind(provider)
|
|
101
|
+
const agent: AgentPort = {
|
|
102
|
+
name: provider.name,
|
|
103
|
+
async execute(invocation) {
|
|
104
|
+
return invoke(invocation)
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
agentCache.set(key, agent)
|
|
109
|
+
return agent
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
codeHost,
|
|
114
|
+
git,
|
|
115
|
+
resolveAgent,
|
|
116
|
+
taskSource,
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/core/runtime.ts
CHANGED
|
@@ -1,39 +1,6 @@
|
|
|
1
|
+
import type { AgentPort } from '../ports/agent'
|
|
2
|
+
import type { CodeHostPort } from '../ports/code-host'
|
|
1
3
|
import type { TaskSourceSession } from '../task-sources/types'
|
|
2
|
-
import type {
|
|
3
|
-
FinalReport,
|
|
4
|
-
ImplementArtifact,
|
|
5
|
-
IntegrateArtifact,
|
|
6
|
-
ReviewArtifact,
|
|
7
|
-
TaskGraph,
|
|
8
|
-
WorkflowEvent,
|
|
9
|
-
WorkflowState,
|
|
10
|
-
} from '../types'
|
|
11
|
-
|
|
12
|
-
export interface AttemptArtifactKey {
|
|
13
|
-
attempt: number
|
|
14
|
-
generation: number
|
|
15
|
-
taskHandle: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface WorkflowStore {
|
|
19
|
-
appendEvent: (event: WorkflowEvent) => Promise<void>
|
|
20
|
-
loadGraph: () => Promise<null | TaskGraph>
|
|
21
|
-
loadImplementArtifact: (
|
|
22
|
-
key: AttemptArtifactKey,
|
|
23
|
-
) => Promise<ImplementArtifact | null>
|
|
24
|
-
loadReviewArtifact: (
|
|
25
|
-
key: AttemptArtifactKey,
|
|
26
|
-
) => Promise<null | ReviewArtifact>
|
|
27
|
-
loadState: () => Promise<null | WorkflowState>
|
|
28
|
-
readReport: () => Promise<FinalReport | null>
|
|
29
|
-
reset: () => Promise<void>
|
|
30
|
-
saveGraph: (graph: TaskGraph) => Promise<void>
|
|
31
|
-
saveImplementArtifact: (artifact: ImplementArtifact) => Promise<void>
|
|
32
|
-
saveIntegrateArtifact: (artifact: IntegrateArtifact) => Promise<void>
|
|
33
|
-
saveReport: (report: FinalReport) => Promise<void>
|
|
34
|
-
saveReviewArtifact: (artifact: ReviewArtifact) => Promise<void>
|
|
35
|
-
saveState: (state: WorkflowState) => Promise<void>
|
|
36
|
-
}
|
|
37
4
|
|
|
38
5
|
export interface GitCheckoutBranchOptions {
|
|
39
6
|
create?: boolean
|
|
@@ -167,9 +134,21 @@ export interface SquashMergePullRequestInput {
|
|
|
167
134
|
subject: string
|
|
168
135
|
}
|
|
169
136
|
|
|
137
|
+
export interface AgentRoleConfig {
|
|
138
|
+
effort?: string | undefined
|
|
139
|
+
model?: string | undefined
|
|
140
|
+
provider: 'claude' | 'codex'
|
|
141
|
+
}
|
|
142
|
+
|
|
170
143
|
export interface OrchestratorRuntime {
|
|
171
144
|
git: GitPort
|
|
172
145
|
github: GitHubPort
|
|
173
|
-
|
|
146
|
+
taskSource: TaskSourceSession
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface RuntimePorts {
|
|
150
|
+
codeHost: CodeHostPort
|
|
151
|
+
git: GitPort
|
|
152
|
+
resolveAgent: (role: AgentRoleConfig) => AgentPort
|
|
174
153
|
taskSource: TaskSourceSession
|
|
175
154
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Artifact, TaskState, TransitionRecord } from './state'
|
|
2
|
+
import type { HarnessStore } from './store'
|
|
3
|
+
|
|
4
|
+
const storeKey = (protocol: string, subjectId: string) =>
|
|
5
|
+
`${protocol}:${subjectId}`
|
|
6
|
+
|
|
7
|
+
export function createInMemoryHarnessStore(): HarnessStore {
|
|
8
|
+
const states = new Map<string, TaskState>()
|
|
9
|
+
const artifacts = new Map<string, Artifact>()
|
|
10
|
+
const transitions = new Map<string, TransitionRecord[]>()
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
async appendTransition(protocol, subjectId, record) {
|
|
14
|
+
const k = storeKey(protocol, subjectId)
|
|
15
|
+
const list = transitions.get(k) ?? []
|
|
16
|
+
list.push(structuredClone(record))
|
|
17
|
+
transitions.set(k, list)
|
|
18
|
+
},
|
|
19
|
+
async listArtifacts(protocol, subjectId) {
|
|
20
|
+
const prefix = `${storeKey(protocol, subjectId)}:`
|
|
21
|
+
return [...artifacts.entries()]
|
|
22
|
+
.filter(([k]) => k.startsWith(prefix))
|
|
23
|
+
.map(([, v]) => structuredClone(v))
|
|
24
|
+
},
|
|
25
|
+
async loadArtifact(protocol, subjectId, artifactId) {
|
|
26
|
+
const artifact = artifacts.get(
|
|
27
|
+
`${storeKey(protocol, subjectId)}:${artifactId}`,
|
|
28
|
+
)
|
|
29
|
+
return artifact ? structuredClone(artifact) : null
|
|
30
|
+
},
|
|
31
|
+
async loadState(protocol, subjectId) {
|
|
32
|
+
const state = states.get(storeKey(protocol, subjectId))
|
|
33
|
+
return state ? structuredClone(state) : null
|
|
34
|
+
},
|
|
35
|
+
async saveArtifact(protocol, subjectId, artifact) {
|
|
36
|
+
artifacts.set(
|
|
37
|
+
`${storeKey(protocol, subjectId)}:${artifact.id}`,
|
|
38
|
+
structuredClone(artifact),
|
|
39
|
+
)
|
|
40
|
+
},
|
|
41
|
+
async saveState(protocol, subjectId, state) {
|
|
42
|
+
states.set(storeKey(protocol, subjectId), structuredClone(state))
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
}
|