pi-harness-runtime 0.10.13 → 0.10.14
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/.versionrc.js +32 -0
- package/cli.js +112 -0
- package/footer-status.js +155 -0
- package/harness/agent-handoff.js +123 -0
- package/harness/auto-compact.js +243 -0
- package/harness/auto-quota-resume.js +104 -0
- package/harness/blackboard.js +258 -0
- package/harness/context-compact-orchestrator.js +365 -0
- package/harness/context-window-manager.js +330 -0
- package/harness/continue-prompt.js +164 -0
- package/harness/e2e/minimax-quota-parser.js +52 -0
- package/harness/e2e/minimax-quota-scraper.js +475 -0
- package/harness/e2e/openai-quota-scraper.js +332 -0
- package/harness/e2e/playwright-runner.js +165 -0
- package/harness/e2e/quota-status.js +140 -0
- package/harness/e2e/test-engine.js +290 -0
- package/harness/forked-summarizer.js +212 -0
- package/harness/index.js +54 -0
- package/harness/job-state-machine.js +277 -0
- package/harness/loop-runtime.js +531 -0
- package/harness/master-planner.js +229 -0
- package/harness/notification-events.js +233 -0
- package/harness/output-limit-handler.js +233 -0
- package/harness/partial-recovery.js +413 -0
- package/harness/project-detector/detector.js +283 -0
- package/harness/repair-engine.js +256 -0
- package/harness/session-memory.js +293 -0
- package/harness/task-graph.js +87 -0
- package/index.js +1121 -0
- package/mirror.js +205 -0
- package/package.json +6 -4
- package/proactive-compact.js +42 -0
- package/renderer.js +134 -0
- package/status-parsers.js +63 -0
- package/tracker.js +49 -0
- package/windows.js +90 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Job State Machine — RFC-0015
|
|
3
|
+
*
|
|
4
|
+
* Manages job lifecycle states with:
|
|
5
|
+
* - Defined state transitions
|
|
6
|
+
* - Event emission on every transition
|
|
7
|
+
* - Automatic checkpointing
|
|
8
|
+
* - Transition guards (validity checks)
|
|
9
|
+
*
|
|
10
|
+
* States:
|
|
11
|
+
* created -> planning -> queued -> running -> testing -> e2e_testing ->
|
|
12
|
+
* reviewing -> repairing -> paused_quota -> waiting_human -> ready_for_client -> archived
|
|
13
|
+
*/
|
|
14
|
+
// Valid transitions map
|
|
15
|
+
const VALID_TRANSITIONS = {
|
|
16
|
+
created: ["planning"],
|
|
17
|
+
planning: ["queued", "cancelled"],
|
|
18
|
+
queued: ["running", "cancelled", "waiting_human"],
|
|
19
|
+
running: [
|
|
20
|
+
"testing",
|
|
21
|
+
"reviewing",
|
|
22
|
+
"repairing",
|
|
23
|
+
"paused_quota",
|
|
24
|
+
"blocked",
|
|
25
|
+
"waiting_human",
|
|
26
|
+
"cancelled",
|
|
27
|
+
],
|
|
28
|
+
testing: [
|
|
29
|
+
"reviewing",
|
|
30
|
+
"running",
|
|
31
|
+
"repairing",
|
|
32
|
+
"paused_quota",
|
|
33
|
+
"waiting_human",
|
|
34
|
+
"cancelled",
|
|
35
|
+
],
|
|
36
|
+
e2e_testing: [
|
|
37
|
+
"reviewing",
|
|
38
|
+
"repairing",
|
|
39
|
+
"paused_quota",
|
|
40
|
+
"waiting_human",
|
|
41
|
+
"cancelled",
|
|
42
|
+
],
|
|
43
|
+
reviewing: [
|
|
44
|
+
"repairing",
|
|
45
|
+
"running",
|
|
46
|
+
"ready_for_client",
|
|
47
|
+
"paused_quota",
|
|
48
|
+
"waiting_human",
|
|
49
|
+
"cancelled",
|
|
50
|
+
],
|
|
51
|
+
repairing: [
|
|
52
|
+
"running",
|
|
53
|
+
"testing",
|
|
54
|
+
"reviewing",
|
|
55
|
+
"paused_quota",
|
|
56
|
+
"waiting_human",
|
|
57
|
+
"cancelled",
|
|
58
|
+
],
|
|
59
|
+
paused_quota: ["running", "waiting_human", "cancelled"],
|
|
60
|
+
waiting_human: ["running", "planning", "cancelled"],
|
|
61
|
+
blocked: ["running", "waiting_human", "cancelled"],
|
|
62
|
+
ready_for_client: ["archived", "repairing"],
|
|
63
|
+
cancelled: [],
|
|
64
|
+
archived: [],
|
|
65
|
+
};
|
|
66
|
+
export class JobStateMachine {
|
|
67
|
+
options;
|
|
68
|
+
currentCheckpoint = null;
|
|
69
|
+
eventLog = [];
|
|
70
|
+
constructor(options) {
|
|
71
|
+
this.options = options;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Initialize a new job with a requirement
|
|
75
|
+
*/
|
|
76
|
+
async createJob(jobId, requirement) {
|
|
77
|
+
const now = new Date().toISOString();
|
|
78
|
+
const checkpoint = {
|
|
79
|
+
version: 1,
|
|
80
|
+
jobId,
|
|
81
|
+
status: "created",
|
|
82
|
+
requirement,
|
|
83
|
+
createdAt: now,
|
|
84
|
+
updatedAt: now,
|
|
85
|
+
};
|
|
86
|
+
const event = this.createEvent(jobId, "JobCreated", `Job ${jobId} created with requirement`, { requirement });
|
|
87
|
+
try {
|
|
88
|
+
await this.options.checkpointManager.save(checkpoint);
|
|
89
|
+
await this.options.checkpointManager.appendEvent(jobId, event);
|
|
90
|
+
this.currentCheckpoint = checkpoint;
|
|
91
|
+
this.eventLog.push(event);
|
|
92
|
+
return { success: true, checkpoint };
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return { success: false, error: String(error) };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resume a job from checkpoint
|
|
100
|
+
*/
|
|
101
|
+
async resumeJob(jobId) {
|
|
102
|
+
const checkpoint = await this.options.checkpointManager.load(jobId);
|
|
103
|
+
if (!checkpoint) {
|
|
104
|
+
return { success: false, error: `No checkpoint found for job ${jobId}` };
|
|
105
|
+
}
|
|
106
|
+
this.currentCheckpoint = checkpoint;
|
|
107
|
+
return { success: true, checkpoint };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Transition to a new state
|
|
111
|
+
*/
|
|
112
|
+
async transition(to, data) {
|
|
113
|
+
if (!this.currentCheckpoint) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: "No active job. Call createJob() or resumeJob() first.",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const from = this.currentCheckpoint.status;
|
|
120
|
+
// Validate transition
|
|
121
|
+
if (!this.isValidTransition(from, to)) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: `Invalid transition: ${from} -> ${to}. Valid transitions: ${VALID_TRANSITIONS[from].join(", ") || "none"}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Create updated checkpoint
|
|
128
|
+
const updated = {
|
|
129
|
+
...this.currentCheckpoint,
|
|
130
|
+
status: to,
|
|
131
|
+
updatedAt: new Date().toISOString(),
|
|
132
|
+
...data,
|
|
133
|
+
};
|
|
134
|
+
// Create event
|
|
135
|
+
const event = this.createEvent(this.currentCheckpoint.jobId, `StateTransition:${from}->${to}`, `Transitioned from ${from} to ${to}`, { from, to, ...data });
|
|
136
|
+
try {
|
|
137
|
+
await this.options.checkpointManager.save(updated);
|
|
138
|
+
await this.options.checkpointManager.appendEvent(this.currentCheckpoint.jobId, event);
|
|
139
|
+
this.currentCheckpoint = updated;
|
|
140
|
+
this.eventLog.push(event);
|
|
141
|
+
return { success: true, checkpoint: updated };
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return { success: false, error: String(error) };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if a transition is valid
|
|
149
|
+
*/
|
|
150
|
+
isValidTransition(from, to) {
|
|
151
|
+
return VALID_TRANSITIONS[from]?.includes(to) ?? false;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get available next states from current state
|
|
155
|
+
*/
|
|
156
|
+
getAvailableTransitions() {
|
|
157
|
+
if (!this.currentCheckpoint)
|
|
158
|
+
return [];
|
|
159
|
+
return VALID_TRANSITIONS[this.currentCheckpoint.status] ?? [];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get current checkpoint
|
|
163
|
+
*/
|
|
164
|
+
getCheckpoint() {
|
|
165
|
+
return this.currentCheckpoint;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get event log
|
|
169
|
+
*/
|
|
170
|
+
getEventLog() {
|
|
171
|
+
return [...this.eventLog];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Set current task
|
|
175
|
+
*/
|
|
176
|
+
async setCurrentTask(taskId) {
|
|
177
|
+
return this.transition(this.currentCheckpoint.status, {
|
|
178
|
+
currentTaskId: taskId,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Set provider
|
|
183
|
+
*/
|
|
184
|
+
async setProvider(provider) {
|
|
185
|
+
return this.transition(this.currentCheckpoint.status, { provider });
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Set resume time (for quota pause)
|
|
189
|
+
*/
|
|
190
|
+
async setResumeTime(resumeAt) {
|
|
191
|
+
if (!this.currentCheckpoint) {
|
|
192
|
+
return { success: false, error: "No active job" };
|
|
193
|
+
}
|
|
194
|
+
this.currentCheckpoint.resumeAt = resumeAt;
|
|
195
|
+
this.currentCheckpoint.updatedAt = new Date().toISOString();
|
|
196
|
+
try {
|
|
197
|
+
await this.options.checkpointManager.save(this.currentCheckpoint);
|
|
198
|
+
return { success: true, checkpoint: this.currentCheckpoint };
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return { success: false, error: String(error) };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Record an error
|
|
206
|
+
*/
|
|
207
|
+
async recordError(error) {
|
|
208
|
+
if (!this.currentCheckpoint) {
|
|
209
|
+
return { success: false, error: "No active job" };
|
|
210
|
+
}
|
|
211
|
+
this.currentCheckpoint.lastError = error;
|
|
212
|
+
this.currentCheckpoint.updatedAt = new Date().toISOString();
|
|
213
|
+
try {
|
|
214
|
+
await this.options.checkpointManager.save(this.currentCheckpoint);
|
|
215
|
+
return { success: true, checkpoint: this.currentCheckpoint };
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
return { success: false, error: String(error) };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Check if job is in a terminal state
|
|
223
|
+
*/
|
|
224
|
+
isTerminal() {
|
|
225
|
+
if (!this.currentCheckpoint)
|
|
226
|
+
return false;
|
|
227
|
+
return ["ready_for_client", "cancelled", "archived"].includes(this.currentCheckpoint.status);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Check if job can be resumed
|
|
231
|
+
*/
|
|
232
|
+
canResume() {
|
|
233
|
+
if (!this.currentCheckpoint)
|
|
234
|
+
return false;
|
|
235
|
+
return (this.currentCheckpoint.status === "paused_quota" ||
|
|
236
|
+
this.currentCheckpoint.status === "blocked");
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get job status summary
|
|
240
|
+
*/
|
|
241
|
+
getStatusSummary() {
|
|
242
|
+
if (!this.currentCheckpoint)
|
|
243
|
+
return null;
|
|
244
|
+
return {
|
|
245
|
+
jobId: this.currentCheckpoint.jobId,
|
|
246
|
+
status: this.currentCheckpoint.status,
|
|
247
|
+
isTerminal: this.isTerminal(),
|
|
248
|
+
canResume: this.canResume(),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
createEvent(jobId, type, message, data) {
|
|
252
|
+
return {
|
|
253
|
+
ts: new Date().toISOString(),
|
|
254
|
+
jobId,
|
|
255
|
+
type,
|
|
256
|
+
message,
|
|
257
|
+
data,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Factory to create a state machine with a JsonCheckpointManager
|
|
263
|
+
*/
|
|
264
|
+
export async function createJobStateMachine(rootDir, jobId) {
|
|
265
|
+
const { JsonCheckpointManager } = await import("../packages/checkpoint/src/checkpoint-manager.ts");
|
|
266
|
+
const manager = new JsonCheckpointManager(rootDir);
|
|
267
|
+
if (jobId) {
|
|
268
|
+
const checkpoint = await manager.load(jobId);
|
|
269
|
+
const machine = new JobStateMachine({ checkpointManager: manager });
|
|
270
|
+
if (checkpoint) {
|
|
271
|
+
machine.resumeJob(jobId);
|
|
272
|
+
}
|
|
273
|
+
return { machine, checkpoint };
|
|
274
|
+
}
|
|
275
|
+
const machine = new JobStateMachine({ checkpointManager: manager });
|
|
276
|
+
return { machine, checkpoint: null };
|
|
277
|
+
}
|