@sebastianandreasson/pi-autonomous-agents 0.5.2 → 0.6.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,10 @@ pi/
60
60
 
61
61
  Typical scripts:
62
62
 
63
+ - `pi:once` / `pi:run` use default `sdk` transport
64
+ - `pi:mock` skips real agent execution
65
+
66
+
63
67
  ```json
64
68
  {
65
69
  "scripts": {
@@ -67,7 +71,8 @@ Typical scripts:
67
71
  "pi:once": "PI_CONFIG_FILE=pi.config.json pi-harness once",
68
72
  "pi:run": "PI_CONFIG_FILE=pi.config.json pi-harness run",
69
73
  "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"
74
+ "pi:visual:once": "PI_CONFIG_FILE=pi.config.json pi-harness visual-once",
75
+ "pi:visualize": "PI_CONFIG_FILE=pi.config.json pi-harness visualize"
71
76
  }
72
77
  }
73
78
  ```
@@ -82,7 +87,7 @@ pi-harness run
82
87
  pi-harness report
83
88
  pi-harness clear-history
84
89
  pi-harness visual-once
85
- pi-harness adapter
90
+ pi-harness visualize
86
91
  pi-harness visual-review-worker
87
92
  ```
88
93
 
@@ -115,6 +120,7 @@ The package supports:
115
120
  - one default visual-review model via `visualReviewModel`
116
121
  - optional per-role overrides via `roleModels`
117
122
  - per-model endpoint config in `models`
123
+ - default transport via `transport` (`sdk` or `mock`)
118
124
 
119
125
  Typical pattern:
120
126
 
@@ -173,8 +179,7 @@ Common fields in `pi.config.json`:
173
179
  - `taskFile`
174
180
  - `developerInstructionsFile`
175
181
  - `testerInstructionsFile`
176
- - `transport`
177
- - `adapterCommand`
182
+ - `transport` (`sdk` or `mock`)
178
183
  - `piModel`
179
184
  - `models`
180
185
  - `roleModels`
@@ -192,7 +197,7 @@ Common fields in `pi.config.json`:
192
197
 
193
198
  Key defaults:
194
199
 
195
- - `transport`: `adapter`
200
+ - `transport`: `sdk`
196
201
  - `commitMode`: `agent`
197
202
  - `promptMode`: `compact`
198
203
  - `piTools`: `read,edit,write,find,ls,bash`
@@ -209,7 +214,7 @@ The package is optimized for local models by default:
209
214
  - changed-file lists and feedback excerpts are capped
210
215
  - prompts prefer `read` for source inspection
211
216
  - shell is intended for `git`, tests, and narrow diagnostics
212
- - the adapter warns on obvious oversized shell-based file reads
217
+ - SDK transport carries forward oversized shell-read warnings and loop/timeout guards
213
218
  - the supervisor emits large-file/spec warnings when touched files are getting risky
214
219
 
215
220
  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 +237,7 @@ Recent versions of the package isolate each run more aggressively:
232
237
  - in-progress iteration state persisted before agent work starts
233
238
  - stale run locks recovered when the owning PID is gone
234
239
  - 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`
240
+ - parent-death watchers shut down orphaned supervisor layers instead of letting them continue under `PPID 1`
236
241
 
237
242
  That is meant to prevent orphaned timed-out agents or concurrent supervisors from corrupting shared state.
238
243
 
@@ -259,6 +264,15 @@ Useful files during a run:
259
264
 
260
265
  `pi-harness report` summarizes recent telemetry and surfaces things like terminal reasons and large-file warnings.
261
266
 
267
+ `pi-harness visualize` starts lightweight local web UI for orchestration flow. By default it listens on `127.0.0.1:4317`. Override with `PI_VISUALIZER_HOST` and `PI_VISUALIZER_PORT`.
268
+
269
+ Visualizer now includes:
270
+ - run history selector from `.pi-runtime/runs/`
271
+ - orchestration flow strip
272
+ - per-iteration stage graph with retries/rechecks
273
+ - clickable graph nodes and timeline rows that show full event JSON
274
+ - historical run summaries and per-run last output snapshots
275
+
262
276
  ## Visual Review Contract
263
277
 
264
278
  Visual review is optional and generic. The harness does not know how to navigate your app.
@@ -282,7 +296,7 @@ That clears configured harness runtime/history artifacts and verifies they are g
282
296
  - [SETUP.md](./SETUP.md)
283
297
  Agent-facing setup instructions for consuming repos.
284
298
  - [docs/PI_SUPERVISOR.md](./docs/PI_SUPERVISOR.md)
285
- More detailed flow, adapter, and runtime documentation.
299
+ More detailed flow, transport, telemetry, and runtime documentation.
286
300
  - [templates/PROJECT_SETUP.md](./templates/PROJECT_SETUP.md)
287
301
  Minimal consuming-repo layout summary.
288
302
 
package/SETUP.md CHANGED
@@ -82,6 +82,9 @@ 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:mock` is for setup validation when real PI execution is not ready yet.
87
+
85
88
  Add these scripts to the consuming repo `package.json`, adapting only if necessary:
86
89
 
87
90
  ```json
@@ -174,6 +177,8 @@ If the repo is not ready for a real run yet, at minimum run:
174
177
  PI_CONFIG_FILE=pi.config.json PI_TRANSPORT=mock PI_TEST_CMD= pi-harness once
175
178
  ```
176
179
 
180
+ Default transport is `sdk`. Only set `PI_TRANSPORT` when you explicitly want `mock`.
181
+
177
182
  If setup validation fails, fix the config rather than leaving a half-configured repo.
178
183
 
179
184
  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,15 @@ 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 visualize` serves polling web UI over local HTTP. Defaults: `PI_VISUALIZER_HOST=127.0.0.1`, `PI_VISUALIZER_PORT=4317`.
57
+
58
+ It 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.
59
+
53
60
  ## Config Contract
54
61
 
55
62
  Projects typically provide their own `pi.config.json` with fields such as:
@@ -98,67 +105,9 @@ For unattended inner-loop work, `testCommand` should be a bounded smoke gate rat
98
105
  The supervisor supports:
99
106
 
100
107
  - `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:
108
+ - `PI_TRANSPORT=sdk` (default)
156
109
 
157
- - `success`
158
- - `stalled`
159
- - `timed_out`
160
- - `failed`
161
- - `canceled`
110
+ `sdk` runs PI in-process via Node SDK. `mock` preserves harness flow but skips real agent work.
162
111
 
163
112
  ## Git Finalization
164
113
 
@@ -215,16 +164,16 @@ The capture command must write a JSON manifest at `PI_VISUAL_MANIFEST_FILE` with
215
164
 
216
165
  ## Loop Mitigation
217
166
 
218
- The built-in adapter mitigates obvious local loops by watching PI RPC tool events:
167
+ SDK transport mitigates obvious local loops by watching agent and tool events:
219
168
 
220
169
  - repeated identical tool calls are aborted
221
170
  - repeated same-path churn is aborted
222
171
  - a soft `continue` can be sent after inactivity
223
172
  - a separate tool-aware watchdog can tolerate long-running `bash` or browser work without treating the turn as dead
224
173
  - 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
174
+ - parent-loss shutdown tears down owned supervisor child work instead of allowing orphaned background runs
226
175
 
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.
176
+ 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
177
 
229
178
  ## Telemetry
230
179
 
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.6.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-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,5 @@ 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'
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,