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.
Files changed (42) hide show
  1. package/README.md +32 -34
  2. package/package.json +2 -2
  3. package/src/adapters/fs/harness-store.ts +84 -0
  4. package/src/agents/claude.ts +159 -9
  5. package/src/agents/codex.ts +68 -4
  6. package/src/agents/event-log.ts +195 -0
  7. package/src/batch/discovery.ts +1 -1
  8. package/src/batch/provider.ts +9 -0
  9. package/src/commands/batch.ts +69 -165
  10. package/src/commands/run-branch-helpers.ts +81 -0
  11. package/src/commands/run-providers.ts +77 -0
  12. package/src/commands/run.ts +117 -225
  13. package/src/core/create-runtime-ports.ts +118 -0
  14. package/src/core/runtime.ts +15 -36
  15. package/src/harness/in-memory-store.ts +45 -0
  16. package/src/harness/kernel.ts +226 -0
  17. package/src/harness/state.ts +47 -0
  18. package/src/harness/store.ts +26 -0
  19. package/src/harness/workflow-builders.ts +87 -0
  20. package/src/harness/workflow-program.ts +86 -0
  21. package/src/ports/agent.ts +17 -0
  22. package/src/ports/code-host.ts +23 -0
  23. package/src/programs/batch.ts +139 -0
  24. package/src/programs/run-direct.ts +209 -0
  25. package/src/programs/run-pr-transitions.ts +81 -0
  26. package/src/programs/run-pr.ts +290 -0
  27. package/src/programs/shared-steps.ts +252 -0
  28. package/src/schedulers/scheduler.ts +208 -0
  29. package/src/session/session.ts +127 -0
  30. package/src/workflow/config.ts +15 -0
  31. package/src/core/engine-helpers.ts +0 -114
  32. package/src/core/engine-outcomes.ts +0 -166
  33. package/src/core/engine.ts +0 -223
  34. package/src/core/orchestrator-helpers.ts +0 -52
  35. package/src/core/orchestrator-integrate-resume.ts +0 -149
  36. package/src/core/orchestrator-review-resume.ts +0 -228
  37. package/src/core/orchestrator-task-attempt.ts +0 -257
  38. package/src/core/orchestrator.ts +0 -99
  39. package/src/runtime/fs-runtime.ts +0 -209
  40. package/src/workflow/direct-preset.ts +0 -44
  41. package/src/workflow/preset.ts +0 -86
  42. 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
+ }
@@ -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 { runWorkflow, type WorkflowRunResult } from '../core/orchestrator'
3
+ import { createRuntimePorts } from '../core/create-runtime-ports'
13
4
  import { buildTaskTopology } from '../core/task-topology'
14
- import { createOrchestratorRuntime } from '../runtime/fs-runtime'
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 type WorkflowExecutionRunner = () => Promise<WorkflowRunResult>
44
-
45
- export interface WorkflowExecution {
46
- config: WorkflowConfig
47
- execute: WorkflowExecutionRunner
48
- workflow: WorkflowRuntime
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 writeClaudeEvent(event: ClaudeAgentEvent) {
100
- const detail = event.type === 'text' ? ` ${event.delta}` : ''
101
- process.stderr.write(`[claude] ${event.type}${detail}\n`)
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
- function createClaudeEventHandler(
105
- verbose: boolean | undefined,
106
- ): ClaudeAgentEventHandler | undefined {
107
- if (!verbose) {
108
- return undefined
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
- function createProviderResolver(
114
- context: WorkspaceContext,
115
- verbose: boolean | undefined,
116
- ): ProviderResolver {
117
- const cache = new Map<
118
- WorkflowProvider,
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
- function createRemoteReviewerResolver(): RemoteReviewerResolver {
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
- function resolveWorkflowRuntime(
168
- input: ResolveWorkflowRuntimeInput,
169
- ): WorkflowRuntime {
170
- const resolveProvider = createProviderResolver(
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
- if (input.config.workflow.mode === 'pull-request') {
178
- const resolveRemoteReviewer = createRemoteReviewerResolver()
179
- const reviewer = resolveRemoteReviewer(reviewerRole.provider)
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
- return {
187
- roles,
188
- preset: createPullRequestWorkflowPreset({
189
- reviewer,
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 implementer = resolveProvider(implementerRole)
204
- const reviewer = resolveProvider(reviewerRole)
205
- const roles: WorkflowRoleProviders = {
206
- implementer,
207
- reviewer,
208
- }
80
+ const protocol = isPullRequest ? 'run-pr' : 'run-direct'
81
+ const store = createFsHarnessStore(
82
+ createRuntimePaths(context.featureDir).runtimeDir,
83
+ )
209
84
 
210
- return {
211
- roles,
212
- preset: createDirectWorkflowPreset({
213
- reviewer,
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
- const workflowInput = {
249
- graph,
250
- runtime,
251
- workflow,
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
- config,
257
- workflow,
258
- async execute() {
259
- return runWorkflow(workflowInput)
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
+ }
@@ -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
- store: WorkflowStore
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
+ }