task-while 0.0.2 → 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 (41) 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 +160 -15
  7. package/src/batch/discovery.ts +1 -1
  8. package/src/commands/batch.ts +63 -164
  9. package/src/commands/run-branch-helpers.ts +81 -0
  10. package/src/commands/run-providers.ts +77 -0
  11. package/src/commands/run.ts +121 -177
  12. package/src/core/create-runtime-ports.ts +118 -0
  13. package/src/core/runtime.ts +15 -36
  14. package/src/harness/in-memory-store.ts +45 -0
  15. package/src/harness/kernel.ts +226 -0
  16. package/src/harness/state.ts +47 -0
  17. package/src/harness/store.ts +26 -0
  18. package/src/harness/workflow-builders.ts +87 -0
  19. package/src/harness/workflow-program.ts +86 -0
  20. package/src/ports/agent.ts +17 -0
  21. package/src/ports/code-host.ts +23 -0
  22. package/src/programs/batch.ts +139 -0
  23. package/src/programs/run-direct.ts +209 -0
  24. package/src/programs/run-pr-transitions.ts +81 -0
  25. package/src/programs/run-pr.ts +290 -0
  26. package/src/programs/shared-steps.ts +252 -0
  27. package/src/schedulers/scheduler.ts +208 -0
  28. package/src/session/session.ts +127 -0
  29. package/src/workflow/config.ts +15 -0
  30. package/src/core/engine-helpers.ts +0 -114
  31. package/src/core/engine-outcomes.ts +0 -166
  32. package/src/core/engine.ts +0 -223
  33. package/src/core/orchestrator-helpers.ts +0 -52
  34. package/src/core/orchestrator-integrate-resume.ts +0 -149
  35. package/src/core/orchestrator-review-resume.ts +0 -228
  36. package/src/core/orchestrator-task-attempt.ts +0 -257
  37. package/src/core/orchestrator.ts +0 -99
  38. package/src/runtime/fs-runtime.ts +0 -209
  39. package/src/workflow/direct-preset.ts +0 -44
  40. package/src/workflow/preset.ts +0 -86
  41. package/src/workflow/pull-request-preset.ts +0 -312
@@ -1,33 +1,16 @@
1
- import { createClaudeProvider } from '../agents/claude'
2
- import { createCodexProvider } from '../agents/codex'
3
- import {
4
- createClaudeEventHandler,
5
- createCodexEventHandler,
6
- } from '../agents/event-log'
1
+ import { createFsHarnessStore } from '../adapters/fs/harness-store'
7
2
  import { providerOptionsEqual } from '../agents/provider-options'
8
- import { runWorkflow, type WorkflowRunResult } from '../core/orchestrator'
3
+ import { createRuntimePorts } from '../core/create-runtime-ports'
9
4
  import { buildTaskTopology } from '../core/task-topology'
10
- 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'
11
11
  import { openTaskSource } from '../task-sources/registry'
12
- import {
13
- loadWorkflowConfig,
14
- type WorkflowConfig,
15
- type WorkflowProvider,
16
- type WorkflowRoleConfig,
17
- } from '../workflow/config'
18
- import {
19
- createDirectWorkflowPreset,
20
- createPullRequestWorkflowPreset,
21
- type WorkflowRuntime,
22
- } from '../workflow/preset'
23
- import { createCodexRemoteReviewerProvider } from '../workflow/remote-reviewer'
24
-
25
- import type {
26
- ImplementerProvider,
27
- RemoteReviewerProvider,
28
- ReviewerProvider,
29
- WorkflowRoleProviders,
30
- } from '../agents/types'
12
+ import { loadWorkflowConfig, type WorkflowConfig } from '../workflow/config'
13
+
31
14
  import type { WorkspaceContext } from '../types'
32
15
 
33
16
  export interface RunCommandOptions {
@@ -36,110 +19,56 @@ export interface RunCommandOptions {
36
19
  verbose?: boolean
37
20
  }
38
21
 
39
- export type WorkflowExecutionRunner = () => Promise<WorkflowRunResult>
40
-
41
- export interface WorkflowExecution {
42
- config: WorkflowConfig
43
- execute: WorkflowExecutionRunner
44
- workflow: WorkflowRuntime
45
- }
46
-
47
- export interface ResolveWorkflowRuntimeInput {
48
- config: WorkflowConfig
49
- context: WorkspaceContext
50
- options: RunCommandOptions
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
29
+ }
51
30
  }
52
31
 
53
- export type ProviderResolver = (
54
- role: WorkflowRoleConfig,
55
- ) => ImplementerProvider & ReviewerProvider
32
+ export async function runCommand(
33
+ context: WorkspaceContext,
34
+ options: RunCommandOptions = {},
35
+ ): Promise<RunCommandResult> {
36
+ const config =
37
+ options.config ?? (await loadWorkflowConfig(context.workspaceRoot))
56
38
 
57
- export type RemoteReviewerResolver = (
58
- providerName: WorkflowProvider,
59
- ) => RemoteReviewerProvider
39
+ const taskSource = await openTaskSource(config.task.source, {
40
+ featureDir: context.featureDir,
41
+ featureId: context.featureId,
42
+ workspaceRoot: context.workspaceRoot,
43
+ })
60
44
 
61
- function createProviderResolver(
62
- context: WorkspaceContext,
63
- verbose: boolean | undefined,
64
- ): ProviderResolver {
65
- const cache = new Map<
66
- WorkflowProvider,
67
- ImplementerProvider & ReviewerProvider
68
- >()
69
- return (role: WorkflowRoleConfig) => {
70
- const cached = cache.get(role.provider)
71
- if (cached) {
72
- return cached
73
- }
74
- let provider: ImplementerProvider & ReviewerProvider
75
- if (role.provider === 'claude') {
76
- const onEvent = createClaudeEventHandler(verbose)
77
- provider = createClaudeProvider({
78
- ...(role.effort ? { effort: role.effort } : {}),
79
- ...(role.model ? { model: role.model } : {}),
80
- workspaceRoot: context.workspaceRoot,
81
- ...(onEvent ? { onEvent } : {}),
82
- })
83
- } else {
84
- const onEvent = createCodexEventHandler(verbose)
85
- provider = createCodexProvider({
86
- ...(role.effort ? { effort: role.effort } : {}),
87
- ...(role.model ? { model: role.model } : {}),
88
- workspaceRoot: context.workspaceRoot,
89
- ...(onEvent ? { onEvent } : {}),
90
- })
91
- }
92
- cache.set(role.provider, provider)
93
- return provider
94
- }
95
- }
45
+ const ports = createRuntimePorts({
46
+ config,
47
+ context,
48
+ taskSource,
49
+ verbose: options.verbose,
50
+ })
96
51
 
97
- function createRemoteReviewerResolver(): RemoteReviewerResolver {
98
- const cache = new Map<WorkflowProvider, RemoteReviewerProvider>()
99
- return (providerName: WorkflowProvider) => {
100
- const cached = cache.get(providerName)
101
- if (cached) {
102
- return cached
103
- }
104
- if (providerName === 'claude') {
105
- throw new Error(
106
- 'claude remote reviewer is not implemented in pull-request mode',
107
- )
108
- }
109
- const provider = createCodexRemoteReviewerProvider()
110
- cache.set(providerName, provider)
111
- return provider
112
- }
113
- }
52
+ await ports.git.requireCleanWorktree()
114
53
 
115
- function resolveWorkflowRuntime(
116
- input: ResolveWorkflowRuntimeInput,
117
- ): WorkflowRuntime {
118
- const resolveProvider = createProviderResolver(
119
- input.context,
120
- input.options.verbose,
54
+ const topology = buildTaskTopology(
55
+ taskSource,
56
+ context.featureId,
57
+ config.task.maxIterations,
121
58
  )
122
- const implementerRole = input.config.workflow.roles.implementer
123
- const reviewerRole = input.config.workflow.roles.reviewer
124
-
125
- if (input.config.workflow.mode === 'pull-request') {
126
- const resolveRemoteReviewer = createRemoteReviewerResolver()
127
- const reviewer = resolveRemoteReviewer(reviewerRole.provider)
128
- const implementer = resolveProvider(implementerRole)
129
- const roles: WorkflowRoleProviders = {
130
- implementer,
131
- reviewer,
132
- }
133
-
134
- return {
135
- roles,
136
- preset: createPullRequestWorkflowPreset({
137
- reviewer,
138
- }),
139
- }
59
+
60
+ const isPullRequest = config.workflow.mode === 'pull-request'
61
+ const implementerRole = config.workflow.roles.implementer
62
+ const reviewerRole = config.workflow.roles.reviewer
63
+
64
+ if (isPullRequest && reviewerRole.provider === 'claude') {
65
+ throw new Error(
66
+ 'claude remote reviewer is not implemented in pull-request mode',
67
+ )
140
68
  }
141
69
 
142
70
  if (
71
+ !isPullRequest &&
143
72
  implementerRole.provider === reviewerRole.provider &&
144
73
  !providerOptionsEqual(implementerRole, reviewerRole)
145
74
  ) {
@@ -148,71 +77,86 @@ function resolveWorkflowRuntime(
148
77
  )
149
78
  }
150
79
 
151
- const implementer = resolveProvider(implementerRole)
152
- const reviewer = resolveProvider(reviewerRole)
153
- const roles: WorkflowRoleProviders = {
154
- implementer,
155
- reviewer,
156
- }
80
+ const protocol = isPullRequest ? 'run-pr' : 'run-direct'
81
+ const store = createFsHarnessStore(
82
+ createRuntimePaths(context.featureDir).runtimeDir,
83
+ )
157
84
 
158
- return {
159
- roles,
160
- preset: createDirectWorkflowPreset({
161
- reviewer,
162
- }),
163
- }
164
- }
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
+ })
165
106
 
166
- export async function loadWorkflowExecution(
167
- context: WorkspaceContext,
168
- options: RunCommandOptions = {},
169
- ): Promise<WorkflowExecution> {
170
- const config =
171
- options.config ?? (await loadWorkflowConfig(context.workspaceRoot))
172
- const workflow = resolveWorkflowRuntime({
173
- config,
174
- context,
175
- options,
176
- })
177
- const taskSource = await openTaskSource(config.task.source, {
178
- featureDir: context.featureDir,
179
- featureId: context.featureId,
180
- workspaceRoot: context.workspaceRoot,
181
- })
182
- const runtime = createOrchestratorRuntime({
183
- featureDir: context.featureDir,
184
- taskSource,
185
- workspaceRoot: context.workspaceRoot,
186
- })
187
- await runtime.git.requireCleanWorktree()
188
- const graph = buildTaskTopology(
189
- taskSource,
190
- context.featureId,
191
- config.task.maxIterations,
192
- )
193
107
  const untilTaskHandle = options.untilTaskId
194
108
  ? taskSource.resolveTaskSelector(options.untilTaskId)
195
109
  : undefined
196
- const workflowInput = {
197
- graph,
198
- runtime,
199
- 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
+ })),
200
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
201
136
  }
202
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
+
203
153
  return {
204
- config,
205
- workflow,
206
- async execute() {
207
- return runWorkflow(workflowInput)
154
+ summary: {
155
+ blockedTasks,
156
+ completedTasks,
157
+ finalStatus,
158
+ replanTasks,
159
+ totalTasks,
208
160
  },
209
161
  }
210
162
  }
211
-
212
- export async function runCommand(
213
- context: WorkspaceContext,
214
- options: RunCommandOptions = {},
215
- ) {
216
- const execution = await loadWorkflowExecution(context, options)
217
- return execution.execute()
218
- }
@@ -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
+ }