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
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# task-while
|
|
2
2
|
|
|
3
|
-
`task-while` is a git-first
|
|
3
|
+
`task-while` is a git-first harness runtime built around a task source protocol. The published package name and CLI binary are both `task-while`.
|
|
4
4
|
|
|
5
5
|
It reads workflow settings from `while.yaml`, opens the configured task source, executes one task at a time, reviews the result, integrates approved work, and creates one git commit per completed task. The built-in task sources are `spec-kit`, which consumes `spec.md`, `plan.md`, and `tasks.md` under `specs/<feature>/`, and `openspec`, which consumes an OpenSpec change under `openspec/changes/<change>/`.
|
|
6
6
|
|
|
7
|
-
It also provides a standalone `batch` command for YAML-driven file processing that is independent from the feature/task
|
|
7
|
+
It also provides a standalone `batch` command for YAML-driven file processing that is independent from the feature/task harness runtime workflow.
|
|
8
8
|
|
|
9
9
|
## Requirements
|
|
10
10
|
|
|
11
|
-
- Node.js
|
|
11
|
+
- Node.js 24 or newer
|
|
12
12
|
- For `run`: a git repository with an initial commit
|
|
13
13
|
- For `run`: a workspace with the directory layout required by the selected task source
|
|
14
14
|
- For `run`: the files required by the selected task source
|
|
@@ -68,7 +68,7 @@ Current status:
|
|
|
68
68
|
- `workflow.mode: pull-request` pushes a task branch, polls GitHub PR review from `chatgpt-codex-connector[bot]`, then squash-merges on approval
|
|
69
69
|
- in `workflow.mode: pull-request`, reviewer `provider` still selects the remote reviewer, but any local reviewer `model` and `effort` values are ignored
|
|
70
70
|
- `workflow.mode: pull-request` currently supports only `codex` as the remote reviewer provider
|
|
71
|
-
- `task.maxIterations`
|
|
71
|
+
- `task.maxIterations` uses the same configured limit for every task in the selected source session; run workflow retries share a single per-task budget across phases
|
|
72
72
|
|
|
73
73
|
Example pull-request mode:
|
|
74
74
|
|
|
@@ -116,7 +116,7 @@ Useful flags:
|
|
|
116
116
|
- `--feature <featureId>`: select the feature explicitly
|
|
117
117
|
- For `task.source: openspec`, `--feature <featureId>` selects the OpenSpec change id
|
|
118
118
|
- `--until-task <taskSelector>`: stop after the target task reaches `done`
|
|
119
|
-
- `--verbose`: stream
|
|
119
|
+
- `--verbose`: stream direct provider details to `stderr`, including Claude init/task/tool/result summaries and Codex thinking, commands, MCP tools, file updates, todo changes, messages, and final usage
|
|
120
120
|
|
|
121
121
|
### `task-while batch`
|
|
122
122
|
|
|
@@ -160,14 +160,13 @@ Batch behavior:
|
|
|
160
160
|
- batch `codex` `effort` accepts `minimal`, `low`, `medium`, `high`, or `xhigh`
|
|
161
161
|
- batch `claude` `effort` accepts `low`, `medium`, `high`, or `max`
|
|
162
162
|
- each run scans files under the `batch.yaml` directory and filters them by `glob`
|
|
163
|
-
- execution state is written beside the YAML file in `state.json`
|
|
164
163
|
- structured results are written beside the YAML file in `results.json`
|
|
164
|
+
- internal harness state is written under `.while/harness/` beside the YAML file
|
|
165
165
|
- result keys are relative to the directory that contains `batch.yaml`
|
|
166
|
-
- `--verbose`
|
|
166
|
+
- `--verbose` streams direct provider details to `stderr` during batch execution, including Claude init/task/tool/result summaries and Codex thinking, commands, MCP tools, file updates, todo changes, messages, and final usage
|
|
167
167
|
- rerunning the command resumes unfinished work and skips files that already have accepted results
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
- there is no retry limit for file-level failures; failed files continue to be retried round by round
|
|
168
|
+
- failed files are suspended and retried after all pending files are processed
|
|
169
|
+
- file-level retries are limited by `maxRetries` (default 3); exhausted files are marked blocked
|
|
171
170
|
- when `glob` matches no files, the command exits successfully without initializing a provider
|
|
172
171
|
|
|
173
172
|
## Task Lifecycle
|
|
@@ -261,52 +260,51 @@ task:
|
|
|
261
260
|
|
|
262
261
|
## What `task-while` Does Not Do
|
|
263
262
|
|
|
264
|
-
`task-while` does not replace Spec Kit's project-level workflow. It does not run Spec Kit commands, checklists,
|
|
263
|
+
`task-while` does not replace Spec Kit's project-level workflow. It does not run Spec Kit commands, checklists, or hooks.
|
|
265
264
|
|
|
266
265
|
Its contract with the selected task source is simple:
|
|
267
266
|
|
|
268
267
|
- the task source parses source artifacts and provides prompts plus completion operations
|
|
269
|
-
-
|
|
268
|
+
- the harness runtime drives implement, review, integrate, and persistence around that protocol
|
|
270
269
|
|
|
271
270
|
The standalone `batch` command is separate from this contract. It does not use task sources, task graphs, review/integrate stages, or git-first completion.
|
|
272
271
|
|
|
272
|
+
## Architecture
|
|
273
|
+
|
|
274
|
+
`task-while` uses a state-machine control plane:
|
|
275
|
+
|
|
276
|
+
- **TaskState** per subject is the single source of truth, written atomically as JSON
|
|
277
|
+
- **Transition log** (append-only JSONL) records phase transitions for debugging
|
|
278
|
+
- **Artifacts** store large structured outputs (contracts, reviews, implementations) separately
|
|
279
|
+
- A **pure kernel interpreter** executes typed workflow programs (action/gate/branch nodes + declarative transition tables)
|
|
280
|
+
- A **session layer** drives multi-subject scheduling via pluggable schedulers
|
|
281
|
+
- All external effects flow through unified **ports** (AgentPort, CodeHostPort, GitPort)
|
|
282
|
+
|
|
273
283
|
## Runtime Layout
|
|
274
284
|
|
|
275
285
|
`run` keeps runtime state under:
|
|
276
286
|
|
|
277
287
|
```text
|
|
278
|
-
<source-entry>/<id>/.while/
|
|
288
|
+
<source-entry>/<id>/.while/harness/
|
|
289
|
+
state/<protocol>/<subject-id>.json — TaskState per subject (truth)
|
|
290
|
+
transitions/<protocol>/<subject-id>.jsonl — TransitionRecord log (debug)
|
|
291
|
+
artifacts/<protocol>/<subject-id>/*.json — Artifact per kind/iteration
|
|
279
292
|
```
|
|
280
293
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
- `state.json`
|
|
284
|
-
- `graph.json`
|
|
285
|
-
- `report.json`
|
|
286
|
-
- `events.jsonl`
|
|
287
|
-
- `tasks/<taskHandle>/g<generation>/a<attempt>/implement.json`
|
|
288
|
-
- `tasks/<taskHandle>/g<generation>/a<attempt>/review.json`
|
|
289
|
-
- `tasks/<taskHandle>/g<generation>/a<attempt>/integrate.json`
|
|
290
|
-
|
|
291
|
-
`.while` is runtime state, not the long-term source of truth. Pull-request review recovery reloads persisted `implement` artifacts by `taskHandle`, `generation`, and `attempt`.
|
|
294
|
+
`.while` is runtime state, not the long-term source of truth. Resume reads the state file directly — no event replay needed.
|
|
292
295
|
|
|
293
296
|
`batch` keeps runtime files beside the YAML config:
|
|
294
297
|
|
|
295
298
|
```text
|
|
296
299
|
<config-dir>/
|
|
297
300
|
├── batch.yaml
|
|
298
|
-
├──
|
|
299
|
-
└──
|
|
301
|
+
├── results.json
|
|
302
|
+
└── .while/harness/
|
|
303
|
+
├── state/batch/*.json
|
|
304
|
+
├── transitions/batch/*.jsonl
|
|
305
|
+
└── artifacts/batch/...
|
|
300
306
|
```
|
|
301
307
|
|
|
302
|
-
`state.json` contains:
|
|
303
|
-
|
|
304
|
-
- `pending`
|
|
305
|
-
- `inProgress`
|
|
306
|
-
- `failed`
|
|
307
|
-
|
|
308
|
-
`failed` is the current round's failure buffer. When `pending` becomes empty, those paths are persisted back into `pending` and retried in the next round. Historical state entries whose files no longer exist are dropped when a new run starts.
|
|
309
|
-
|
|
310
308
|
`results.json` maps accepted structured output by file path relative to the `batch.yaml` directory. If the config lives under a subdirectory and uses patterns such as `../input/*.txt`, the keys keep that relative form.
|
|
311
309
|
|
|
312
310
|
## Publishing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-while",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"packageManager": "pnpm@10.32.1",
|
|
5
5
|
"description": "Git-first task orchestrator for task-source workspaces",
|
|
6
6
|
"author": "Zhang Yu",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@anthropic-ai/claude-agent-sdk": "^0.2.92",
|
|
51
|
-
"@openai/codex-sdk": "^0.
|
|
51
|
+
"@openai/codex-sdk": "^0.118.0",
|
|
52
52
|
"ajv": "^8.18.0",
|
|
53
53
|
"arg": "^5.0.2",
|
|
54
54
|
"execa": "^8.0.1",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { appendFile, mkdir, readdir, rename, writeFile } from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { pathExists, readJson } from 'fs-extra'
|
|
5
|
+
|
|
6
|
+
import type { HarnessStore } from '../../harness/store'
|
|
7
|
+
|
|
8
|
+
function encodeArtifactFileName(artifactId: string) {
|
|
9
|
+
return encodeURIComponent(artifactId)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createFsHarnessStore(root: string): HarnessStore {
|
|
13
|
+
const stateFile = (protocol: string, subjectId: string) =>
|
|
14
|
+
path.join(root, 'state', protocol, `${encodeURIComponent(subjectId)}.json`)
|
|
15
|
+
|
|
16
|
+
const artifactFile = (
|
|
17
|
+
protocol: string,
|
|
18
|
+
subjectId: string,
|
|
19
|
+
artifactId: string,
|
|
20
|
+
) =>
|
|
21
|
+
path.join(
|
|
22
|
+
root,
|
|
23
|
+
'artifacts',
|
|
24
|
+
protocol,
|
|
25
|
+
encodeURIComponent(subjectId),
|
|
26
|
+
`${encodeArtifactFileName(artifactId)}.json`,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const artifactDir = (protocol: string, subjectId: string) =>
|
|
30
|
+
path.join(root, 'artifacts', protocol, encodeURIComponent(subjectId))
|
|
31
|
+
|
|
32
|
+
const transitionFile = (protocol: string, subjectId: string) =>
|
|
33
|
+
path.join(
|
|
34
|
+
root,
|
|
35
|
+
'transitions',
|
|
36
|
+
protocol,
|
|
37
|
+
`${encodeURIComponent(subjectId)}.jsonl`,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
async appendTransition(protocol, subjectId, record) {
|
|
42
|
+
const file = transitionFile(protocol, subjectId)
|
|
43
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
44
|
+
await appendFile(file, `${JSON.stringify(record)}\n`)
|
|
45
|
+
},
|
|
46
|
+
async listArtifacts(protocol, subjectId) {
|
|
47
|
+
const dir = artifactDir(protocol, subjectId)
|
|
48
|
+
if (!(await pathExists(dir))) {
|
|
49
|
+
return []
|
|
50
|
+
}
|
|
51
|
+
const directoryEntries = await readdir(dir)
|
|
52
|
+
const entries = directoryEntries.filter((e) => e.endsWith('.json'))
|
|
53
|
+
return Promise.all(
|
|
54
|
+
entries.map((entry) => readJson(path.join(dir, entry))),
|
|
55
|
+
)
|
|
56
|
+
},
|
|
57
|
+
async loadArtifact(protocol, subjectId, artifactId) {
|
|
58
|
+
const file = artifactFile(protocol, subjectId, artifactId)
|
|
59
|
+
if (!(await pathExists(file))) {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
return readJson(file)
|
|
63
|
+
},
|
|
64
|
+
async loadState(protocol, subjectId) {
|
|
65
|
+
const file = stateFile(protocol, subjectId)
|
|
66
|
+
if (!(await pathExists(file))) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
return readJson(file)
|
|
70
|
+
},
|
|
71
|
+
async saveArtifact(protocol, subjectId, artifact) {
|
|
72
|
+
const file = artifactFile(protocol, subjectId, artifact.id)
|
|
73
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
74
|
+
await writeFile(file, JSON.stringify(artifact, null, 2))
|
|
75
|
+
},
|
|
76
|
+
async saveState(protocol, subjectId, state) {
|
|
77
|
+
const file = stateFile(protocol, subjectId)
|
|
78
|
+
const tmpFile = `${file}.tmp`
|
|
79
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
80
|
+
await writeFile(tmpFile, JSON.stringify(state, null, 2))
|
|
81
|
+
await rename(tmpFile, file)
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/agents/claude.ts
CHANGED
|
@@ -22,11 +22,45 @@ export interface ClaudeTextEvent {
|
|
|
22
22
|
type: 'text'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export interface
|
|
26
|
-
|
|
25
|
+
export interface ClaudeInitEvent {
|
|
26
|
+
mcpServers: { name: string; status: string }[]
|
|
27
|
+
model: string
|
|
28
|
+
permissionMode: string
|
|
29
|
+
skills: string[]
|
|
30
|
+
tools: string[]
|
|
31
|
+
type: 'system.init'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ClaudeTaskStartedEvent {
|
|
35
|
+
description: string
|
|
36
|
+
taskId: string
|
|
37
|
+
type: 'task.started'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ClaudeTaskProgressEvent {
|
|
41
|
+
description: string
|
|
42
|
+
lastToolName?: string
|
|
43
|
+
summary?: string
|
|
44
|
+
taskId: string
|
|
45
|
+
type: 'task.progress'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ClaudeToolProgressEvent {
|
|
49
|
+
elapsedTimeSeconds: number
|
|
50
|
+
toolName: string
|
|
51
|
+
toolUseId: string
|
|
52
|
+
type: 'tool.progress'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ClaudeToolSummaryEvent {
|
|
56
|
+
summary: string
|
|
57
|
+
type: 'tool.summary'
|
|
27
58
|
}
|
|
28
59
|
|
|
29
60
|
export interface ClaudeResultEvent {
|
|
61
|
+
durationMs: number
|
|
62
|
+
numTurns: number
|
|
63
|
+
subtype: 'success'
|
|
30
64
|
type: 'result'
|
|
31
65
|
}
|
|
32
66
|
|
|
@@ -36,15 +70,21 @@ export interface ClaudeErrorEvent {
|
|
|
36
70
|
}
|
|
37
71
|
|
|
38
72
|
export type ClaudeAgentEvent =
|
|
39
|
-
| ClaudeAssistantEvent
|
|
40
73
|
| ClaudeErrorEvent
|
|
74
|
+
| ClaudeInitEvent
|
|
41
75
|
| ClaudeResultEvent
|
|
76
|
+
| ClaudeTaskProgressEvent
|
|
77
|
+
| ClaudeTaskStartedEvent
|
|
42
78
|
| ClaudeTextEvent
|
|
79
|
+
| ClaudeToolProgressEvent
|
|
80
|
+
| ClaudeToolSummaryEvent
|
|
43
81
|
|
|
44
82
|
export type ClaudeAgentEventHandler = (event: ClaudeAgentEvent) => void
|
|
45
83
|
|
|
46
84
|
interface QueryResultMessage {
|
|
85
|
+
duration_ms?: number
|
|
47
86
|
errors?: string[]
|
|
87
|
+
num_turns?: number
|
|
48
88
|
structured_output?: unknown
|
|
49
89
|
subtype: string
|
|
50
90
|
type: 'result'
|
|
@@ -62,10 +102,53 @@ interface QueryAssistantMessage {
|
|
|
62
102
|
type: 'assistant'
|
|
63
103
|
}
|
|
64
104
|
|
|
105
|
+
interface QuerySystemInitMessage {
|
|
106
|
+
mcp_servers: { name: string; status: string }[]
|
|
107
|
+
model: string
|
|
108
|
+
permissionMode: string
|
|
109
|
+
skills: string[]
|
|
110
|
+
subtype: 'init'
|
|
111
|
+
tools: string[]
|
|
112
|
+
type: 'system'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface QueryTaskStartedMessage {
|
|
116
|
+
description: string
|
|
117
|
+
subtype: 'task_started'
|
|
118
|
+
task_id: string
|
|
119
|
+
type: 'system'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface QueryTaskProgressMessage {
|
|
123
|
+
description: string
|
|
124
|
+
last_tool_name?: string
|
|
125
|
+
subtype: 'task_progress'
|
|
126
|
+
summary?: string
|
|
127
|
+
task_id: string
|
|
128
|
+
type: 'system'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface QueryToolProgressMessage {
|
|
132
|
+
elapsed_time_seconds: number
|
|
133
|
+
tool_name: string
|
|
134
|
+
tool_use_id: string
|
|
135
|
+
type: 'tool_progress'
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface QueryToolUseSummaryMessage {
|
|
139
|
+
summary: string
|
|
140
|
+
type: 'tool_use_summary'
|
|
141
|
+
}
|
|
142
|
+
|
|
65
143
|
type QueryMessage =
|
|
66
144
|
| QueryAssistantMessage
|
|
67
145
|
| QueryResultMessage
|
|
68
146
|
| QueryStreamEventMessage
|
|
147
|
+
| QuerySystemInitMessage
|
|
148
|
+
| QueryTaskProgressMessage
|
|
149
|
+
| QueryTaskStartedMessage
|
|
150
|
+
| QueryToolProgressMessage
|
|
151
|
+
| QueryToolUseSummaryMessage
|
|
69
152
|
|
|
70
153
|
export interface ClaudeAgentClientOptions extends ClaudeProviderOptions {
|
|
71
154
|
onEvent?: ClaudeAgentEventHandler
|
|
@@ -90,6 +173,65 @@ export class ClaudeAgentClient
|
|
|
90
173
|
let structuredOutput: unknown = null
|
|
91
174
|
|
|
92
175
|
for await (const message of messages) {
|
|
176
|
+
if (
|
|
177
|
+
message.type === 'system' &&
|
|
178
|
+
message.subtype === 'init' &&
|
|
179
|
+
this.options.onEvent
|
|
180
|
+
) {
|
|
181
|
+
this.options.onEvent({
|
|
182
|
+
mcpServers: message.mcp_servers,
|
|
183
|
+
model: message.model,
|
|
184
|
+
permissionMode: message.permissionMode,
|
|
185
|
+
skills: message.skills,
|
|
186
|
+
tools: message.tools,
|
|
187
|
+
type: 'system.init',
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
message.type === 'system' &&
|
|
193
|
+
message.subtype === 'task_started' &&
|
|
194
|
+
this.options.onEvent
|
|
195
|
+
) {
|
|
196
|
+
this.options.onEvent({
|
|
197
|
+
description: message.description,
|
|
198
|
+
taskId: message.task_id,
|
|
199
|
+
type: 'task.started',
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (
|
|
204
|
+
message.type === 'system' &&
|
|
205
|
+
message.subtype === 'task_progress' &&
|
|
206
|
+
this.options.onEvent
|
|
207
|
+
) {
|
|
208
|
+
this.options.onEvent({
|
|
209
|
+
description: message.description,
|
|
210
|
+
taskId: message.task_id,
|
|
211
|
+
type: 'task.progress',
|
|
212
|
+
...(message.last_tool_name
|
|
213
|
+
? { lastToolName: message.last_tool_name }
|
|
214
|
+
: {}),
|
|
215
|
+
...(message.summary ? { summary: message.summary } : {}),
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (message.type === 'tool_progress' && this.options.onEvent) {
|
|
220
|
+
this.options.onEvent({
|
|
221
|
+
elapsedTimeSeconds: message.elapsed_time_seconds,
|
|
222
|
+
toolName: message.tool_name,
|
|
223
|
+
toolUseId: message.tool_use_id,
|
|
224
|
+
type: 'tool.progress',
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (message.type === 'tool_use_summary' && this.options.onEvent) {
|
|
229
|
+
this.options.onEvent({
|
|
230
|
+
summary: message.summary,
|
|
231
|
+
type: 'tool.summary',
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
93
235
|
if (message.type === 'stream_event' && this.options.onEvent) {
|
|
94
236
|
const event = message.event
|
|
95
237
|
if (
|
|
@@ -101,10 +243,6 @@ export class ClaudeAgentClient
|
|
|
101
243
|
}
|
|
102
244
|
}
|
|
103
245
|
|
|
104
|
-
if (message.type === 'assistant' && this.options.onEvent) {
|
|
105
|
-
this.options.onEvent({ type: 'assistant' })
|
|
106
|
-
}
|
|
107
|
-
|
|
108
246
|
if (message.type === 'result') {
|
|
109
247
|
if (message.subtype !== 'success') {
|
|
110
248
|
const detail = message.errors?.join('; ') ?? message.subtype
|
|
@@ -112,7 +250,12 @@ export class ClaudeAgentClient
|
|
|
112
250
|
}
|
|
113
251
|
structuredOutput = message.structured_output ?? null
|
|
114
252
|
if (this.options.onEvent) {
|
|
115
|
-
this.options.onEvent({
|
|
253
|
+
this.options.onEvent({
|
|
254
|
+
durationMs: message.duration_ms ?? 0,
|
|
255
|
+
numTurns: message.num_turns ?? 0,
|
|
256
|
+
subtype: 'success',
|
|
257
|
+
type: 'result',
|
|
258
|
+
})
|
|
116
259
|
}
|
|
117
260
|
}
|
|
118
261
|
}
|
|
@@ -138,12 +281,19 @@ export class ClaudeAgentClient
|
|
|
138
281
|
const queryOptions = {
|
|
139
282
|
allowDangerouslySkipPermissions: true,
|
|
140
283
|
cwd: this.options.workspaceRoot,
|
|
141
|
-
includePartialMessages: !!this.options.onEvent,
|
|
142
284
|
permissionMode: 'bypassPermissions',
|
|
143
285
|
outputFormat: {
|
|
144
286
|
schema: input.outputSchema,
|
|
145
287
|
type: 'json_schema',
|
|
146
288
|
},
|
|
289
|
+
...(this.options.onEvent
|
|
290
|
+
? {
|
|
291
|
+
agentProgressSummaries: true,
|
|
292
|
+
includePartialMessages: true,
|
|
293
|
+
}
|
|
294
|
+
: {
|
|
295
|
+
includePartialMessages: false,
|
|
296
|
+
}),
|
|
147
297
|
...(this.options.model ? { model: this.options.model } : {}),
|
|
148
298
|
...(this.options.effort ? { effort: this.options.effort } : {}),
|
|
149
299
|
} satisfies ClaudeQueryOptions
|
package/src/agents/codex.ts
CHANGED
|
@@ -58,11 +58,75 @@ export interface CodexTurnFailedError {
|
|
|
58
58
|
message: string
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export interface
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
export interface CodexAgentMessageItem {
|
|
62
|
+
id: string
|
|
63
|
+
text: string
|
|
64
|
+
type: 'agent_message'
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
export interface CodexReasoningItem {
|
|
68
|
+
id: string
|
|
69
|
+
text: string
|
|
70
|
+
type: 'reasoning'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface CodexCommandExecutionItem {
|
|
74
|
+
aggregated_output: string
|
|
75
|
+
command: string
|
|
76
|
+
exit_code?: number
|
|
77
|
+
id: string
|
|
78
|
+
status: 'completed' | 'failed' | 'in_progress'
|
|
79
|
+
type: 'command_execution'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface CodexFileChangeItem {
|
|
83
|
+
changes: { kind: 'add' | 'delete' | 'update'; path: string }[]
|
|
84
|
+
id: string
|
|
85
|
+
status: 'completed' | 'failed'
|
|
86
|
+
type: 'file_change'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface CodexMcpToolCallItem {
|
|
90
|
+
arguments: unknown
|
|
91
|
+
error?: { message: string }
|
|
92
|
+
id: string
|
|
93
|
+
result?: {
|
|
94
|
+
structured_content: unknown
|
|
95
|
+
}
|
|
96
|
+
server: string
|
|
97
|
+
status: 'completed' | 'failed' | 'in_progress'
|
|
98
|
+
tool: string
|
|
99
|
+
type: 'mcp_tool_call'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface CodexWebSearchItem {
|
|
103
|
+
id: string
|
|
104
|
+
query: string
|
|
105
|
+
type: 'web_search'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface CodexTodoListItem {
|
|
109
|
+
id: string
|
|
110
|
+
items: { completed: boolean; text: string }[]
|
|
111
|
+
type: 'todo_list'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface CodexErrorItem {
|
|
115
|
+
id: string
|
|
116
|
+
message: string
|
|
117
|
+
type: 'error'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type CodexItemPayload =
|
|
121
|
+
| CodexAgentMessageItem
|
|
122
|
+
| CodexCommandExecutionItem
|
|
123
|
+
| CodexErrorItem
|
|
124
|
+
| CodexFileChangeItem
|
|
125
|
+
| CodexMcpToolCallItem
|
|
126
|
+
| CodexReasoningItem
|
|
127
|
+
| CodexTodoListItem
|
|
128
|
+
| CodexWebSearchItem
|
|
129
|
+
|
|
66
130
|
export type CodexThreadEvent =
|
|
67
131
|
| CodexErrorEvent
|
|
68
132
|
| CodexItemEvent
|
|
@@ -147,7 +211,7 @@ export class CodexAgentClient implements ImplementerProvider, ReviewerProvider {
|
|
|
147
211
|
event.type === 'item.completed' &&
|
|
148
212
|
event.item.type === 'agent_message'
|
|
149
213
|
) {
|
|
150
|
-
finalResponse = event.item.text
|
|
214
|
+
finalResponse = event.item.text.trim()
|
|
151
215
|
}
|
|
152
216
|
}
|
|
153
217
|
|