@sebastianandreasson/pi-autonomous-agents 0.1.0 → 0.3.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 +21 -1
- package/SETUP.md +43 -0
- package/docs/PI_SUPERVISOR.md +7 -9
- package/package.json +3 -3
- package/pi.config.json +1 -0
- package/src/cli.mjs +1 -0
- package/src/index.mjs +6 -0
- package/src/pi-clear-history.mjs +26 -0
- package/src/pi-client.mjs +37 -0
- package/src/pi-config.mjs +48 -17
- package/src/pi-history.mjs +92 -0
- package/src/pi-preflight.mjs +253 -0
- package/src/pi-prompts.mjs +308 -110
- package/src/pi-repo.mjs +6 -3
- package/src/pi-rpc-adapter.mjs +31 -0
- package/src/pi-supervisor.mjs +432 -27
- package/src/pi-telemetry.mjs +14 -1
- package/templates/pi.config.example.json +2 -1
package/README.md
CHANGED
|
@@ -59,6 +59,7 @@ packages/pi-harness/
|
|
|
59
59
|
pi-harness once
|
|
60
60
|
pi-harness run
|
|
61
61
|
pi-harness report
|
|
62
|
+
pi-harness clear-history
|
|
62
63
|
pi-harness visual-once
|
|
63
64
|
pi-harness adapter
|
|
64
65
|
pi-harness visual-review-worker
|
|
@@ -82,12 +83,25 @@ Find SETUP.md in @sebastianandreasson/pi-autonomous-agents and set everything up
|
|
|
82
83
|
|
|
83
84
|
The package ships a top-level [SETUP.md](./SETUP.md) specifically for that workflow.
|
|
84
85
|
|
|
86
|
+
If you want to wipe all harness-generated state and start over cleanly in a repo, run:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
PI_CONFIG_FILE=pi.config.json pi-harness clear-history
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The command removes configured harness history/runtime files and verifies that no configured history paths remain afterward.
|
|
93
|
+
|
|
94
|
+
For prompt debugging, the harness also writes the exact assembled prompt for the current role to `.pi-last-prompt.txt` by default.
|
|
95
|
+
For flow debugging, it also writes a machine-readable `.pi-last-iteration.json` summary with the selected task, tester verdict, commit-plan state, and terminal reason.
|
|
96
|
+
|
|
85
97
|
## Generic Contracts
|
|
86
98
|
|
|
87
99
|
- `taskFile`: usually `TODOS.md`
|
|
88
100
|
- `developerInstructionsFile`: per-project developer instructions
|
|
89
101
|
- `testerInstructionsFile`: per-project tester instructions
|
|
90
102
|
- `roleModels`: optional per-role model overrides
|
|
103
|
+
- `commitMode`: `agent` by default, `plan` only for legacy harness-managed commit parsing
|
|
104
|
+
- `promptMode`: `compact` by default
|
|
91
105
|
- `testCommand`: fast verification command
|
|
92
106
|
- `visualCaptureCommand`: project-defined screenshot capture command
|
|
93
107
|
- `visualFeedbackFile`: latest visual-review handoff
|
|
@@ -95,8 +109,14 @@ The package ships a top-level [SETUP.md](./SETUP.md) specifically for that workf
|
|
|
95
109
|
|
|
96
110
|
For unattended loops, keep `testCommand` fast and bounded, such as a smoke suite. Long real-time Playwright happy-path specs belong in an explicit nightly or post-run lane, not the default developer/tester inner loop.
|
|
97
111
|
|
|
112
|
+
Keep TODO items extremely small and implementation-shaped when using weaker local models. Broad tasks tend to produce much longer turns, more retries, and more tester drift than narrow one-step tasks.
|
|
113
|
+
|
|
98
114
|
The adapter heartbeat is PI-RPC-event based. Streaming shell output does not count as progress on its own, so long-running tools should rely on the tool-aware watchdog thresholds rather than terminal streaming.
|
|
99
115
|
|
|
100
|
-
`piModel` remains the default text model, but you can override specific roles with `roleModels` such as `developer`, `developerRetry`, `developerFix`, `tester`, `testerCommit
|
|
116
|
+
`piModel` remains the default text model, but you can override specific roles with `roleModels` such as `developer`, `developerRetry`, `developerFix`, `tester`, and `visualReview`. `testerCommit` is only relevant if you opt back into `commitMode: "plan"`.
|
|
117
|
+
|
|
118
|
+
By default, successful tester passes should stage and create the commit directly in the same PI turn. The old commit-plan parsing flow is still available as `commitMode: "plan"`, but it is now a compatibility mode rather than the default.
|
|
119
|
+
|
|
120
|
+
Prompt/context handoff is compact by default. The harness now caps prior feedback excerpts, changed-file lists, verification excerpts, and prompt note handoff. If needed, tune `maxPromptChangedFiles`, `maxVisualFeedbackLines`, `maxTesterFeedbackLines`, `maxPromptNotesLines`, and `maxVerificationExcerptLines`.
|
|
101
121
|
|
|
102
122
|
The harness expects screenshot capture to produce a `manifest.json` plus image files under the configured visual capture directory.
|
package/SETUP.md
CHANGED
|
@@ -46,10 +46,16 @@ If the repo uses another package manager already, use the repo-native equivalent
|
|
|
46
46
|
- `taskFile`: usually `TODOS.md`
|
|
47
47
|
- `developerInstructionsFile`: `pi/DEVELOPER.md`
|
|
48
48
|
- `testerInstructionsFile`: `pi/TESTER.md`
|
|
49
|
+
- `commitMode`: normally `agent`
|
|
49
50
|
- `testCommand`: a fast bounded verification command for this repo
|
|
50
51
|
- `visualCaptureCommand`: only if this repo has a real screenshot capture flow
|
|
51
52
|
- `models` / `piModel` / `visualReviewModel` / `roleModels`: configure the models actually available in this environment
|
|
52
53
|
|
|
54
|
+
Important:
|
|
55
|
+
|
|
56
|
+
- Do not assume a local provider’s served model id matches a GGUF filename or a guessed name.
|
|
57
|
+
- If the repo uses custom OpenAI-compatible providers, verify the exact served ids from each provider’s `/v1/models` response before finalizing `piModel`, `visualReviewModel`, or `roleModels`.
|
|
58
|
+
|
|
53
59
|
3. Create role instruction files.
|
|
54
60
|
|
|
55
61
|
- Copy `node_modules/@sebastianandreasson/pi-autonomous-agents/templates/DEVELOPER.md` to `pi/DEVELOPER.md`.
|
|
@@ -118,6 +124,7 @@ Recommended pattern:
|
|
|
118
124
|
- local model for `developerFix`
|
|
119
125
|
- local or slightly stronger model for `tester`
|
|
120
126
|
- stronger frontier model for `visualReview` only if available
|
|
127
|
+
- keep `commitMode` as `agent` unless the repo explicitly needs legacy harness-managed commit-plan parsing
|
|
121
128
|
|
|
122
129
|
Example shape:
|
|
123
130
|
|
|
@@ -136,6 +143,21 @@ Example shape:
|
|
|
136
143
|
}
|
|
137
144
|
```
|
|
138
145
|
|
|
146
|
+
If the repo uses a custom OpenAI-compatible local provider, validate it directly:
|
|
147
|
+
|
|
148
|
+
1. Verify the endpoint is reachable.
|
|
149
|
+
2. Query `<baseUrl>/models`.
|
|
150
|
+
3. Use the exact returned model id.
|
|
151
|
+
4. Do not assume the served id equals a GGUF filename on disk.
|
|
152
|
+
|
|
153
|
+
If the repo overrides `PI_CODING_AGENT_DIR`:
|
|
154
|
+
|
|
155
|
+
- do not point it at an empty directory
|
|
156
|
+
- ensure that PI home is already bootstrapped
|
|
157
|
+
- ensure `models.json` exists there before running the harness
|
|
158
|
+
|
|
159
|
+
If `PI_CODING_AGENT_DIR` is set to a repo-local PI home and `models.json` is missing, setup is incomplete.
|
|
160
|
+
|
|
139
161
|
9. Validate the setup.
|
|
140
162
|
|
|
141
163
|
Run at least:
|
|
@@ -152,6 +174,16 @@ PI_CONFIG_FILE=pi.config.json PI_TRANSPORT=mock PI_TEST_CMD= pi-harness once
|
|
|
152
174
|
|
|
153
175
|
If setup validation fails, fix the config rather than leaving a half-configured repo.
|
|
154
176
|
|
|
177
|
+
The harness should fail fast if:
|
|
178
|
+
|
|
179
|
+
- PI cannot list models
|
|
180
|
+
- a configured PI role model does not exist
|
|
181
|
+
- a configured provider endpoint is unreachable
|
|
182
|
+
- a configured provider does not serve the configured model id
|
|
183
|
+
|
|
184
|
+
For prompt debugging, inspect `.pi-last-prompt.txt` after a run. It contains the exact assembled prompt that was sent for the active role.
|
|
185
|
+
For flow debugging, inspect `.pi-last-iteration.json` after a run. It summarizes the selected task, repo-change outcome, tester verdict, commit-plan state, and terminal reason.
|
|
186
|
+
|
|
155
187
|
## Agent Rules
|
|
156
188
|
|
|
157
189
|
- Reuse existing repo conventions where possible.
|
|
@@ -159,6 +191,7 @@ If setup validation fails, fix the config rather than leaving a half-configured
|
|
|
159
191
|
- Do not invent fake test commands or model endpoints.
|
|
160
192
|
- Do not enable visual review unless the repo actually has a usable capture command and model config.
|
|
161
193
|
- Keep changes minimal and local to harness setup.
|
|
194
|
+
- Prefer very small, implementation-shaped TODO items for local models. Broad tasks tend to create long turns, retries, and weak tester behavior.
|
|
162
195
|
|
|
163
196
|
## What To Report Back
|
|
164
197
|
|
|
@@ -169,3 +202,13 @@ When setup is complete, report:
|
|
|
169
202
|
- whether visual review was enabled
|
|
170
203
|
- which roles were mapped to which models
|
|
171
204
|
- whether validation was run successfully
|
|
205
|
+
|
|
206
|
+
## Resetting Harness State
|
|
207
|
+
|
|
208
|
+
If the user wants to start over from a clean slate later, use:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
PI_CONFIG_FILE=pi.config.json pi-harness clear-history
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
This should remove harness-generated runtime/history state only, not project source files.
|
package/docs/PI_SUPERVISOR.md
CHANGED
|
@@ -18,7 +18,7 @@ Each real iteration follows this sequence:
|
|
|
18
18
|
2. A fast local verification command runs immediately after the developer round.
|
|
19
19
|
3. If verification passes, `tester` reviews the change independently from a skeptical user-facing perspective.
|
|
20
20
|
4. If tester or verification finds a real issue, the supervisor gives the findings back to `developer` for one focused repair pass.
|
|
21
|
-
5. If tester reaches `PASS`, tester
|
|
21
|
+
5. If tester reaches `PASS`, tester creates the commit directly in the same turn by default.
|
|
22
22
|
6. Optionally, every `N` successful iterations, the harness runs a read-only visual review over screenshots and persists the feedback for later runs.
|
|
23
23
|
7. If that visual review returns `FAIL`, `BLOCKED`, or times out, the iteration is not counted as a success and the feedback is carried into later prompts.
|
|
24
24
|
|
|
@@ -69,6 +69,7 @@ Projects typically provide their own `pi.config.json` with fields such as:
|
|
|
69
69
|
- `models`
|
|
70
70
|
- `piModel`
|
|
71
71
|
- `visualReviewModel`
|
|
72
|
+
- `commitMode`
|
|
72
73
|
|
|
73
74
|
Model entries may carry their own OpenAI-compatible endpoint settings, so the PI text loop and the multimodal visual reviewer can point at different backends without changing code.
|
|
74
75
|
|
|
@@ -83,7 +84,6 @@ Model entries may carry their own OpenAI-compatible endpoint settings, so the PI
|
|
|
83
84
|
"developerRetry": "local/dev-model",
|
|
84
85
|
"developerFix": "local/dev-model",
|
|
85
86
|
"tester": "local/tester-model",
|
|
86
|
-
"testerCommit": "local/tester-model",
|
|
87
87
|
"visualReview": "cloud/vision-model"
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -162,15 +162,13 @@ Allowed response `status` values:
|
|
|
162
162
|
|
|
163
163
|
## Git Finalization
|
|
164
164
|
|
|
165
|
-
The
|
|
165
|
+
The default flow keeps commit ownership with the active agent:
|
|
166
166
|
|
|
167
167
|
1. `developer` should leave a clean, reviewable diff and should not commit.
|
|
168
|
-
2. `tester` should review functionality and, on `PASS`,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
3. The harness stages only those requested files and performs the commit itself.
|
|
173
|
-
4. If the requested plan cannot be isolated safely, the iteration is blocked or failed instead of committing unrelated work.
|
|
168
|
+
2. `tester` should review functionality and, on `PASS`, stage only the task-related files and create the commit directly.
|
|
169
|
+
3. If the working tree is too messy to isolate safely, tester should return `VERDICT: BLOCKED` instead of guessing.
|
|
170
|
+
|
|
171
|
+
If a repo explicitly needs the older harness-managed commit-plan flow, set `commitMode` to `plan`. In that mode, `testerCommit` and parsed commit plans are used as a compatibility path rather than the default.
|
|
174
172
|
|
|
175
173
|
## Persistent Handoffs
|
|
176
174
|
|
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.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Portable unattended PI harness for developer/tester/visual-review loops.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"pi-harness": "./src/cli.mjs"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"check": "node --check src/cli.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-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-role-models.test.mjs && node --check test/pi-flow.test.mjs",
|
|
20
|
-
"test": "node --test test/pi-heartbeat.test.mjs test/pi-role-models.test.mjs test/pi-flow.test.mjs"
|
|
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-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",
|
|
20
|
+
"test": "node --test test/pi-heartbeat.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"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"src",
|
package/pi.config.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -11,6 +11,7 @@ const COMMANDS = new Map([
|
|
|
11
11
|
['once', 'pi-supervisor.mjs'],
|
|
12
12
|
['run', 'pi-supervisor.mjs'],
|
|
13
13
|
['report', 'pi-report.mjs'],
|
|
14
|
+
['clear-history', 'pi-clear-history.mjs'],
|
|
14
15
|
['visual-once', 'pi-visual-once.mjs'],
|
|
15
16
|
['adapter', 'pi-rpc-adapter.mjs'],
|
|
16
17
|
['visual-review-worker', 'pi-visual-review.mjs'],
|
package/src/index.mjs
CHANGED
|
@@ -4,4 +4,10 @@ export {
|
|
|
4
4
|
deriveWorkflowStatus,
|
|
5
5
|
shouldPersistLatestTesterFeedback,
|
|
6
6
|
} from './pi-flow.mjs'
|
|
7
|
+
export {
|
|
8
|
+
extractModelIdsFromProviderResponse,
|
|
9
|
+
parsePiListModelsOutput,
|
|
10
|
+
runStartupPreflight,
|
|
11
|
+
} from './pi-preflight.mjs'
|
|
12
|
+
export { clearHarnessHistory, collectHistoryTargets } from './pi-history.mjs'
|
|
7
13
|
export { runAgentTurn } from './pi-client.mjs'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { loadConfig } from './pi-config.mjs'
|
|
4
|
+
import { ensureRepo } from './pi-repo.mjs'
|
|
5
|
+
import { clearHarnessHistory } from './pi-history.mjs'
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const config = loadConfig('once')
|
|
9
|
+
ensureRepo(config.cwd)
|
|
10
|
+
|
|
11
|
+
const result = await clearHarnessHistory(config)
|
|
12
|
+
|
|
13
|
+
console.log(`Cleared harness history for ${result.clearedTargets.length} existing paths.`)
|
|
14
|
+
if (result.clearedTargets.length > 0) {
|
|
15
|
+
console.log('Cleared:')
|
|
16
|
+
for (const targetPath of result.clearedTargets) {
|
|
17
|
+
console.log(`- ${targetPath}`)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
console.log('Verification passed: no configured harness history paths remain.')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
main().catch((error) => {
|
|
24
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
25
|
+
process.exitCode = 1
|
|
26
|
+
})
|
package/src/pi-client.mjs
CHANGED
|
@@ -18,6 +18,7 @@ function formatLastAgentOutput(response) {
|
|
|
18
18
|
`status: ${String(response.status ?? '')}`,
|
|
19
19
|
`sessionId: ${String(response.sessionId ?? '')}`,
|
|
20
20
|
`sessionFile: ${String(response.sessionFile ?? '')}`,
|
|
21
|
+
`terminalReason: ${String(response.terminalReason ?? '')}`,
|
|
21
22
|
`notes: ${String(response.notes ?? '').trim()}`,
|
|
22
23
|
]
|
|
23
24
|
|
|
@@ -58,6 +59,15 @@ async function runMockTurn({ config, sessionId, sessionFile, prompt, reason }) {
|
|
|
58
59
|
durationSeconds: 0,
|
|
59
60
|
output,
|
|
60
61
|
notes: 'Mock transport completed without repo edits.',
|
|
62
|
+
role: '',
|
|
63
|
+
model: '',
|
|
64
|
+
toolCalls: 0,
|
|
65
|
+
toolErrors: 0,
|
|
66
|
+
messageUpdates: 0,
|
|
67
|
+
stopReason: '',
|
|
68
|
+
loopDetected: false,
|
|
69
|
+
loopSignature: '',
|
|
70
|
+
terminalReason: 'mock_completed',
|
|
61
71
|
}
|
|
62
72
|
}
|
|
63
73
|
|
|
@@ -142,6 +152,15 @@ async function runAdapterTurn({ config, model, sessionId, sessionFile, prompt, i
|
|
|
142
152
|
durationSeconds: result.durationSeconds,
|
|
143
153
|
output: result.combinedOutput,
|
|
144
154
|
notes: 'Adapter process exceeded the configured timeout.',
|
|
155
|
+
role: '',
|
|
156
|
+
model: model ?? config.piModel,
|
|
157
|
+
toolCalls: 0,
|
|
158
|
+
toolErrors: 0,
|
|
159
|
+
messageUpdates: 0,
|
|
160
|
+
stopReason: '',
|
|
161
|
+
loopDetected: false,
|
|
162
|
+
loopSignature: '',
|
|
163
|
+
terminalReason: 'agent_timeout',
|
|
145
164
|
}
|
|
146
165
|
}
|
|
147
166
|
|
|
@@ -157,6 +176,15 @@ async function runAdapterTurn({ config, model, sessionId, sessionFile, prompt, i
|
|
|
157
176
|
durationSeconds: result.durationSeconds,
|
|
158
177
|
output: result.combinedOutput,
|
|
159
178
|
notes: truncateForNotes(result.combinedOutput) || 'Adapter exited non-zero.',
|
|
179
|
+
role: '',
|
|
180
|
+
model: model ?? config.piModel,
|
|
181
|
+
toolCalls: 0,
|
|
182
|
+
toolErrors: 0,
|
|
183
|
+
messageUpdates: 0,
|
|
184
|
+
stopReason: '',
|
|
185
|
+
loopDetected: false,
|
|
186
|
+
loopSignature: '',
|
|
187
|
+
terminalReason: 'adapter_failed',
|
|
160
188
|
}
|
|
161
189
|
}
|
|
162
190
|
|
|
@@ -179,6 +207,15 @@ async function runAdapterTurn({ config, model, sessionId, sessionFile, prompt, i
|
|
|
179
207
|
durationSeconds: result.durationSeconds,
|
|
180
208
|
output,
|
|
181
209
|
notes,
|
|
210
|
+
role: String(response.role ?? ''),
|
|
211
|
+
model: String(response.model ?? model ?? config.piModel ?? ''),
|
|
212
|
+
toolCalls: Number.isFinite(Number(response.toolCalls)) ? Number(response.toolCalls) : 0,
|
|
213
|
+
toolErrors: Number.isFinite(Number(response.toolErrors)) ? Number(response.toolErrors) : 0,
|
|
214
|
+
messageUpdates: Number.isFinite(Number(response.messageUpdates)) ? Number(response.messageUpdates) : 0,
|
|
215
|
+
stopReason: String(response.stopReason ?? ''),
|
|
216
|
+
loopDetected: response.loopDetected === true,
|
|
217
|
+
loopSignature: String(response.loopSignature ?? ''),
|
|
218
|
+
terminalReason: String(response.terminalReason ?? ''),
|
|
182
219
|
}
|
|
183
220
|
}
|
|
184
221
|
|
package/src/pi-config.mjs
CHANGED
|
@@ -130,6 +130,22 @@ function normalizeRoleModels(raw) {
|
|
|
130
130
|
return normalized
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function normalizeCommitMode(raw) {
|
|
134
|
+
const value = normalizeString(raw, 'agent').trim().toLowerCase()
|
|
135
|
+
if (value === 'agent' || value === 'plan') {
|
|
136
|
+
return value
|
|
137
|
+
}
|
|
138
|
+
throw new Error(`Expected commitMode to be "agent" or "plan", received "${raw}"`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizePromptMode(raw) {
|
|
142
|
+
const value = normalizeString(raw, 'compact').trim().toLowerCase()
|
|
143
|
+
if (value === 'compact' || value === 'full') {
|
|
144
|
+
return value
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`Expected promptMode to be "compact" or "full", received "${raw}"`)
|
|
147
|
+
}
|
|
148
|
+
|
|
133
149
|
function resolveModelProfile(modelProfiles, modelName) {
|
|
134
150
|
if (!modelName || typeof modelName !== 'string') {
|
|
135
151
|
return null
|
|
@@ -181,12 +197,30 @@ export function loadConfig(mode = 'once') {
|
|
|
181
197
|
const repoConfig = readRepoConfig(cwd)
|
|
182
198
|
const file = repoConfig.values
|
|
183
199
|
const bundledAdapterCommand = 'pi-harness adapter'
|
|
200
|
+
const bundledDeveloperInstructionsFile = path.join(packageRoot, 'templates', 'DEVELOPER.md')
|
|
201
|
+
const bundledTesterInstructionsFile = path.join(packageRoot, 'templates', 'TESTER.md')
|
|
184
202
|
const modelProfiles = readObject('models', file.models, {})
|
|
185
203
|
const roleModels = normalizeRoleModels(file.roleModels)
|
|
186
204
|
const piModel = readString('PI_MODEL', file.piModel, '')
|
|
187
205
|
const visualReviewModel = readString('PI_VISUAL_REVIEW_MODEL', file.visualReviewModel, '')
|
|
188
206
|
const resolvedPiModel = resolveModelProfile(modelProfiles, piModel)
|
|
189
207
|
const resolvedVisualReviewModel = resolveModelProfile(modelProfiles, visualReviewModel)
|
|
208
|
+
const developerInstructionsFile = resolveInstructionsFile(
|
|
209
|
+
cwd,
|
|
210
|
+
'PI_DEVELOPER_INSTRUCTIONS_FILE',
|
|
211
|
+
file.developerInstructionsFile,
|
|
212
|
+
hasValue(file.instructionsFile)
|
|
213
|
+
? String(file.instructionsFile)
|
|
214
|
+
: bundledDeveloperInstructionsFile
|
|
215
|
+
)
|
|
216
|
+
const testerInstructionsFile = resolveInstructionsFile(
|
|
217
|
+
cwd,
|
|
218
|
+
'PI_TESTER_INSTRUCTIONS_FILE',
|
|
219
|
+
file.testerInstructionsFile,
|
|
220
|
+
hasValue(file.instructionsFile)
|
|
221
|
+
? String(file.instructionsFile)
|
|
222
|
+
: bundledTesterInstructionsFile
|
|
223
|
+
)
|
|
190
224
|
|
|
191
225
|
return {
|
|
192
226
|
cwd,
|
|
@@ -196,23 +230,11 @@ export function loadConfig(mode = 'once') {
|
|
|
196
230
|
agentName: readString('PI_AGENT_NAME', file.agentName, 'PI'),
|
|
197
231
|
adapterCommand: readString('PI_ADAPTER_COMMAND', file.adapterCommand, bundledAdapterCommand),
|
|
198
232
|
taskFile: resolveFromCwd(cwd, 'PI_TASK_FILE', file.taskFile, 'TODOS.md'),
|
|
199
|
-
instructionsFile: resolveInstructionsFile(cwd, 'PI_INSTRUCTIONS_FILE', file.instructionsFile,
|
|
200
|
-
developerInstructionsFile
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
hasValue(file.instructionsFile)
|
|
205
|
-
? String(file.instructionsFile)
|
|
206
|
-
: path.join(packageRoot, 'templates', 'DEVELOPER.md')
|
|
207
|
-
),
|
|
208
|
-
testerInstructionsFile: resolveInstructionsFile(
|
|
209
|
-
cwd,
|
|
210
|
-
'PI_TESTER_INSTRUCTIONS_FILE',
|
|
211
|
-
file.testerInstructionsFile,
|
|
212
|
-
hasValue(file.instructionsFile)
|
|
213
|
-
? String(file.instructionsFile)
|
|
214
|
-
: path.join(packageRoot, 'templates', 'TESTER.md')
|
|
215
|
-
),
|
|
233
|
+
instructionsFile: resolveInstructionsFile(cwd, 'PI_INSTRUCTIONS_FILE', file.instructionsFile, bundledDeveloperInstructionsFile),
|
|
234
|
+
developerInstructionsFile,
|
|
235
|
+
testerInstructionsFile,
|
|
236
|
+
usingBundledDeveloperInstructions: developerInstructionsFile === bundledDeveloperInstructionsFile,
|
|
237
|
+
usingBundledTesterInstructions: testerInstructionsFile === bundledTesterInstructionsFile,
|
|
216
238
|
logFile: resolveFromCwd(cwd, 'PI_LOG_FILE', file.logFile, 'pi.log'),
|
|
217
239
|
telemetryJsonl: resolveFromCwd(cwd, 'PI_TELEMETRY_JSONL', file.telemetryJsonl, 'pi_telemetry.jsonl'),
|
|
218
240
|
telemetryCsv: resolveFromCwd(cwd, 'PI_TELEMETRY_CSV', file.telemetryCsv, 'pi_telemetry.csv'),
|
|
@@ -221,12 +243,21 @@ export function loadConfig(mode = 'once') {
|
|
|
221
243
|
lastAgentOutputFile: resolveFromCwd(cwd, 'PI_LAST_AGENT_OUTPUT_FILE', file.lastAgentOutputFile, '.pi-last-output.txt'),
|
|
222
244
|
lastVerificationOutputFile: resolveFromCwd(cwd, 'PI_LAST_VERIFICATION_OUTPUT_FILE', file.lastVerificationOutputFile, '.pi-last-verification.txt'),
|
|
223
245
|
changedFilesFile: resolveFromCwd(cwd, 'PI_CHANGED_FILES_FILE', file.changedFilesFile, '.pi-changed-files.txt'),
|
|
246
|
+
lastPromptFile: resolveFromCwd(cwd, 'PI_LAST_PROMPT_FILE', file.lastPromptFile, '.pi-last-prompt.txt'),
|
|
247
|
+
lastIterationSummaryFile: resolveFromCwd(cwd, 'PI_LAST_ITERATION_SUMMARY_FILE', file.lastIterationSummaryFile, '.pi-last-iteration.json'),
|
|
224
248
|
piRuntimeDir: resolveFromCwd(cwd, 'PI_RUNTIME_DIR', file.piRuntimeDir, '.pi-runtime'),
|
|
225
249
|
piCli: readString('PI_CLI', file.piCli, 'pi'),
|
|
226
250
|
piModel,
|
|
227
251
|
piModelProfile: resolvedPiModel,
|
|
228
252
|
modelProfiles,
|
|
229
253
|
roleModels,
|
|
254
|
+
commitMode: normalizeCommitMode(readString('PI_COMMIT_MODE', file.commitMode, 'agent')),
|
|
255
|
+
promptMode: normalizePromptMode(readString('PI_PROMPT_MODE', file.promptMode, 'compact')),
|
|
256
|
+
maxPromptChangedFiles: readInt('PI_MAX_PROMPT_CHANGED_FILES', file.maxPromptChangedFiles, 10),
|
|
257
|
+
maxVisualFeedbackLines: readInt('PI_MAX_VISUAL_FEEDBACK_LINES', file.maxVisualFeedbackLines, 20),
|
|
258
|
+
maxTesterFeedbackLines: readInt('PI_MAX_TESTER_FEEDBACK_LINES', file.maxTesterFeedbackLines, 32),
|
|
259
|
+
maxPromptNotesLines: readInt('PI_MAX_PROMPT_NOTES_LINES', file.maxPromptNotesLines, 16),
|
|
260
|
+
maxVerificationExcerptLines: readInt('PI_MAX_VERIFICATION_EXCERPT_LINES', file.maxVerificationExcerptLines, 40),
|
|
230
261
|
piTools: readString('PI_TOOLS', file.piTools, 'read,bash,edit,write,grep,find,ls'),
|
|
231
262
|
piThinking: readString('PI_THINKING', file.piThinking, ''),
|
|
232
263
|
piNoExtensions: readBool('PI_NO_EXTENSIONS', file.piNoExtensions, false),
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
function unique(values) {
|
|
5
|
+
return [...new Set(values)]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isWithinCwd(cwd, targetPath) {
|
|
9
|
+
const relativePath = path.relative(cwd, targetPath)
|
|
10
|
+
return relativePath !== '' && !relativePath.startsWith('..') && !path.isAbsolute(relativePath)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function collectHistoryTargets(config) {
|
|
14
|
+
return unique([
|
|
15
|
+
config.logFile,
|
|
16
|
+
config.telemetryJsonl,
|
|
17
|
+
config.telemetryCsv,
|
|
18
|
+
config.stateFile,
|
|
19
|
+
config.sessionFile,
|
|
20
|
+
config.lastAgentOutputFile,
|
|
21
|
+
config.lastVerificationOutputFile,
|
|
22
|
+
config.changedFilesFile,
|
|
23
|
+
config.lastPromptFile,
|
|
24
|
+
config.lastIterationSummaryFile,
|
|
25
|
+
config.piRuntimeDir,
|
|
26
|
+
config.visualFeedbackFile,
|
|
27
|
+
config.testerFeedbackFile,
|
|
28
|
+
config.testerFeedbackHistoryDir,
|
|
29
|
+
config.visualReviewHistoryDir,
|
|
30
|
+
config.visualCaptureDir,
|
|
31
|
+
].map((value) => String(value ?? '').trim()).filter(Boolean))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function pathExists(targetPath) {
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(targetPath)
|
|
37
|
+
return true
|
|
38
|
+
} catch {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateHistoryTargets(config, targets) {
|
|
44
|
+
const invalidTargets = targets.filter((targetPath) => {
|
|
45
|
+
if (!path.isAbsolute(targetPath)) {
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
if (targetPath === config.cwd || targetPath === path.parse(targetPath).root) {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
return !isWithinCwd(config.cwd, targetPath)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
if (invalidTargets.length > 0) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Refusing to clear history outside the repo root. Invalid targets: ${invalidTargets.join(', ')}`
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function clearHarnessHistory(config) {
|
|
62
|
+
const targets = collectHistoryTargets(config)
|
|
63
|
+
validateHistoryTargets(config, targets)
|
|
64
|
+
|
|
65
|
+
const existingTargets = []
|
|
66
|
+
for (const targetPath of targets) {
|
|
67
|
+
if (await pathExists(targetPath)) {
|
|
68
|
+
existingTargets.push(targetPath)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const targetPath of [...existingTargets].sort((left, right) => right.length - left.length)) {
|
|
73
|
+
await fs.rm(targetPath, { recursive: true, force: true })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const remainingTargets = []
|
|
77
|
+
for (const targetPath of targets) {
|
|
78
|
+
if (await pathExists(targetPath)) {
|
|
79
|
+
remainingTargets.push(targetPath)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (remainingTargets.length > 0) {
|
|
84
|
+
throw new Error(`Failed to clear harness history for: ${remainingTargets.join(', ')}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
targets,
|
|
89
|
+
clearedTargets: existingTargets,
|
|
90
|
+
remainingTargets,
|
|
91
|
+
}
|
|
92
|
+
}
|