@sebastianandreasson/pi-autonomous-agents 0.5.2 → 0.7.0
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 +28 -9
- package/SETUP.md +6 -0
- package/docs/PI_SUPERVISOR.md +16 -65
- package/package.json +6 -3
- package/pi.config.json +1 -2
- package/src/cli.mjs +1 -1
- package/src/index.mjs +3 -0
- package/src/pi-client.mjs +68 -119
- package/src/pi-config.mjs +3 -3
- package/src/pi-sdk-turn.mjs +654 -0
- package/src/pi-supervisor.mjs +73 -0
- package/src/pi-telemetry.mjs +4 -0
- package/src/pi-visualizer-server.mjs +522 -0
- package/src/pi-visualizer-shared.mjs +219 -0
- package/src/pi-visualizer.mjs +16 -0
- package/templates/pi.config.example.json +1 -2
- package/src/pi-rpc-adapter.mjs +0 -668
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ The package ships a top-level [SETUP.md](./SETUP.md) specifically for that workf
|
|
|
29
29
|
## What This Package Owns
|
|
30
30
|
|
|
31
31
|
- unattended loop orchestration
|
|
32
|
-
- PI
|
|
32
|
+
- PI Node SDK integration
|
|
33
33
|
- config loading
|
|
34
34
|
- prompt assembly
|
|
35
35
|
- verification/tester/visual-review handoff
|
|
@@ -60,6 +60,11 @@ pi/
|
|
|
60
60
|
|
|
61
61
|
Typical scripts:
|
|
62
62
|
|
|
63
|
+
- `pi:once` / `pi:run` use default `sdk` transport
|
|
64
|
+
- `pi:run` also hosts web UI on `127.0.0.1:4317` by default
|
|
65
|
+
- `pi:mock` skips real agent execution
|
|
66
|
+
|
|
67
|
+
|
|
63
68
|
```json
|
|
64
69
|
{
|
|
65
70
|
"scripts": {
|
|
@@ -67,7 +72,8 @@ Typical scripts:
|
|
|
67
72
|
"pi:once": "PI_CONFIG_FILE=pi.config.json pi-harness once",
|
|
68
73
|
"pi:run": "PI_CONFIG_FILE=pi.config.json pi-harness run",
|
|
69
74
|
"pi:report": "PI_CONFIG_FILE=pi.config.json pi-harness report",
|
|
70
|
-
"pi:visual:once": "PI_CONFIG_FILE=pi.config.json pi-harness visual-once"
|
|
75
|
+
"pi:visual:once": "PI_CONFIG_FILE=pi.config.json pi-harness visual-once",
|
|
76
|
+
"pi:visualize": "PI_CONFIG_FILE=pi.config.json pi-harness visualize"
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
```
|
|
@@ -82,7 +88,7 @@ pi-harness run
|
|
|
82
88
|
pi-harness report
|
|
83
89
|
pi-harness clear-history
|
|
84
90
|
pi-harness visual-once
|
|
85
|
-
pi-harness
|
|
91
|
+
pi-harness visualize
|
|
86
92
|
pi-harness visual-review-worker
|
|
87
93
|
```
|
|
88
94
|
|
|
@@ -115,6 +121,7 @@ The package supports:
|
|
|
115
121
|
- one default visual-review model via `visualReviewModel`
|
|
116
122
|
- optional per-role overrides via `roleModels`
|
|
117
123
|
- per-model endpoint config in `models`
|
|
124
|
+
- default transport via `transport` (`sdk` or `mock`)
|
|
118
125
|
|
|
119
126
|
Typical pattern:
|
|
120
127
|
|
|
@@ -173,8 +180,7 @@ Common fields in `pi.config.json`:
|
|
|
173
180
|
- `taskFile`
|
|
174
181
|
- `developerInstructionsFile`
|
|
175
182
|
- `testerInstructionsFile`
|
|
176
|
-
- `transport`
|
|
177
|
-
- `adapterCommand`
|
|
183
|
+
- `transport` (`sdk` or `mock`)
|
|
178
184
|
- `piModel`
|
|
179
185
|
- `models`
|
|
180
186
|
- `roleModels`
|
|
@@ -192,7 +198,7 @@ Common fields in `pi.config.json`:
|
|
|
192
198
|
|
|
193
199
|
Key defaults:
|
|
194
200
|
|
|
195
|
-
- `transport`: `
|
|
201
|
+
- `transport`: `sdk`
|
|
196
202
|
- `commitMode`: `agent`
|
|
197
203
|
- `promptMode`: `compact`
|
|
198
204
|
- `piTools`: `read,edit,write,find,ls,bash`
|
|
@@ -209,7 +215,7 @@ The package is optimized for local models by default:
|
|
|
209
215
|
- changed-file lists and feedback excerpts are capped
|
|
210
216
|
- prompts prefer `read` for source inspection
|
|
211
217
|
- shell is intended for `git`, tests, and narrow diagnostics
|
|
212
|
-
-
|
|
218
|
+
- SDK transport carries forward oversized shell-read warnings and loop/timeout guards
|
|
213
219
|
- the supervisor emits large-file/spec warnings when touched files are getting risky
|
|
214
220
|
|
|
215
221
|
This is deliberate. Large monolith files, huge e2e specs, and broad TODO items are one of the main causes of local-model drift and retry loops.
|
|
@@ -232,7 +238,7 @@ Recent versions of the package isolate each run more aggressively:
|
|
|
232
238
|
- in-progress iteration state persisted before agent work starts
|
|
233
239
|
- stale run locks recovered when the owning PID is gone
|
|
234
240
|
- timeout cleanup kills the full spawned process group, not only the direct child
|
|
235
|
-
- parent-death watchers shut down orphaned supervisor
|
|
241
|
+
- parent-death watchers shut down orphaned supervisor layers instead of letting them continue under `PPID 1`
|
|
236
242
|
|
|
237
243
|
That is meant to prevent orphaned timed-out agents or concurrent supervisors from corrupting shared state.
|
|
238
244
|
|
|
@@ -259,6 +265,19 @@ Useful files during a run:
|
|
|
259
265
|
|
|
260
266
|
`pi-harness report` summarizes recent telemetry and surfaces things like terminal reasons and large-file warnings.
|
|
261
267
|
|
|
268
|
+
`pi-harness run` now also starts lightweight local web UI for orchestration flow by default. By default it listens on `127.0.0.1:4317`. Override with `PI_VISUALIZER_HOST` and `PI_VISUALIZER_PORT`. Set `PI_VISUALIZER=0` to disable embedded web UI for a run.
|
|
269
|
+
|
|
270
|
+
Visualizer uses SSE for live updates instead of browser polling.
|
|
271
|
+
|
|
272
|
+
`pi-harness visualize` still exists as standalone viewer if you want to inspect run history without starting a new run.
|
|
273
|
+
|
|
274
|
+
Visualizer now includes:
|
|
275
|
+
- run history selector from `.pi-runtime/runs/`
|
|
276
|
+
- orchestration flow strip
|
|
277
|
+
- per-iteration stage graph with retries/rechecks
|
|
278
|
+
- clickable graph nodes and timeline rows that show full event JSON
|
|
279
|
+
- historical run summaries and per-run last output snapshots
|
|
280
|
+
|
|
262
281
|
## Visual Review Contract
|
|
263
282
|
|
|
264
283
|
Visual review is optional and generic. The harness does not know how to navigate your app.
|
|
@@ -282,7 +301,7 @@ That clears configured harness runtime/history artifacts and verifies they are g
|
|
|
282
301
|
- [SETUP.md](./SETUP.md)
|
|
283
302
|
Agent-facing setup instructions for consuming repos.
|
|
284
303
|
- [docs/PI_SUPERVISOR.md](./docs/PI_SUPERVISOR.md)
|
|
285
|
-
More detailed flow,
|
|
304
|
+
More detailed flow, transport, telemetry, and runtime documentation.
|
|
286
305
|
- [templates/PROJECT_SETUP.md](./templates/PROJECT_SETUP.md)
|
|
287
306
|
Minimal consuming-repo layout summary.
|
|
288
307
|
|
package/SETUP.md
CHANGED
|
@@ -82,6 +82,10 @@ Minimal example:
|
|
|
82
82
|
|
|
83
83
|
5. Add package scripts.
|
|
84
84
|
|
|
85
|
+
- `pi:once` and `pi:run` should use default `sdk` transport unless the repo has a very specific reason not to.
|
|
86
|
+
- `pi:run` will also host local orchestration web UI by default.
|
|
87
|
+
- `pi:mock` is for setup validation when real PI execution is not ready yet.
|
|
88
|
+
|
|
85
89
|
Add these scripts to the consuming repo `package.json`, adapting only if necessary:
|
|
86
90
|
|
|
87
91
|
```json
|
|
@@ -174,6 +178,8 @@ If the repo is not ready for a real run yet, at minimum run:
|
|
|
174
178
|
PI_CONFIG_FILE=pi.config.json PI_TRANSPORT=mock PI_TEST_CMD= pi-harness once
|
|
175
179
|
```
|
|
176
180
|
|
|
181
|
+
Default transport is `sdk`. Only set `PI_TRANSPORT` when you explicitly want `mock`.
|
|
182
|
+
|
|
177
183
|
If setup validation fails, fix the config rather than leaving a half-configured repo.
|
|
178
184
|
|
|
179
185
|
The harness should fail fast if:
|
package/docs/PI_SUPERVISOR.md
CHANGED
|
@@ -28,13 +28,15 @@ Main package files:
|
|
|
28
28
|
|
|
29
29
|
- `src/pi-supervisor.mjs`: controller
|
|
30
30
|
- `src/pi-client.mjs`: transport layer
|
|
31
|
-
- `src/pi-
|
|
31
|
+
- `src/pi-sdk-turn.mjs`: in-process PI SDK turn runner for `transport: "sdk"`
|
|
32
32
|
- `src/pi-config.mjs`: config loader
|
|
33
|
-
- `src/pi-repo.mjs`: repo helpers, verification runner, and
|
|
33
|
+
- `src/pi-repo.mjs`: repo helpers, verification runner, and git finalize helpers
|
|
34
34
|
- `src/pi-telemetry.mjs`: telemetry writer/reader
|
|
35
35
|
- `src/pi-prompts.mjs`: default prompt builders
|
|
36
36
|
- `src/pi-visual-review.mjs`: multimodal visual-review worker
|
|
37
37
|
- `src/pi-visual-once.mjs`: one-shot manual visual review runner
|
|
38
|
+
- `src/pi-visualizer.mjs`: local web UI for orchestration flow and active stage
|
|
39
|
+
- `src/pi-visualizer-shared.mjs`: flow-state helpers for visualizer
|
|
38
40
|
- `src/pi-report.mjs`: telemetry summary report
|
|
39
41
|
- `templates/DEVELOPER.md`: default developer-role instructions template
|
|
40
42
|
- `templates/TESTER.md`: default tester-role instructions template
|
|
@@ -46,10 +48,17 @@ pi-harness once
|
|
|
46
48
|
pi-harness run
|
|
47
49
|
pi-harness report
|
|
48
50
|
pi-harness visual-once
|
|
51
|
+
pi-harness visualize
|
|
49
52
|
```
|
|
50
53
|
|
|
51
54
|
The package reads `PI_CONFIG_FILE` if provided. Otherwise it falls back to the bundled generic `pi.config.json`.
|
|
52
55
|
|
|
56
|
+
`pi-harness run` also starts SSE-backed web UI over local HTTP by default. Defaults: `PI_VISUALIZER_HOST=127.0.0.1`, `PI_VISUALIZER_PORT=4317`. Set `PI_VISUALIZER=0` to disable it for a run.
|
|
57
|
+
|
|
58
|
+
`pi-harness visualize` remains available as standalone viewer.
|
|
59
|
+
|
|
60
|
+
Visualizer reads active-run lock, per-run state, per-run iteration summary, per-run last output snapshot, and telemetry to show current stage plus historical runs.
|
|
61
|
+
|
|
53
62
|
## Config Contract
|
|
54
63
|
|
|
55
64
|
Projects typically provide their own `pi.config.json` with fields such as:
|
|
@@ -98,67 +107,9 @@ For unattended inner-loop work, `testCommand` should be a bounded smoke gate rat
|
|
|
98
107
|
The supervisor supports:
|
|
99
108
|
|
|
100
109
|
- `PI_TRANSPORT=mock`
|
|
101
|
-
- `PI_TRANSPORT=
|
|
102
|
-
|
|
103
|
-
The built-in adapter command is typically:
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
pi-harness adapter
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
When using `adapter`, set `PI_ADAPTER_COMMAND` to a command that:
|
|
110
|
-
|
|
111
|
-
1. Reads one JSON request from `stdin`
|
|
112
|
-
2. Talks to PI RPC or your own PI wrapper
|
|
113
|
-
3. Writes one JSON response to `stdout`
|
|
114
|
-
4. Exits with code `0` on success
|
|
115
|
-
|
|
116
|
-
Request shape:
|
|
117
|
-
|
|
118
|
-
```json
|
|
119
|
-
{
|
|
120
|
-
"sessionId": "existing-or-empty",
|
|
121
|
-
"sessionFile": "/absolute/path/to/session.jsonl",
|
|
122
|
-
"prompt": "controller prompt",
|
|
123
|
-
"cwd": "/absolute/repo/path",
|
|
124
|
-
"taskFile": "/absolute/repo/path/TODOS.md",
|
|
125
|
-
"instructionsFile": "/absolute/repo/path/pi/DEVELOPER.md",
|
|
126
|
-
"runtimeDir": "/absolute/repo/path/.pi-runtime",
|
|
127
|
-
"piCli": "pi",
|
|
128
|
-
"model": "local/model-name",
|
|
129
|
-
"tools": "read,edit,write,find,ls,bash",
|
|
130
|
-
"thinking": "",
|
|
131
|
-
"noExtensions": false,
|
|
132
|
-
"noSkills": false,
|
|
133
|
-
"noPromptTemplates": false,
|
|
134
|
-
"noThemes": true,
|
|
135
|
-
"metadata": {
|
|
136
|
-
"iteration": 1,
|
|
137
|
-
"retryCount": 0,
|
|
138
|
-
"reason": "main_workflow"
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Response shape:
|
|
144
|
-
|
|
145
|
-
```json
|
|
146
|
-
{
|
|
147
|
-
"sessionId": "stable-session-id",
|
|
148
|
-
"sessionFile": "/absolute/path/to/session.jsonl",
|
|
149
|
-
"status": "success",
|
|
150
|
-
"output": "agent output text",
|
|
151
|
-
"notes": "short controller note"
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Allowed response `status` values:
|
|
110
|
+
- `PI_TRANSPORT=sdk` (default)
|
|
156
111
|
|
|
157
|
-
- `
|
|
158
|
-
- `stalled`
|
|
159
|
-
- `timed_out`
|
|
160
|
-
- `failed`
|
|
161
|
-
- `canceled`
|
|
112
|
+
`sdk` runs PI in-process via Node SDK. `mock` preserves harness flow but skips real agent work.
|
|
162
113
|
|
|
163
114
|
## Git Finalization
|
|
164
115
|
|
|
@@ -215,16 +166,16 @@ The capture command must write a JSON manifest at `PI_VISUAL_MANIFEST_FILE` with
|
|
|
215
166
|
|
|
216
167
|
## Loop Mitigation
|
|
217
168
|
|
|
218
|
-
|
|
169
|
+
SDK transport mitigates obvious local loops by watching agent and tool events:
|
|
219
170
|
|
|
220
171
|
- repeated identical tool calls are aborted
|
|
221
172
|
- repeated same-path churn is aborted
|
|
222
173
|
- a soft `continue` can be sent after inactivity
|
|
223
174
|
- a separate tool-aware watchdog can tolerate long-running `bash` or browser work without treating the turn as dead
|
|
224
175
|
- a hard no-event timeout aborts a wedged turn instead of hanging indefinitely
|
|
225
|
-
- parent-loss shutdown tears down
|
|
176
|
+
- parent-loss shutdown tears down owned supervisor child work instead of allowing orphaned background runs
|
|
226
177
|
|
|
227
|
-
Important: terminal streaming does not reset the heartbeat by itself. The watchdog keys off
|
|
178
|
+
Important: terminal streaming does not reset the heartbeat by itself. The watchdog keys off SDK agent events and active tool state, not raw shell output.
|
|
228
179
|
|
|
229
180
|
## Telemetry
|
|
230
181
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sebastianandreasson/pi-autonomous-agents",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Portable unattended PI harness for developer/tester/visual-review loops.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
"bin": {
|
|
16
16
|
"pi-harness": "./src/cli.mjs"
|
|
17
17
|
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@mariozechner/pi-coding-agent": "^0.66.1"
|
|
20
|
+
},
|
|
18
21
|
"scripts": {
|
|
19
|
-
"check": "node --check src/cli.mjs && node --check src/pi-clear-history.mjs && node --check src/pi-client.mjs && node --check src/pi-config.mjs && node --check src/pi-flow.mjs && node --check src/pi-heartbeat.mjs && node --check src/pi-history.mjs && node --check src/pi-preflight.mjs && node --check src/pi-prompts.mjs && node --check src/pi-repo.mjs && node --check src/pi-report.mjs && node --check src/pi-
|
|
20
|
-
"test": "node --test test/pi-heartbeat.test.mjs test/pi-lifecycle.test.mjs test/pi-role-models.test.mjs test/pi-flow.test.mjs test/pi-history.test.mjs test/pi-prompts.test.mjs test/pi-preflight.test.mjs test/pi-repo.test.mjs test/pi-telemetry.test.mjs"
|
|
22
|
+
"check": "node --check src/cli.mjs && node --check src/pi-clear-history.mjs && node --check src/pi-client.mjs && node --check src/pi-config.mjs && node --check src/pi-flow.mjs && node --check src/pi-heartbeat.mjs && node --check src/pi-history.mjs && node --check src/pi-preflight.mjs && node --check src/pi-prompts.mjs && node --check src/pi-repo.mjs && node --check src/pi-report.mjs && node --check src/pi-sdk-turn.mjs && node --check src/pi-supervisor.mjs && node --check src/pi-telemetry.mjs && node --check src/pi-visual-once.mjs && node --check src/pi-visual-review.mjs && node --check src/pi-visualizer.mjs && node --check src/pi-visualizer-server.mjs && node --check src/pi-visualizer-shared.mjs && node --check src/index.mjs && node --check test/pi-heartbeat.test.mjs && node --check test/pi-lifecycle.test.mjs && node --check test/pi-role-models.test.mjs && node --check test/pi-flow.test.mjs && node --check test/pi-history.test.mjs && node --check test/pi-prompts.test.mjs && node --check test/pi-preflight.test.mjs && node --check test/pi-repo.test.mjs && node --check test/pi-sdk-supervisor.test.mjs && node --check test/pi-sdk-turn.test.mjs && node --check test/pi-telemetry.test.mjs && node --check test/pi-visualizer-shared.test.mjs && node --check test/fixtures/fake-pi.mjs && node --check test/fixtures/fake-pi-sdk.mjs",
|
|
23
|
+
"test": "node --test test/pi-heartbeat.test.mjs test/pi-lifecycle.test.mjs test/pi-role-models.test.mjs test/pi-flow.test.mjs test/pi-history.test.mjs test/pi-prompts.test.mjs test/pi-preflight.test.mjs test/pi-repo.test.mjs test/pi-sdk-supervisor.test.mjs test/pi-sdk-turn.test.mjs test/pi-telemetry.test.mjs test/pi-visualizer-shared.test.mjs"
|
|
21
24
|
},
|
|
22
25
|
"files": [
|
|
23
26
|
"src",
|
package/pi.config.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -18,7 +18,7 @@ const COMMANDS = new Map([
|
|
|
18
18
|
['report', 'pi-report.mjs'],
|
|
19
19
|
['clear-history', 'pi-clear-history.mjs'],
|
|
20
20
|
['visual-once', 'pi-visual-once.mjs'],
|
|
21
|
-
['
|
|
21
|
+
['visualize', 'pi-visualizer.mjs'],
|
|
22
22
|
['visual-review-worker', 'pi-visual-review.mjs'],
|
|
23
23
|
])
|
|
24
24
|
|
package/src/index.mjs
CHANGED
|
@@ -12,3 +12,6 @@ export {
|
|
|
12
12
|
export { clearHarnessHistory, collectHistoryTargets } from './pi-history.mjs'
|
|
13
13
|
export { collectLargeFileWarnings } from './pi-repo.mjs'
|
|
14
14
|
export { runAgentTurn } from './pi-client.mjs'
|
|
15
|
+
export { createSdkSession, createTools, normalizeToolNames, resolveModel, runSdkTurn, runSdkTurnWithPi, splitModelSpec } from './pi-sdk-turn.mjs'
|
|
16
|
+
export { deriveCurrentIteration, deriveFlowSnapshot, deriveStageGraph, formatActiveLabel, getFlowSteps, getLabelForKind, getStepKeyForActiveRun, getStepKeyForKind } from './pi-visualizer-shared.mjs'
|
|
17
|
+
export { buildSnapshot, readVisualizerHost, readVisualizerPort, renderHtml, startVisualizerServer } from './pi-visualizer-server.mjs'
|
package/src/pi-client.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import {
|
|
3
3
|
appendLog,
|
|
4
|
-
runShellCommand,
|
|
5
4
|
writeTextFile,
|
|
6
5
|
} from './pi-repo.mjs'
|
|
6
|
+
import { runSdkTurn } from './pi-sdk-turn.mjs'
|
|
7
7
|
|
|
8
8
|
function truncateForNotes(text) {
|
|
9
9
|
const trimmed = text.trim()
|
|
@@ -30,6 +30,13 @@ function formatLastAgentOutput(response) {
|
|
|
30
30
|
return `${sections.join('\n')}\n`
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
async function writeAgentOutputSnapshot(config, content) {
|
|
34
|
+
await writeTextFile(config.lastAgentOutputFile, content)
|
|
35
|
+
if (config.runLastAgentOutputFile && config.runLastAgentOutputFile !== config.lastAgentOutputFile) {
|
|
36
|
+
await writeTextFile(config.runLastAgentOutputFile, content)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
async function runMockTurn({ config, sessionId, sessionFile, prompt, reason }) {
|
|
34
41
|
const nextSessionId = sessionId || `mock-${randomUUID()}`
|
|
35
42
|
const nextSessionFile = sessionFile || `${config.piRuntimeDir}/mock-${nextSessionId}.jsonl`
|
|
@@ -40,10 +47,10 @@ async function runMockTurn({ config, sessionId, sessionFile, prompt, reason }) {
|
|
|
40
47
|
'Prompt preview:',
|
|
41
48
|
prompt,
|
|
42
49
|
'',
|
|
43
|
-
'Mock mode does not edit files.
|
|
50
|
+
'Mock mode does not edit files. Use default sdk transport for real unattended work.',
|
|
44
51
|
].join('\n')
|
|
45
52
|
|
|
46
|
-
await
|
|
53
|
+
await writeAgentOutputSnapshot(config, `${output}\n`)
|
|
47
54
|
await appendLog(config.logFile, `Mock agent turn completed for session ${nextSessionId}`)
|
|
48
55
|
if (config.streamTerminal) {
|
|
49
56
|
process.stderr.write(`[PI mock] ${reason}\n`)
|
|
@@ -71,111 +78,60 @@ async function runMockTurn({ config, sessionId, sessionFile, prompt, reason }) {
|
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
80
|
|
|
74
|
-
function
|
|
75
|
-
const trimmed = stdout.trim()
|
|
76
|
-
if (trimmed === '') {
|
|
77
|
-
throw new Error('Adapter returned no JSON on stdout.')
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
return JSON.parse(trimmed)
|
|
82
|
-
} catch {
|
|
83
|
-
const lines = trimmed.split('\n').map((line) => line.trim()).filter(Boolean)
|
|
84
|
-
const lastLine = lines.at(-1)
|
|
85
|
-
if (!lastLine) {
|
|
86
|
-
throw new Error('Adapter returned no parseable JSON on stdout.')
|
|
87
|
-
}
|
|
88
|
-
return JSON.parse(lastLine)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function runAdapterTurn({ config, model, sessionId, sessionFile, prompt, iteration, retryCount, reason }) {
|
|
93
|
-
if (config.adapterCommand.trim() === '') {
|
|
94
|
-
throw new Error('PI_TRANSPORT=adapter requires PI_ADAPTER_COMMAND to be set.')
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const request = {
|
|
98
|
-
sessionId,
|
|
99
|
-
sessionFile,
|
|
100
|
-
prompt,
|
|
101
|
-
cwd: config.cwd,
|
|
102
|
-
taskFile: config.taskFile,
|
|
103
|
-
instructionsFile: config.instructionsFile,
|
|
104
|
-
developerInstructionsFile: config.developerInstructionsFile,
|
|
105
|
-
testerInstructionsFile: config.testerInstructionsFile,
|
|
106
|
-
runtimeDir: config.runRuntimeDir || config.piRuntimeDir,
|
|
107
|
-
piCli: config.piCli,
|
|
108
|
-
model: model ?? config.piModel,
|
|
109
|
-
tools: config.piTools,
|
|
110
|
-
thinking: config.piThinking,
|
|
111
|
-
noExtensions: config.piNoExtensions,
|
|
112
|
-
noSkills: config.piNoSkills,
|
|
113
|
-
noPromptTemplates: config.piNoPromptTemplates,
|
|
114
|
-
noThemes: config.piNoThemes,
|
|
115
|
-
streamTerminal: config.streamTerminal,
|
|
116
|
-
loopRepeatThreshold: config.loopRepeatThreshold,
|
|
117
|
-
samePathRepeatThreshold: config.samePathRepeatThreshold,
|
|
118
|
-
continueAfterSeconds: config.continueAfterSeconds,
|
|
119
|
-
toolContinueAfterSeconds: config.toolContinueAfterSeconds,
|
|
120
|
-
continueMessage: config.continueMessage,
|
|
121
|
-
noEventTimeoutSeconds: config.noEventTimeoutSeconds,
|
|
122
|
-
toolNoEventTimeoutSeconds: config.toolNoEventTimeoutSeconds,
|
|
123
|
-
metadata: {
|
|
124
|
-
iteration,
|
|
125
|
-
retryCount,
|
|
126
|
-
reason,
|
|
127
|
-
},
|
|
128
|
-
}
|
|
129
|
-
|
|
81
|
+
async function runSdkTransportTurn({ config, model, sessionId, sessionFile, prompt, iteration, retryCount, reason }) {
|
|
130
82
|
await appendLog(
|
|
131
83
|
config.logFile,
|
|
132
|
-
`Starting
|
|
84
|
+
`Starting SDK turn iteration=${iteration} retry=${retryCount} reason=${reason}`
|
|
133
85
|
)
|
|
134
|
-
const result = await runShellCommand({
|
|
135
|
-
cwd: config.cwd,
|
|
136
|
-
command: config.adapterCommand,
|
|
137
|
-
timeoutSeconds: config.agentTimeoutSeconds,
|
|
138
|
-
stdinText: `${JSON.stringify(request)}\n`,
|
|
139
|
-
streamStderrToParent: config.streamTerminal,
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
await writeTextFile(config.lastAgentOutputFile, result.combinedOutput)
|
|
143
86
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
87
|
+
const startedAt = Date.now()
|
|
88
|
+
let response
|
|
89
|
+
try {
|
|
90
|
+
response = await runSdkTurn({
|
|
91
|
+
sessionId,
|
|
92
|
+
sessionFile,
|
|
93
|
+
prompt,
|
|
94
|
+
cwd: config.cwd,
|
|
95
|
+
taskFile: config.taskFile,
|
|
96
|
+
instructionsFile: config.instructionsFile,
|
|
97
|
+
developerInstructionsFile: config.developerInstructionsFile,
|
|
98
|
+
testerInstructionsFile: config.testerInstructionsFile,
|
|
99
|
+
runtimeDir: config.runRuntimeDir || config.piRuntimeDir,
|
|
100
|
+
piCli: config.piCli,
|
|
156
101
|
model: model ?? config.piModel,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
102
|
+
tools: config.piTools,
|
|
103
|
+
thinking: config.piThinking,
|
|
104
|
+
noExtensions: config.piNoExtensions,
|
|
105
|
+
noSkills: config.piNoSkills,
|
|
106
|
+
noPromptTemplates: config.piNoPromptTemplates,
|
|
107
|
+
noThemes: config.piNoThemes,
|
|
108
|
+
streamTerminal: config.streamTerminal,
|
|
109
|
+
loopRepeatThreshold: config.loopRepeatThreshold,
|
|
110
|
+
samePathRepeatThreshold: config.samePathRepeatThreshold,
|
|
111
|
+
continueAfterSeconds: config.continueAfterSeconds,
|
|
112
|
+
toolContinueAfterSeconds: config.toolContinueAfterSeconds,
|
|
113
|
+
continueMessage: config.continueMessage,
|
|
114
|
+
noEventTimeoutSeconds: config.noEventTimeoutSeconds,
|
|
115
|
+
toolNoEventTimeoutSeconds: config.toolNoEventTimeoutSeconds,
|
|
116
|
+
metadata: {
|
|
117
|
+
iteration,
|
|
118
|
+
retryCount,
|
|
119
|
+
reason,
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const notes = error instanceof Error ? error.message : String(error)
|
|
124
|
+
await writeAgentOutputSnapshot(config, `${notes}\n`)
|
|
125
|
+
await appendLog(config.logFile, `SDK turn failed: ${notes}`)
|
|
170
126
|
return {
|
|
171
127
|
sessionId: sessionId || '',
|
|
172
128
|
sessionFile: sessionFile || '',
|
|
173
129
|
status: 'failed',
|
|
174
|
-
exitCode:
|
|
130
|
+
exitCode: 1,
|
|
175
131
|
timedOut: false,
|
|
176
|
-
durationSeconds:
|
|
177
|
-
output:
|
|
178
|
-
notes
|
|
132
|
+
durationSeconds: Math.max(0, Math.round((Date.now() - startedAt) / 1000)),
|
|
133
|
+
output: '',
|
|
134
|
+
notes,
|
|
179
135
|
role: '',
|
|
180
136
|
model: model ?? config.piModel,
|
|
181
137
|
toolCalls: 0,
|
|
@@ -184,29 +140,22 @@ async function runAdapterTurn({ config, model, sessionId, sessionFile, prompt, i
|
|
|
184
140
|
stopReason: '',
|
|
185
141
|
loopDetected: false,
|
|
186
142
|
loopSignature: '',
|
|
187
|
-
terminalReason: '
|
|
143
|
+
terminalReason: 'sdk_failed',
|
|
188
144
|
}
|
|
189
145
|
}
|
|
190
146
|
|
|
191
|
-
|
|
192
|
-
await
|
|
193
|
-
const nextSessionId = String(response.sessionId ?? sessionId ?? '')
|
|
194
|
-
const nextSessionFile = String(response.sessionFile ?? sessionFile ?? '')
|
|
195
|
-
const status = String(response.status ?? 'success')
|
|
196
|
-
const output = String(response.output ?? result.combinedOutput)
|
|
197
|
-
const notes = String(response.notes ?? truncateForNotes(output))
|
|
198
|
-
|
|
199
|
-
await appendLog(config.logFile, `Adapter turn completed with status ${status}`)
|
|
147
|
+
await writeAgentOutputSnapshot(config, formatLastAgentOutput(response))
|
|
148
|
+
await appendLog(config.logFile, `SDK turn completed with status ${String(response.status ?? 'success')}`)
|
|
200
149
|
|
|
201
150
|
return {
|
|
202
|
-
sessionId:
|
|
203
|
-
sessionFile:
|
|
204
|
-
status,
|
|
205
|
-
exitCode:
|
|
206
|
-
timedOut:
|
|
207
|
-
durationSeconds:
|
|
208
|
-
output,
|
|
209
|
-
notes,
|
|
151
|
+
sessionId: String(response.sessionId ?? sessionId ?? ''),
|
|
152
|
+
sessionFile: String(response.sessionFile ?? sessionFile ?? ''),
|
|
153
|
+
status: String(response.status ?? 'success'),
|
|
154
|
+
exitCode: 0,
|
|
155
|
+
timedOut: String(response.status ?? '') === 'timed_out',
|
|
156
|
+
durationSeconds: Math.max(0, Math.round((Date.now() - startedAt) / 1000)),
|
|
157
|
+
output: String(response.output ?? ''),
|
|
158
|
+
notes: String(response.notes ?? ''),
|
|
210
159
|
role: String(response.role ?? ''),
|
|
211
160
|
model: String(response.model ?? model ?? config.piModel ?? ''),
|
|
212
161
|
toolCalls: Number.isFinite(Number(response.toolCalls)) ? Number(response.toolCalls) : 0,
|
|
@@ -224,9 +173,9 @@ export async function runAgentTurn(args) {
|
|
|
224
173
|
return await runMockTurn(args)
|
|
225
174
|
}
|
|
226
175
|
|
|
227
|
-
if (args.config.transport === '
|
|
228
|
-
return await
|
|
176
|
+
if (args.config.transport === 'sdk') {
|
|
177
|
+
return await runSdkTransportTurn(args)
|
|
229
178
|
}
|
|
230
179
|
|
|
231
|
-
throw new Error(`Unsupported PI transport "${args.config.transport}". Expected "mock" or "
|
|
180
|
+
throw new Error(`Unsupported PI transport "${args.config.transport}". Expected "mock" or "sdk".`)
|
|
232
181
|
}
|
package/src/pi-config.mjs
CHANGED
|
@@ -196,7 +196,6 @@ export function loadConfig(mode = 'once') {
|
|
|
196
196
|
const cwd = process.cwd()
|
|
197
197
|
const repoConfig = readRepoConfig(cwd)
|
|
198
198
|
const file = repoConfig.values
|
|
199
|
-
const bundledAdapterCommand = 'pi-harness adapter'
|
|
200
199
|
const bundledDeveloperInstructionsFile = path.join(packageRoot, 'templates', 'DEVELOPER.md')
|
|
201
200
|
const bundledTesterInstructionsFile = path.join(packageRoot, 'templates', 'TESTER.md')
|
|
202
201
|
const modelProfiles = readObject('models', file.models, {})
|
|
@@ -222,13 +221,14 @@ export function loadConfig(mode = 'once') {
|
|
|
222
221
|
: bundledTesterInstructionsFile
|
|
223
222
|
)
|
|
224
223
|
|
|
224
|
+
const transport = readString('PI_TRANSPORT', file.transport, 'sdk')
|
|
225
|
+
|
|
225
226
|
return {
|
|
226
227
|
cwd,
|
|
227
228
|
configFile: repoConfig.configFile,
|
|
228
229
|
mode: mode === 'run' ? 'run' : 'once',
|
|
229
|
-
transport
|
|
230
|
+
transport,
|
|
230
231
|
agentName: readString('PI_AGENT_NAME', file.agentName, 'PI'),
|
|
231
|
-
adapterCommand: readString('PI_ADAPTER_COMMAND', file.adapterCommand, bundledAdapterCommand),
|
|
232
232
|
taskFile: resolveFromCwd(cwd, 'PI_TASK_FILE', file.taskFile, 'TODOS.md'),
|
|
233
233
|
instructionsFile: resolveInstructionsFile(cwd, 'PI_INSTRUCTIONS_FILE', file.instructionsFile, bundledDeveloperInstructionsFile),
|
|
234
234
|
developerInstructionsFile,
|