pi-harness-runtime 0.2.0 → 0.3.1
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 +133 -158
- package/harness/agent-handoff.ts +189 -0
- package/harness/blackboard.ts +291 -0
- package/harness/context-window-manager.ts +210 -0
- package/harness/e2e/playwright-runner.ts +252 -0
- package/harness/e2e/test-engine.ts +402 -0
- package/harness/job-state-machine.ts +363 -0
- package/harness/loop-runtime.ts +337 -0
- package/harness/master-planner.ts +327 -0
- package/harness/project-detector/detector.ts +328 -0
- package/harness/repair-engine.ts +340 -0
- package/harness/task-graph.ts +336 -0
- package/index.ts +294 -2
- package/package.json +6 -3
- package/packages/checkpoint/README.md +3 -0
- package/packages/checkpoint/src/checkpoint-manager.ts +38 -0
- package/packages/provider-router/src/provider-router.ts +42 -0
- package/packages/providers/README.md +3 -0
- package/packages/providers/adapters.ts +261 -0
- package/packages/quota-manager/README.md +3 -0
- package/packages/quota-manager/quota-manager.ts +328 -0
- package/packages/runtime/README.md +3 -0
- package/packages/scheduler/README.md +3 -0
- package/packages/scheduler/src/scheduler.ts +50 -0
- package/packages/shared-context/README.md +3 -0
- package/packages/shared-context/src/shared-context.ts +42 -0
- package/packages/tui/README.md +3 -0
- package/packages/types/src/runtime-types.ts +439 -0
- package/packages/worktree/README.md +3 -0
- package/packages/worktree/worktree.ts +293 -0
- package/skills/harness-runtime/SKILL.md +186 -72
package/README.md
CHANGED
|
@@ -1,207 +1,182 @@
|
|
|
1
1
|
# Pi Harness Runtime
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/pi-harness-runtime)
|
|
3
|
+
[](https://github.com/ManotLuijiu/pi-harness-runtime)
|
|
4
|
+
[](https://www.npmjs.com/package/pi-harness-runtime)
|
|
6
5
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[]()
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
> ⚠️ **⚠️ Beta Notice — Not Production Ready ⚠️**
|
|
9
|
+
> This project is in active development. Core features work but E2E testing is not yet implemented.
|
|
10
|
+
> **148+ downloads** and counting — we appreciate the interest! Help us test by filing issues.
|
|
10
11
|
|
|
11
|
-
**
|
|
12
|
+
**Autonomous AI coding harness for pi: local token tracking + provider mirror + task orchestration.**
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
## Two Flavors
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
### 1. `/usage` — Token Tracking (stable ✅)
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Codex-style usage tracking with local tokens + provider mirror.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
### 2. `/harness` — Autonomous Coding Loop (beta 🔨)
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
pi install npm:pi-harness-runtime
|
|
23
|
-
```
|
|
22
|
+
Give the runtime a requirement. It keeps working, survives interruption, and returns with code ready for review.
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
## Install
|
|
26
25
|
|
|
27
26
|
```bash
|
|
28
|
-
|
|
29
|
-
ln -s pi-harness-runtime ~/.pi/agent/extensions/pi-harness-runtime
|
|
30
|
-
pi reload
|
|
27
|
+
pi install npm:pi-harness-runtime
|
|
31
28
|
```
|
|
32
29
|
|
|
33
|
-
**Important**: Symlink the **directory** into `~/.pi/agent/extensions/`. Pi's loader scans that directory and reads `package.json`'s `pi.extensions` field for multi-file extensions. Don't symlink a single file (relative imports break) or symlink into `node_modules/` (pi doesn't auto-scan there).
|
|
34
|
-
|
|
35
|
-
No API keys required. No build step (Bun runs `.ts` directly).
|
|
36
|
-
|
|
37
30
|
Requires Pi v0.37.3+.
|
|
38
31
|
|
|
39
|
-
##
|
|
32
|
+
## Usage Commands
|
|
40
33
|
|
|
41
34
|
```bash
|
|
42
|
-
/usage # show full status (
|
|
43
|
-
/usage sync #
|
|
44
|
-
/usage today #
|
|
45
|
-
/usage week #
|
|
46
|
-
/usage reset # clear provider mirror
|
|
35
|
+
/usage # show full status (local tracking + provider mirror)
|
|
36
|
+
/usage sync # mirror provider quota from console.minimax.io
|
|
37
|
+
/usage today # today's usage + 5h window
|
|
38
|
+
/usage week # this week's usage + lifetime totals
|
|
39
|
+
/usage reset # clear provider mirror
|
|
47
40
|
```
|
|
48
41
|
|
|
49
|
-
## Commands
|
|
42
|
+
## Harness Commands
|
|
50
43
|
|
|
51
|
-
|
|
44
|
+
```bash
|
|
45
|
+
/harness start <requirement> # Start a new harness job (beta)
|
|
46
|
+
/harness status # Show current job status
|
|
47
|
+
/harness tasks # List all tasks
|
|
48
|
+
/harness pause # Pause when quota low
|
|
49
|
+
/harness resume # Resume after quota reset
|
|
50
|
+
/harness cancel # Cancel job
|
|
51
|
+
```
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
## Example Session
|
|
54
54
|
|
|
55
55
|
```
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
56
|
+
/harness start Build a REST API with JWT authentication
|
|
57
|
+
|
|
58
|
+
# Runtime creates task graph:
|
|
59
|
+
# task-001: Analyze requirements → ready
|
|
60
|
+
# task-002: Implement API → depends on task-001
|
|
61
|
+
# task-003: Write tests → depends on task-002
|
|
62
|
+
# task-004: Code review → depends on task-003
|
|
63
|
+
|
|
64
|
+
/harness status
|
|
65
|
+
# Job: job-123, Status: running
|
|
66
|
+
# Tasks: 1/4 done, 1 running
|
|
67
|
+
|
|
68
|
+
# When quota runs low:
|
|
69
|
+
/harness pause
|
|
70
|
+
# Job paused. Resume when quota resets.
|
|
71
|
+
|
|
72
|
+
# When quota resets:
|
|
73
|
+
/harness resume
|
|
74
|
+
# Job resumed. Continue working.
|
|
75
|
+
|
|
76
|
+
# When all tasks complete:
|
|
77
|
+
/harness status
|
|
78
|
+
# Job: job-123, Status: ready_for_client
|
|
79
|
+
# Tasks: 4/4 done ✓
|
|
79
80
|
```
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Open a 6-prompt form to mirror provider-side quota:
|
|
82
|
+
## Architecture
|
|
84
83
|
|
|
85
84
|
```
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
pi-harness-runtime/
|
|
86
|
+
├── index.ts # Extension entry point
|
|
87
|
+
├── harness/
|
|
88
|
+
│ ├── job-state-machine.ts # 14-state lifecycle machine
|
|
89
|
+
│ ├── task-graph.ts # DAG-based task management
|
|
90
|
+
│ ├── master-planner.ts # Requirement → task graph
|
|
91
|
+
│ ├── loop-runtime.ts # Core execution loop
|
|
92
|
+
│ ├── repair-engine.ts # Auto-fix failures
|
|
93
|
+
│ ├── blackboard.ts # Agent coordination
|
|
94
|
+
│ ├── context-window-manager.ts # Context tracking
|
|
95
|
+
│ ├── agent-handoff.ts # Clean agent transitions
|
|
96
|
+
│ ├── e2e/
|
|
97
|
+
│ │ ├── test-engine.ts # E2E test runner (coming soon)
|
|
98
|
+
│ │ └── playwright-runner.ts # Browser automation
|
|
99
|
+
│ └── project-detector/
|
|
100
|
+
│ └── detector.ts # Auto-detect project type
|
|
101
|
+
├── packages/
|
|
102
|
+
│ ├── providers/adapters.ts # MiniMax, OpenAI adapters
|
|
103
|
+
│ ├── quota-manager/ # Quota signal collection
|
|
104
|
+
│ └── worktree/ # Git worktree per task
|
|
105
|
+
└── skills/
|
|
106
|
+
└── harness-runtime/
|
|
107
|
+
└── SKILL.md # Skill documentation
|
|
92
108
|
```
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
### `/usage today`
|
|
97
|
-
|
|
98
|
-
Focused view: today's usage + 5h window. Quick check before starting a long session.
|
|
99
|
-
|
|
100
|
-
### `/usage week`
|
|
101
|
-
|
|
102
|
-
Focused view: this week's usage + lifetime totals. Good for end-of-week review.
|
|
103
|
-
|
|
104
|
-
### `/usage reset`
|
|
105
|
-
|
|
106
|
-
Clear the provider mirror. Local usage log is preserved. Asks for confirmation.
|
|
107
|
-
|
|
108
|
-
## How It Works
|
|
110
|
+
## Job State Machine
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
2. **Provider mirror** — manually entered from `https://platform.minimax.io/console/usage`
|
|
118
|
-
- Stored at `~/.pi/usage-status/mirror.json`
|
|
119
|
-
- Synced via `/usage sync` form
|
|
120
|
-
- Ground truth for TOTAL quota (across all clients)
|
|
121
|
-
|
|
122
|
-
3. **Derived** — burn rate, reset times, divergence
|
|
123
|
-
- Local reset time = oldest request in window + window duration
|
|
124
|
-
- Burn rate = mirror weekly % / elapsed days
|
|
125
|
-
- Divergence warning if local tracking differs from mirror by >5%
|
|
126
|
-
|
|
127
|
-
### Files Written
|
|
128
|
-
|
|
129
|
-
| Path | Contents |
|
|
130
|
-
|---|---|
|
|
131
|
-
| `~/.pi/usage-status/usage.jsonl` | Append-only usage log |
|
|
132
|
-
| `~/.pi/usage-status/mirror.json` | Manual provider mirror |
|
|
133
|
-
| (none — pure functions) | CLI helpers in `cli.ts` |
|
|
134
|
-
|
|
135
|
-
Override location with `PI_USAGE_DIR` env var (useful for testing).
|
|
136
|
-
|
|
137
|
-
### What's NOT Stored
|
|
138
|
-
|
|
139
|
-
- Your password (we don't ask for it, ever)
|
|
140
|
-
- Provider session tokens (we don't read browser cookies)
|
|
141
|
-
- Telemetry or remote calls (all local)
|
|
142
|
-
- Auto-send quota (you always confirm any sync)
|
|
143
|
-
|
|
144
|
-
## Why Manual Mirror?
|
|
112
|
+
```
|
|
113
|
+
created → planning → queued → running → testing → reviewing
|
|
114
|
+
↓ ↓ ↓ ↓ ↓
|
|
115
|
+
cancelled blocked waiting_human repairing ready_for_client
|
|
116
|
+
↓ ↓
|
|
117
|
+
paused_quota archived
|
|
118
|
+
```
|
|
145
119
|
|
|
146
|
-
|
|
120
|
+
## Key Features
|
|
147
121
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
122
|
+
- **Resumable**: Every state change is checkpointed to disk
|
|
123
|
+
- **Quota-aware**: Detects quota exhaustion, pauses, resumes after reset
|
|
124
|
+
- **Provider-agnostic**: MiniMax, OpenAI, Claude adapters
|
|
125
|
+
- **Task DAG**: Dependencies tracked, topological execution
|
|
126
|
+
- **Auto-repair**: Failure classification + retry with exponential backoff
|
|
127
|
+
- **E2E testing**: Scenario-based Playwright integration (coming soon)
|
|
128
|
+
- **Project detection**: Auto-detects Frappe, Next.js, React, Django, Laravel
|
|
153
129
|
|
|
154
|
-
|
|
130
|
+
## Data Directory
|
|
155
131
|
|
|
156
|
-
|
|
157
|
-
2. Cookie extraction has ongoing maintenance burden
|
|
158
|
-
3. Password storage is a security anti-pattern
|
|
132
|
+
All data stored locally in `~/.pi/`:
|
|
159
133
|
|
|
160
|
-
|
|
134
|
+
```
|
|
135
|
+
~/.pi/
|
|
136
|
+
├── usage-status/ # /usage data
|
|
137
|
+
│ ├── usage.jsonl
|
|
138
|
+
│ └── mirror.json
|
|
139
|
+
└── harness/ # /harness data
|
|
140
|
+
└── jobs/
|
|
141
|
+
└── <job-id>/
|
|
142
|
+
├── checkpoint.json
|
|
143
|
+
├── events.jsonl
|
|
144
|
+
├── task-graph.json
|
|
145
|
+
├── blackboard/
|
|
146
|
+
└── repair-tasks.jsonl
|
|
147
|
+
```
|
|
161
148
|
|
|
162
149
|
## Safety Properties
|
|
163
150
|
|
|
164
|
-
- ✅ **
|
|
165
|
-
- ✅ **No
|
|
166
|
-
- ✅ **
|
|
167
|
-
- ✅ **
|
|
168
|
-
- ✅ **
|
|
169
|
-
- ✅ **No credentials stored** — passwords, tokens, cookies: none
|
|
151
|
+
- ✅ **Local-first**: All data stays on disk
|
|
152
|
+
- ✅ **No credentials stored**: No passwords, tokens, or cookies
|
|
153
|
+
- ✅ **Human-on-the-loop**: Clear intervention points
|
|
154
|
+
- ✅ **Checkpointed**: Resume from any state
|
|
155
|
+
- ✅ **Idempotent**: Safe to run multiple times
|
|
170
156
|
|
|
171
|
-
##
|
|
157
|
+
## Testing
|
|
172
158
|
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
├── index.ts # entry — exports default (pi: ExtensionAPI)
|
|
176
|
-
├── tracker.ts # UsageTracker — JSONL writer/reader
|
|
177
|
-
├── mirror.ts # MirrorStore — JSON read/write + freshness check
|
|
178
|
-
├── windows.ts # WindowAggregator — pure aggregation functions
|
|
179
|
-
├── renderer.ts # StatusRenderer — Codex-style progress bars
|
|
180
|
-
├── sync-form.ts # /usage sync form handling
|
|
181
|
-
├── cli.ts # shared pure helpers (paths, formatting, IO)
|
|
182
|
-
├── skills/
|
|
183
|
-
│ └── harness-runtime/
|
|
184
|
-
│ └── SKILL.md # bundled skill (auto-loaded)
|
|
185
|
-
├── test/ # node --test
|
|
186
|
-
│ ├── cli.test.mjs
|
|
187
|
-
│ ├── tracker.test.mjs
|
|
188
|
-
│ ├── mirror.test.mjs
|
|
189
|
-
│ ├── windows.test.mjs
|
|
190
|
-
│ ├── renderer.test.mjs
|
|
191
|
-
│ └── sync-form.test.mjs
|
|
192
|
-
├── package.json # "pi" field declares extensions + skills
|
|
193
|
-
├── README.md
|
|
194
|
-
├── CHANGELOG.md
|
|
195
|
-
├── LICENSE # MIT
|
|
196
|
-
└── .gitignore
|
|
159
|
+
```bash
|
|
160
|
+
bun test # 131+ tests passing
|
|
197
161
|
```
|
|
198
162
|
|
|
199
|
-
##
|
|
163
|
+
## Roadmap
|
|
200
164
|
|
|
201
|
-
- [
|
|
202
|
-
- [
|
|
203
|
-
- [
|
|
165
|
+
- [x] Core harness infrastructure (job state machine, task graph)
|
|
166
|
+
- [x] Master planner
|
|
167
|
+
- [x] Repair engine
|
|
168
|
+
- [x] Shared blackboard
|
|
169
|
+
- [ ] E2E test engine integration
|
|
170
|
+
- [ ] MiniMax Web automatic quota detection
|
|
171
|
+
- [ ] Claude adapter
|
|
172
|
+
- [ ] Production stress testing
|
|
204
173
|
|
|
205
174
|
## License
|
|
206
175
|
|
|
207
176
|
MIT © 2026 MooCoding
|
|
177
|
+
|
|
178
|
+
## Related
|
|
179
|
+
|
|
180
|
+
- [pi-coding-agent](https://github.com/earendil-works/pi-coding-agent) — Underlying pi agent
|
|
181
|
+
- [context-mode](https://github.com/MiniMax-AI/context-mode) — Context window tracking
|
|
182
|
+
- [pi-web-access](https://github.com/nicobailon/pi-web-access) — Web search for pi
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Handoff Protocol — RFC-0012
|
|
3
|
+
*
|
|
4
|
+
* Clean handoff between agents with context transfer.
|
|
5
|
+
* Ensures continuity when switching agents mid-task.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
HandoffContext,
|
|
10
|
+
HandoffEvent,
|
|
11
|
+
} from "../packages/types/src/runtime-types.ts";
|
|
12
|
+
import { writeJson, readJson } from "../cli.ts";
|
|
13
|
+
// @ts-expect-error - Bun has built-in Node.js types
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
16
|
+
export interface HandoffData {
|
|
17
|
+
fromAgent: string;
|
|
18
|
+
toAgent: string;
|
|
19
|
+
taskId: string;
|
|
20
|
+
taskSummary: string;
|
|
21
|
+
contextFiles: string[];
|
|
22
|
+
recentHistory: string[];
|
|
23
|
+
currentState: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class AgentHandoffProtocol {
|
|
27
|
+
private readonly rootDir: string;
|
|
28
|
+
|
|
29
|
+
constructor(rootDir: string) {
|
|
30
|
+
this.rootDir = rootDir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a handoff context for switching agents
|
|
35
|
+
*/
|
|
36
|
+
createHandoff(
|
|
37
|
+
jobId: string,
|
|
38
|
+
taskId: string,
|
|
39
|
+
fromAgent: string,
|
|
40
|
+
toAgent: string,
|
|
41
|
+
currentState?: Record<string, unknown>,
|
|
42
|
+
): HandoffContext {
|
|
43
|
+
const events = this.loadHandoffHistory(jobId, taskId);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
jobId,
|
|
47
|
+
taskId,
|
|
48
|
+
fromAgent,
|
|
49
|
+
toAgent,
|
|
50
|
+
sharedFiles: [],
|
|
51
|
+
taskHistory: events,
|
|
52
|
+
summary: this.generateSummary(taskId, currentState),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Record a handoff event
|
|
58
|
+
*/
|
|
59
|
+
recordHandoff(context: HandoffContext, result?: string): void {
|
|
60
|
+
const path = join(
|
|
61
|
+
this.rootDir,
|
|
62
|
+
"jobs",
|
|
63
|
+
context.jobId,
|
|
64
|
+
"handoffs",
|
|
65
|
+
`${context.taskId}.json`,
|
|
66
|
+
);
|
|
67
|
+
const event: HandoffEvent = {
|
|
68
|
+
ts: new Date().toISOString(),
|
|
69
|
+
agentId: context.toAgent,
|
|
70
|
+
action: "handoff_received",
|
|
71
|
+
result,
|
|
72
|
+
};
|
|
73
|
+
context.taskHistory.push(event);
|
|
74
|
+
writeJson(path, context);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generate handoff prompt for the receiving agent
|
|
79
|
+
*/
|
|
80
|
+
generateHandoffPrompt(context: HandoffContext): string {
|
|
81
|
+
const lines = [
|
|
82
|
+
`## Agent Handoff`,
|
|
83
|
+
``,
|
|
84
|
+
`**From Agent:** ${context.fromAgent}`,
|
|
85
|
+
`**To Agent:** ${context.toAgent}`,
|
|
86
|
+
`**Task:** ${context.taskId}`,
|
|
87
|
+
``,
|
|
88
|
+
`### Task History`,
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
for (const event of context.taskHistory) {
|
|
92
|
+
lines.push(`- [${event.ts}] ${event.agentId}: ${event.action}`);
|
|
93
|
+
if (event.result) {
|
|
94
|
+
lines.push(` Result: ${event.result}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lines.push(``);
|
|
99
|
+
lines.push(`### Summary`);
|
|
100
|
+
lines.push(context.summary);
|
|
101
|
+
|
|
102
|
+
if (context.sharedFiles.length > 0) {
|
|
103
|
+
lines.push(``);
|
|
104
|
+
lines.push(`### Shared Files`);
|
|
105
|
+
for (const file of context.sharedFiles) {
|
|
106
|
+
lines.push(`- ${file}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validate handoff readiness
|
|
115
|
+
*/
|
|
116
|
+
validateHandoff(context: HandoffContext): {
|
|
117
|
+
valid: boolean;
|
|
118
|
+
issues: string[];
|
|
119
|
+
} {
|
|
120
|
+
const issues: string[] = [];
|
|
121
|
+
|
|
122
|
+
if (!context.summary) {
|
|
123
|
+
issues.push("Task summary is empty");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (context.taskHistory.length === 0) {
|
|
127
|
+
issues.push("No task history recorded");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check for recent handoffs
|
|
131
|
+
const recentHandoffs = context.taskHistory.filter((h) => {
|
|
132
|
+
const age = Date.now() - Date.parse(h.ts);
|
|
133
|
+
return age < 5 * 60 * 1000; // 5 minutes
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (recentHandoffs.length > 3) {
|
|
137
|
+
issues.push(
|
|
138
|
+
`Too many recent handoffs (${recentHandoffs.length}). Possible ping-pong.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { valid: issues.length === 0, issues };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Load handoff history for a task
|
|
147
|
+
*/
|
|
148
|
+
private loadHandoffHistory(jobId: string, taskId: string): HandoffEvent[] {
|
|
149
|
+
const path = join(
|
|
150
|
+
this.rootDir,
|
|
151
|
+
"jobs",
|
|
152
|
+
jobId,
|
|
153
|
+
"handoffs",
|
|
154
|
+
`${taskId}.json`,
|
|
155
|
+
);
|
|
156
|
+
const data = readJson(path) as HandoffContext | null;
|
|
157
|
+
return data?.taskHistory ?? [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Generate a summary of the task state
|
|
162
|
+
*/
|
|
163
|
+
private generateSummary(
|
|
164
|
+
taskId: string,
|
|
165
|
+
currentState?: Record<string, unknown>,
|
|
166
|
+
): string {
|
|
167
|
+
if (!currentState) {
|
|
168
|
+
return `Task ${taskId} requires continuation. Check task files for current state.`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const lines = [`Task ${taskId} is in progress.`];
|
|
172
|
+
|
|
173
|
+
if (currentState.filesModified) {
|
|
174
|
+
lines.push(
|
|
175
|
+
`Files modified: ${(currentState.filesModified as string[]).join(", ")}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (currentState.lastAction) {
|
|
180
|
+
lines.push(`Last action: ${currentState.lastAction}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (currentState.blockers) {
|
|
184
|
+
lines.push(`Blockers: ${currentState.blockers}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
}
|