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,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Test Engine — RFC-0013
|
|
3
|
+
*
|
|
4
|
+
* Browser-based end-to-end testing before work is marked ready for client.
|
|
5
|
+
*
|
|
6
|
+
* Artifact Layout:
|
|
7
|
+
* harness/e2e/
|
|
8
|
+
* scenarios/
|
|
9
|
+
* test-data/
|
|
10
|
+
* reports/
|
|
11
|
+
* artifacts/screenshots/
|
|
12
|
+
* artifacts/traces/
|
|
13
|
+
* artifacts/videos/
|
|
14
|
+
*/
|
|
15
|
+
import { writeJson, appendJsonl } from "../../cli.js";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
export class E2ETestEngine {
|
|
18
|
+
rootDir;
|
|
19
|
+
config;
|
|
20
|
+
runner = null;
|
|
21
|
+
constructor(rootDir, config) {
|
|
22
|
+
this.rootDir = rootDir;
|
|
23
|
+
this.config = {
|
|
24
|
+
headless: true,
|
|
25
|
+
screenshotOnFailure: true,
|
|
26
|
+
videoOnFailure: false,
|
|
27
|
+
traceOnFailure: false,
|
|
28
|
+
timeout: 30000,
|
|
29
|
+
...config,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Set the Playwright runner
|
|
34
|
+
*/
|
|
35
|
+
setRunner(runner) {
|
|
36
|
+
this.runner = runner;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run a single E2E scenario
|
|
40
|
+
*/
|
|
41
|
+
async runScenario(scenario, context) {
|
|
42
|
+
if (!this.runner) {
|
|
43
|
+
return this.createErrorResult(scenario.id, "No runner configured");
|
|
44
|
+
}
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
let stepsExecuted = 0;
|
|
47
|
+
let stepsPassed = 0;
|
|
48
|
+
let stepsFailed = 0;
|
|
49
|
+
let screenshotPath;
|
|
50
|
+
let failedStep;
|
|
51
|
+
try {
|
|
52
|
+
for (let i = 0; i < scenario.steps.length; i++) {
|
|
53
|
+
const step = scenario.steps[i];
|
|
54
|
+
stepsExecuted++;
|
|
55
|
+
try {
|
|
56
|
+
const success = await this.executeStep(step, context);
|
|
57
|
+
if (success) {
|
|
58
|
+
stepsPassed++;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
stepsFailed++;
|
|
62
|
+
failedStep = i;
|
|
63
|
+
if (this.config.screenshotOnFailure) {
|
|
64
|
+
screenshotPath = await this.captureScreenshot(scenario.id, i);
|
|
65
|
+
}
|
|
66
|
+
break; // Stop on first failure
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
stepsFailed++;
|
|
71
|
+
failedStep = i;
|
|
72
|
+
if (this.config.screenshotOnFailure) {
|
|
73
|
+
screenshotPath = await this.captureScreenshot(scenario.id, i);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const duration = Date.now() - startTime;
|
|
79
|
+
return {
|
|
80
|
+
scenarioId: scenario.id,
|
|
81
|
+
status: stepsFailed > 0 ? "failed" : "passed",
|
|
82
|
+
duration,
|
|
83
|
+
stepsExecuted,
|
|
84
|
+
stepsPassed,
|
|
85
|
+
stepsFailed,
|
|
86
|
+
screenshotPath,
|
|
87
|
+
executedAt: new Date().toISOString(),
|
|
88
|
+
failedStep,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return this.createErrorResult(scenario.id, String(error));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run all scenarios for a job
|
|
97
|
+
*/
|
|
98
|
+
async runAllScenarios(jobId, scenarios, context) {
|
|
99
|
+
const results = [];
|
|
100
|
+
let totalDuration = 0;
|
|
101
|
+
for (const scenario of scenarios) {
|
|
102
|
+
if (!scenario.required) {
|
|
103
|
+
// Skip non-required scenarios on failure of required ones
|
|
104
|
+
const result = await this.runScenario(scenario, context);
|
|
105
|
+
results.push(result);
|
|
106
|
+
totalDuration += result.duration;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const result = await this.runScenario(scenario, context);
|
|
110
|
+
results.push(result);
|
|
111
|
+
totalDuration += result.duration;
|
|
112
|
+
// Stop on first required failure
|
|
113
|
+
if (result.status === "failed") {
|
|
114
|
+
// Run remaining non-required scenarios
|
|
115
|
+
for (const nextScenario of scenarios.slice(scenarios.indexOf(scenario) + 1)) {
|
|
116
|
+
if (!nextScenario.required) {
|
|
117
|
+
const nextResult = await this.runScenario(nextScenario, context);
|
|
118
|
+
results.push(nextResult);
|
|
119
|
+
totalDuration += nextResult.duration;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const report = {
|
|
127
|
+
jobId,
|
|
128
|
+
scenarios,
|
|
129
|
+
results,
|
|
130
|
+
summary: {
|
|
131
|
+
total: results.length,
|
|
132
|
+
passed: results.filter((r) => r.status === "passed").length,
|
|
133
|
+
failed: results.filter((r) => r.status === "failed").length,
|
|
134
|
+
skipped: results.filter((r) => r.status === "skipped").length,
|
|
135
|
+
duration: totalDuration,
|
|
136
|
+
},
|
|
137
|
+
createdAt: new Date().toISOString(),
|
|
138
|
+
};
|
|
139
|
+
// Save report
|
|
140
|
+
this.saveReport(report);
|
|
141
|
+
return report;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Create a scenario from a natural language description
|
|
145
|
+
*/
|
|
146
|
+
createScenario(id, name, description, required = true) {
|
|
147
|
+
return {
|
|
148
|
+
id,
|
|
149
|
+
name,
|
|
150
|
+
description,
|
|
151
|
+
steps: [],
|
|
152
|
+
required,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Add a step to a scenario
|
|
157
|
+
*/
|
|
158
|
+
addStep(scenario, action, options) {
|
|
159
|
+
scenario.steps.push({
|
|
160
|
+
action,
|
|
161
|
+
selector: options?.selector,
|
|
162
|
+
value: options?.value,
|
|
163
|
+
timeout: options?.timeout,
|
|
164
|
+
assertCondition: options?.assertCondition,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Execute a single step
|
|
169
|
+
*/
|
|
170
|
+
async executeStep(step, context) {
|
|
171
|
+
if (!this.runner)
|
|
172
|
+
return false;
|
|
173
|
+
const timeout = step.timeout ?? this.config.timeout ?? 30000;
|
|
174
|
+
switch (step.action) {
|
|
175
|
+
case "navigate":
|
|
176
|
+
await this.runner.navigate(this.resolveValue(step.value ?? "", context));
|
|
177
|
+
return true;
|
|
178
|
+
case "click":
|
|
179
|
+
await this.runner.wait(step.selector, timeout);
|
|
180
|
+
await this.runner.click(step.selector);
|
|
181
|
+
return true;
|
|
182
|
+
case "type":
|
|
183
|
+
await this.runner.wait(step.selector, timeout);
|
|
184
|
+
await this.runner.type(step.selector, this.resolveValue(step.value ?? "", context));
|
|
185
|
+
return true;
|
|
186
|
+
case "wait":
|
|
187
|
+
await this.runner.wait(step.selector, timeout);
|
|
188
|
+
return true;
|
|
189
|
+
case "screenshot": {
|
|
190
|
+
const path = this.resolveValue(step.value ?? "screenshot.png", context);
|
|
191
|
+
await this.runner.screenshot(path);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
case "assert":
|
|
195
|
+
return await this.runner.assert(step.assertCondition ?? "true", `Assertion failed: ${step.assertCondition}`);
|
|
196
|
+
case "hover":
|
|
197
|
+
await this.runner.wait(step.selector, timeout);
|
|
198
|
+
// await this.runner.hover(step.selector!);
|
|
199
|
+
return true;
|
|
200
|
+
case "select":
|
|
201
|
+
await this.runner.wait(step.selector, timeout);
|
|
202
|
+
// await this.runner.select(step.selector!, step.value!);
|
|
203
|
+
return true;
|
|
204
|
+
case "upload":
|
|
205
|
+
await this.runner.wait(step.selector, timeout);
|
|
206
|
+
// await this.runner.upload(step.selector!, step.value!);
|
|
207
|
+
return true;
|
|
208
|
+
default:
|
|
209
|
+
console.warn(`Unknown step action: ${step.action}`);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Capture screenshot
|
|
215
|
+
*/
|
|
216
|
+
async captureScreenshot(scenarioId, stepIndex) {
|
|
217
|
+
const path = join(this.rootDir, "harness", "e2e", "artifacts", "screenshots", `${scenarioId}-step-${stepIndex}.png`);
|
|
218
|
+
if (this.runner) {
|
|
219
|
+
await this.runner.screenshot(path);
|
|
220
|
+
}
|
|
221
|
+
return path;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Create error result
|
|
225
|
+
*/
|
|
226
|
+
createErrorResult(scenarioId, error) {
|
|
227
|
+
return {
|
|
228
|
+
scenarioId,
|
|
229
|
+
status: "error",
|
|
230
|
+
duration: 0,
|
|
231
|
+
stepsExecuted: 0,
|
|
232
|
+
stepsPassed: 0,
|
|
233
|
+
stepsFailed: 0,
|
|
234
|
+
errorMessage: error,
|
|
235
|
+
executedAt: new Date().toISOString(),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Save report to file
|
|
240
|
+
*/
|
|
241
|
+
saveReport(report) {
|
|
242
|
+
const reportPath = join(this.rootDir, "harness", "e2e", "reports", `${report.jobId}-${Date.now()}.json`);
|
|
243
|
+
writeJson(reportPath, report);
|
|
244
|
+
// Also append to a log
|
|
245
|
+
const logPath = join(this.rootDir, "harness", "e2e", "reports", `${report.jobId}.jsonl`);
|
|
246
|
+
appendJsonl(logPath, report);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Resolve variables in values
|
|
250
|
+
*/
|
|
251
|
+
resolveValue(value, context) {
|
|
252
|
+
if (!context)
|
|
253
|
+
return value;
|
|
254
|
+
let result = value;
|
|
255
|
+
for (const [key, val] of Object.entries(context)) {
|
|
256
|
+
result = result.replace(new RegExp(`{{${key}}}`, "g"), String(val));
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Load scenarios from directory
|
|
262
|
+
*/
|
|
263
|
+
loadScenarios(_scenariosDir) {
|
|
264
|
+
// In practice, this would read from the scenarios directory
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Default E2E steps for common workflows
|
|
270
|
+
*/
|
|
271
|
+
export const CommonSteps = {
|
|
272
|
+
login: (username, password) => [
|
|
273
|
+
{ action: "navigate", value: "/login" },
|
|
274
|
+
{ action: "type", selector: "#username", value: username },
|
|
275
|
+
{ action: "type", selector: "#password", value: password },
|
|
276
|
+
{ action: "click", selector: 'button[type="submit"]' },
|
|
277
|
+
{ action: "wait", selector: ".dashboard", timeout: 10000 },
|
|
278
|
+
],
|
|
279
|
+
logout: () => [
|
|
280
|
+
{ action: "click", selector: ".user-menu" },
|
|
281
|
+
{ action: "click", selector: 'a[href="/logout"]' },
|
|
282
|
+
],
|
|
283
|
+
fillForm: (fields) => {
|
|
284
|
+
const steps = [];
|
|
285
|
+
for (const [selector, value] of Object.entries(fields)) {
|
|
286
|
+
steps.push({ action: "type", selector, value });
|
|
287
|
+
}
|
|
288
|
+
return steps;
|
|
289
|
+
},
|
|
290
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forked Summarizer — RFC-0028
|
|
3
|
+
*
|
|
4
|
+
* Summarizes old messages via a separate LLM call (forked context).
|
|
5
|
+
* This prevents the summarization request from hitting the same
|
|
6
|
+
* context-too-long error that triggered compaction.
|
|
7
|
+
*/
|
|
8
|
+
// --- Default System Prompt ---------------------------------------------------
|
|
9
|
+
const DEFAULT_SUMMARY_SYSTEM = `You are a conversation summarizer. Your task is to create concise, accurate summaries that preserve essential context for continuing a conversation.
|
|
10
|
+
|
|
11
|
+
Focus on:
|
|
12
|
+
- What was the goal/problem being addressed?
|
|
13
|
+
- What approaches were tried and what were the results?
|
|
14
|
+
- What decisions were made and why?
|
|
15
|
+
- What remains to be done?
|
|
16
|
+
- What files or code sections are relevant?
|
|
17
|
+
|
|
18
|
+
IMPORTANT: Do NOT include tool_result content verbatim. Instead, summarize the outcomes.`;
|
|
19
|
+
const WORK_DONE_FOCUS = `Focus specifically on:
|
|
20
|
+
1. What work was accomplished?
|
|
21
|
+
2. What files were created, modified, or deleted?
|
|
22
|
+
3. What tests passed or failed?
|
|
23
|
+
4. What errors or issues were encountered and resolved?
|
|
24
|
+
5. What is the current state of the project?`;
|
|
25
|
+
const DECISIONS_FOCUS = `Focus specifically on:
|
|
26
|
+
1. What architectural decisions were made?
|
|
27
|
+
2. What approaches were chosen over alternatives?
|
|
28
|
+
3. What was the rationale for each decision?
|
|
29
|
+
4. What constraints or requirements influenced the decisions?
|
|
30
|
+
5. Any lessons learned or trade-offs considered?`;
|
|
31
|
+
// --- Forked Summarizer ------------------------------------------------------
|
|
32
|
+
export class ForkedSummarizer {
|
|
33
|
+
config;
|
|
34
|
+
invokeAgent;
|
|
35
|
+
constructor(config, invokeAgent) {
|
|
36
|
+
this.config = {
|
|
37
|
+
provider: "openai",
|
|
38
|
+
focusOn: "all",
|
|
39
|
+
...config,
|
|
40
|
+
};
|
|
41
|
+
this.invokeAgent = invokeAgent;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Summarize old messages, keeping recent ones as context
|
|
45
|
+
*/
|
|
46
|
+
async summarize(messages, options) {
|
|
47
|
+
const keepRecentCount = options?.keepRecentCount ?? 5;
|
|
48
|
+
const focusOn = options?.focusOn ?? this.config.focusOn ?? "all";
|
|
49
|
+
// Split messages
|
|
50
|
+
const recentMessages = messages.slice(-keepRecentCount);
|
|
51
|
+
const oldMessages = messages.slice(0, -keepRecentCount);
|
|
52
|
+
if (oldMessages.length === 0) {
|
|
53
|
+
return { summary: "", droppedCount: 0 };
|
|
54
|
+
}
|
|
55
|
+
// Build summary prompt
|
|
56
|
+
const summaryPrompt = this.buildSummaryPrompt(oldMessages, { focusOn });
|
|
57
|
+
// Create summarization messages
|
|
58
|
+
const summarizationMessages = [
|
|
59
|
+
{
|
|
60
|
+
role: "system",
|
|
61
|
+
content: this.config.systemPrompt ?? DEFAULT_SUMMARY_SYSTEM,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
role: "user",
|
|
65
|
+
content: summaryPrompt,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
// Call summarization model
|
|
69
|
+
try {
|
|
70
|
+
const result = await this.invokeAgent({
|
|
71
|
+
messages: summarizationMessages,
|
|
72
|
+
model: this.config.model,
|
|
73
|
+
maxOutputTokens: this.config.maxSummaryTokens,
|
|
74
|
+
});
|
|
75
|
+
if (!result.success || !result.output) {
|
|
76
|
+
throw new Error(`Summarization failed: ${result.error ?? "unknown"}`);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
summary: result.output.trim(),
|
|
80
|
+
droppedCount: oldMessages.length,
|
|
81
|
+
tokensUsed: result.usage?.totalTokens,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Return heuristic summary on failure
|
|
86
|
+
return {
|
|
87
|
+
summary: this.heuristicSummary(oldMessages, focusOn),
|
|
88
|
+
droppedCount: oldMessages.length,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build the summary prompt for the LLM
|
|
94
|
+
*/
|
|
95
|
+
buildSummaryPrompt(messages, options) {
|
|
96
|
+
const focusOn = options.focusOn ?? "all";
|
|
97
|
+
const formattedMessages = messages
|
|
98
|
+
.map((m) => `[${m.role}]\n${this.truncateContent(m.content, 2000)}`)
|
|
99
|
+
.join("\n\n---\n\n");
|
|
100
|
+
const focusInstructions = {
|
|
101
|
+
work_done: WORK_DONE_FOCUS,
|
|
102
|
+
decisions: DECISIONS_FOCUS,
|
|
103
|
+
all: "Provide a comprehensive summary covering all important aspects of the conversation.",
|
|
104
|
+
}[focusOn];
|
|
105
|
+
return `Summarize the following conversation concisely.
|
|
106
|
+
|
|
107
|
+
${focusInstructions}
|
|
108
|
+
|
|
109
|
+
## Conversation to Summarize
|
|
110
|
+
${formattedMessages}
|
|
111
|
+
|
|
112
|
+
## Output Format
|
|
113
|
+
Provide a summary in 500-1000 tokens that:
|
|
114
|
+
1. Captures the essential context
|
|
115
|
+
2. Preserves key decisions and their rationale
|
|
116
|
+
3. Notes any ongoing work or next steps
|
|
117
|
+
4. Lists any important files or code sections referenced
|
|
118
|
+
|
|
119
|
+
Do not include verbatim tool results — summarize their outcomes instead.`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Truncate content to max length
|
|
123
|
+
*/
|
|
124
|
+
truncateContent(content, maxLength) {
|
|
125
|
+
if (content.length <= maxLength)
|
|
126
|
+
return content;
|
|
127
|
+
return content.substring(0, maxLength) + "... [truncated]";
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Heuristic summary when LLM summarization fails
|
|
131
|
+
*/
|
|
132
|
+
heuristicSummary(messages, focusOn) {
|
|
133
|
+
const summaries = [];
|
|
134
|
+
const keyFiles = new Set();
|
|
135
|
+
const decisions = [];
|
|
136
|
+
// Extract patterns from last 20 messages
|
|
137
|
+
for (const msg of messages.slice(-20)) {
|
|
138
|
+
const content = msg.content;
|
|
139
|
+
// Look for file references
|
|
140
|
+
const filePatterns = [
|
|
141
|
+
/created (?:file|module):?\s*([^\n.]+)/gi,
|
|
142
|
+
/modified (?:file|module):?\s*([^\n.]+)/gi,
|
|
143
|
+
/wrote to ([\w./-]+)/gi,
|
|
144
|
+
/updated ([\w./-]+)/gi,
|
|
145
|
+
];
|
|
146
|
+
for (const pattern of filePatterns) {
|
|
147
|
+
for (const match of content.matchAll(pattern)) {
|
|
148
|
+
if (match[1])
|
|
149
|
+
keyFiles.add(match[1].trim());
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Look for decisions
|
|
153
|
+
const decisionPatterns = [
|
|
154
|
+
/(?:decided|choosing|choice|chose):?\s*([^\n.]+)/gi,
|
|
155
|
+
/(?:approach|strategy):?\s*([^\n.]+)/gi,
|
|
156
|
+
];
|
|
157
|
+
for (const pattern of decisionPatterns) {
|
|
158
|
+
for (const match of content.matchAll(pattern)) {
|
|
159
|
+
if (match[1])
|
|
160
|
+
decisions.push(match[1].trim());
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Extract key user messages
|
|
164
|
+
if (msg.role === "user" && content.length > 50) {
|
|
165
|
+
summaries.push(`- User: ${content.substring(0, 300)}`);
|
|
166
|
+
}
|
|
167
|
+
// Extract significant actions from assistant
|
|
168
|
+
if (msg.role === "assistant" && msg.metadata?.action) {
|
|
169
|
+
summaries.push(`- Action: ${msg.metadata.action}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Build heuristic summary
|
|
173
|
+
const parts = [
|
|
174
|
+
`[Context compacted — ${messages.length} messages summarized by heuristic]`,
|
|
175
|
+
"",
|
|
176
|
+
];
|
|
177
|
+
if (keyFiles.size > 0) {
|
|
178
|
+
parts.push("## Files Referenced");
|
|
179
|
+
for (const file of Array.from(keyFiles).slice(0, 10)) {
|
|
180
|
+
parts.push(`- ${file}`);
|
|
181
|
+
}
|
|
182
|
+
parts.push("");
|
|
183
|
+
}
|
|
184
|
+
if (decisions.length > 0) {
|
|
185
|
+
parts.push("## Key Points");
|
|
186
|
+
for (const decision of Array.from(new Set(decisions)).slice(0, 5)) {
|
|
187
|
+
parts.push(`- ${decision}`);
|
|
188
|
+
}
|
|
189
|
+
parts.push("");
|
|
190
|
+
}
|
|
191
|
+
if (summaries.length > 0) {
|
|
192
|
+
parts.push("## Summary");
|
|
193
|
+
for (const summary of summaries.slice(0, 10)) {
|
|
194
|
+
parts.push(summary);
|
|
195
|
+
}
|
|
196
|
+
parts.push("");
|
|
197
|
+
}
|
|
198
|
+
parts.push("Please continue from the recent messages provided.");
|
|
199
|
+
return parts.join("\n");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// --- Convenience Factory ----------------------------------------------------
|
|
203
|
+
/**
|
|
204
|
+
* Create a ForkedSummarizer with default config
|
|
205
|
+
*/
|
|
206
|
+
export function createForkedSummarizer(model, invokeAgent, config) {
|
|
207
|
+
return new ForkedSummarizer({
|
|
208
|
+
model,
|
|
209
|
+
maxSummaryTokens: 4096,
|
|
210
|
+
...config,
|
|
211
|
+
}, invokeAgent);
|
|
212
|
+
}
|
package/harness/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness Runtime — Core Module Index
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all harness components for easy importing.
|
|
5
|
+
*/
|
|
6
|
+
// State Machine
|
|
7
|
+
export { JobStateMachine } from "./job-state-machine.js";
|
|
8
|
+
// Loop Runtime
|
|
9
|
+
export { LoopRuntime } from "./loop-runtime.js";
|
|
10
|
+
// Repair Engine
|
|
11
|
+
export { RepairEngine } from "./repair-engine.js";
|
|
12
|
+
// Auto Quota Resume (5h window auto-resume)
|
|
13
|
+
export { scheduleAutoResume, cancelAutoResume, getScheduledResume, } from "./auto-quota-resume.js";
|
|
14
|
+
// Master Planner
|
|
15
|
+
export { MasterPlanner } from "./master-planner.js";
|
|
16
|
+
// Context Window Manager (RFC-0010 — enhanced with thresholds)
|
|
17
|
+
export { ContextWindowManager, microcompactToolResults, parseTokenGapFromError, AUTOCOMPACT_BUFFER_TOKENS, WARNING_THRESHOLD_BUFFER_TOKENS, BLOCKING_THRESHOLD_BUFFER_TOKENS, MAX_CONSECUTIVE_COMPACT_FAILURES, } from "./context-window-manager.js";
|
|
18
|
+
// Forked Summarizer (RFC-0028 Phase 2 — LLM-based compaction)
|
|
19
|
+
export { ForkedSummarizer, createForkedSummarizer, } from "./forked-summarizer.js";
|
|
20
|
+
// Continue Prompt Generator (RFC-0029 Phase 5 — auto-resume)
|
|
21
|
+
export { ContinuePromptGenerator, continuePromptGenerator, } from "./continue-prompt.js";
|
|
22
|
+
// BlackBoard
|
|
23
|
+
export { createBlackboard, SharedBlackboard } from "./blackboard.js";
|
|
24
|
+
// Agent Handoff
|
|
25
|
+
export { AgentHandoffProtocol } from "./agent-handoff.js";
|
|
26
|
+
// Auto Compact (RFC-0019)
|
|
27
|
+
export { AutoCompactEngine } from "./auto-compact.js";
|
|
28
|
+
// Context Compact Orchestrator (RFC-0028)
|
|
29
|
+
export { CompactOrchestrator } from "./context-compact-orchestrator.js";
|
|
30
|
+
// Output Limit Handler (RFC-0020)
|
|
31
|
+
export { OutputLimitHandler } from "./output-limit-handler.js";
|
|
32
|
+
// Partial Recovery (RFC-0021)
|
|
33
|
+
export { PartialRecovery, createPartialRecovery } from "./partial-recovery.js";
|
|
34
|
+
// Session Memory (RFC-0030)
|
|
35
|
+
export { SessionMemoryManager, createSessionMemoryManager, } from "./session-memory.js";
|
|
36
|
+
// Notification Events (RFC-0022)
|
|
37
|
+
export { HarnessNotificationEvents, createNotificationConfigFromEnv, } from "./notification-events.js";
|
|
38
|
+
// E2E Testing
|
|
39
|
+
export { E2ETestEngine } from "./e2e/test-engine.js";
|
|
40
|
+
export { PlaywrightE2ERunner } from "./e2e/playwright-runner.js";
|
|
41
|
+
export { MiniMaxQuotaScraper, MiniMaxQuotaManager, } from "./e2e/minimax-quota-scraper.js";
|
|
42
|
+
export { QuotaStatusManager, formatQuotaStatus, createQuotaStatusManagerFromEnv, } from "./e2e/quota-status.js";
|
|
43
|
+
// Project Detector
|
|
44
|
+
export { ProjectDetector } from "./project-detector/detector.js";
|
|
45
|
+
// --- RFC-0056: Performance Optimizer ----------------------------------------
|
|
46
|
+
export { PerformanceOptimizer, createPerformanceOptimizer, } from "../packages/performance-optimizer/src/index.js";
|
|
47
|
+
// --- RFC-0057: Evaluation Engine -------------------------------------------
|
|
48
|
+
export { EvaluationEngine, createEvaluationEngine, } from "../packages/evaluation-engine/src/index.js";
|
|
49
|
+
// --- RFC-0058: Learning Engine ---------------------------------------------
|
|
50
|
+
export { LearningEngine, createLearningEngine, } from "../packages/learning-engine/src/index.js";
|
|
51
|
+
// --- RFC-0059: Experience Replay ------------------------------------------
|
|
52
|
+
export { ExperienceReplay, createExperienceReplay, } from "../packages/experience-replay/src/index.js";
|
|
53
|
+
// --- RFC-0060: Memory Engine -----------------------------------------------
|
|
54
|
+
export { MemoryEngine, createMemoryEngine, } from "../packages/memory-engine/src/index.js";
|