@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 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 adapter integration
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 adapter
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`: `adapter`
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
- - the adapter warns on obvious oversized shell-based file reads
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 and adapter layers instead of letting them continue under `PPID 1`
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, adapter, and runtime documentation.
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:
@@ -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-rpc-adapter.mjs`: built-in adapter from supervisor JSON to `pi --mode rpc`
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 optional legacy git finalize step
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=adapter`
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
- - `success`
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
- The built-in adapter mitigates obvious local loops by watching PI RPC tool events:
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 the owned supervisor/adapter/PI child tree instead of allowing orphaned background runs
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 PI RPC events and active tool state, not raw shell output.
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.5.2",
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-rpc-adapter.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/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-telemetry.test.mjs && node --check test/fixtures/fake-pi.mjs",
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
@@ -1,6 +1,5 @@
1
1
  {
2
- "transport": "adapter",
3
- "adapterCommand": "pi-harness adapter",
2
+ "transport": "sdk",
4
3
  "instructionsFile": "",
5
4
  "taskFile": "TODOS.md",
6
5
  "commitMode": "agent",
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
- ['adapter', 'pi-rpc-adapter.mjs'],
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. Point PI_TRANSPORT=adapter at a real adapter to enable unattended work.',
50
+ 'Mock mode does not edit files. Use default sdk transport for real unattended work.',
44
51
  ].join('\n')
45
52
 
46
- await writeTextFile(config.lastAgentOutputFile, `${output}\n`)
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 parseAdapterResponse(stdout) {
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 adapter turn via: ${config.adapterCommand} iteration=${iteration} retry=${retryCount} reason=${reason}`
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
- if (result.timedOut) {
145
- await appendLog(config.logFile, 'Adapter turn timed out')
146
- return {
147
- sessionId: sessionId || '',
148
- sessionFile: sessionFile || '',
149
- status: 'timed_out',
150
- exitCode: result.exitCode,
151
- timedOut: true,
152
- durationSeconds: result.durationSeconds,
153
- output: result.combinedOutput,
154
- notes: 'Adapter process exceeded the configured timeout.',
155
- role: '',
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
- toolCalls: 0,
158
- toolErrors: 0,
159
- messageUpdates: 0,
160
- stopReason: '',
161
- loopDetected: false,
162
- loopSignature: '',
163
- terminalReason: 'agent_timeout',
164
- }
165
- }
166
-
167
- if (result.exitCode !== 0) {
168
- await appendLog(config.logFile, `Adapter turn failed with exit code ${result.exitCode}`)
169
- await writeTextFile(config.lastAgentOutputFile, result.combinedOutput)
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: result.exitCode,
130
+ exitCode: 1,
175
131
  timedOut: false,
176
- durationSeconds: result.durationSeconds,
177
- output: result.combinedOutput,
178
- notes: truncateForNotes(result.combinedOutput) || 'Adapter exited non-zero.',
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: 'adapter_failed',
143
+ terminalReason: 'sdk_failed',
188
144
  }
189
145
  }
190
146
 
191
- const response = parseAdapterResponse(result.stdout)
192
- await writeTextFile(config.lastAgentOutputFile, formatLastAgentOutput(response))
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: nextSessionId,
203
- sessionFile: nextSessionFile,
204
- status,
205
- exitCode: result.exitCode,
206
- timedOut: false,
207
- durationSeconds: result.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 === 'adapter') {
228
- return await runAdapterTurn(args)
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 "adapter".`)
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: readString('PI_TRANSPORT', file.transport, 'adapter'),
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,