@abdwhb-png/pi-test-harness 0.7.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/CHANGELOG.md +161 -0
- package/LICENSE +21 -0
- package/README.md +673 -0
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +61 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/events.d.ts +6 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +33 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-pi-script.mjs +176 -0
- package/dist/mock-pi.d.ts +32 -0
- package/dist/mock-pi.d.ts.map +1 -0
- package/dist/mock-pi.js +150 -0
- package/dist/mock-pi.js.map +1 -0
- package/dist/mock-tools.d.ts +51 -0
- package/dist/mock-tools.d.ts.map +1 -0
- package/dist/mock-tools.js +192 -0
- package/dist/mock-tools.js.map +1 -0
- package/dist/mock-ui.d.ts +13 -0
- package/dist/mock-ui.d.ts.map +1 -0
- package/dist/mock-ui.js +159 -0
- package/dist/mock-ui.js.map +1 -0
- package/dist/pi-loader-parity.d.ts +36 -0
- package/dist/pi-loader-parity.d.ts.map +1 -0
- package/dist/pi-loader-parity.js +60 -0
- package/dist/pi-loader-parity.js.map +1 -0
- package/dist/playbook.d.ts +44 -0
- package/dist/playbook.d.ts.map +1 -0
- package/dist/playbook.js +143 -0
- package/dist/playbook.js.map +1 -0
- package/dist/sandbox.d.ts +27 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +269 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +187 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +171 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +32 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +46 -0
- package/dist/utils.js.map +1 -0
- package/package.json +84 -0
- package/skills/pi-test-harness/SKILL.md +451 -0
- package/skills/pi-test-harness/evals/evals.json +26 -0
- package/skills/pi-test-harness/references/api-reference.md +480 -0
- package/skills/pi-test-harness/references/mock-pi-cli.md +135 -0
- package/skills/pi-test-harness/references/mock-tools.md +176 -0
- package/skills/pi-test-harness/references/mock-ui.md +170 -0
- package/skills/pi-test-harness/references/playbook-dsl.md +209 -0
- package/skills/pi-test-harness/references/sandbox-install.md +113 -0
- package/src/diagnostics.ts +90 -0
- package/src/events.ts +43 -0
- package/src/index.ts +42 -0
- package/src/mock-pi-script.mjs +176 -0
- package/src/mock-pi.ts +169 -0
- package/src/mock-tools.ts +252 -0
- package/src/mock-ui.ts +196 -0
- package/src/pi-loader-parity.ts +61 -0
- package/src/playbook.ts +189 -0
- package/src/sandbox.ts +334 -0
- package/src/session.ts +249 -0
- package/src/types.ts +203 -0
- package/src/utils.ts +46 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playbook diagnostic messages — clear errors when things diverge.
|
|
3
|
+
*/
|
|
4
|
+
function formatAction(action) {
|
|
5
|
+
if (action.type === "say") {
|
|
6
|
+
return `says("${action.text?.slice(0, 60)}${(action.text?.length ?? 0) > 60 ? "..." : ""}")`;
|
|
7
|
+
}
|
|
8
|
+
if (action.type === "call") {
|
|
9
|
+
const params = typeof action.params === "function" ? "<late-bound>" : JSON.stringify(action.params);
|
|
10
|
+
const truncated = params.length > 80 ? params.slice(0, 80) + "..." : params;
|
|
11
|
+
return `calls("${action.toolName}", ${truncated})`;
|
|
12
|
+
}
|
|
13
|
+
return `unknown(${action.type})`;
|
|
14
|
+
}
|
|
15
|
+
export function formatPlaybookDiagnostic(type, state, remainingActions) {
|
|
16
|
+
if (type === "exhausted") {
|
|
17
|
+
const last = state.consumedActions[state.consumedActions.length - 1];
|
|
18
|
+
const lines = [
|
|
19
|
+
`Playbook exhausted unexpectedly.`,
|
|
20
|
+
` Consumed ${state.consumed} action(s).`,
|
|
21
|
+
];
|
|
22
|
+
if (last) {
|
|
23
|
+
lines.push(` Last consumed: ${formatAction(last)} at step ${state.consumed}`);
|
|
24
|
+
}
|
|
25
|
+
lines.push("", " The agent loop called streamFn but no more playbook actions were available.", " This usually means a tool call produced an unexpected result that caused", " additional streamFn calls (retries, error handling).");
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
if (type === "remaining" && remainingActions) {
|
|
29
|
+
const lines = [
|
|
30
|
+
`Playbook not fully consumed after run() completed.`,
|
|
31
|
+
` Consumed ${state.consumed} of ${state.consumed + remainingActions.length} action(s).`,
|
|
32
|
+
` Remaining:`,
|
|
33
|
+
];
|
|
34
|
+
for (const action of remainingActions.slice(0, 5)) {
|
|
35
|
+
lines.push(` - ${formatAction(action)}`);
|
|
36
|
+
}
|
|
37
|
+
if (remainingActions.length > 5) {
|
|
38
|
+
lines.push(` ... +${remainingActions.length - 5} more`);
|
|
39
|
+
}
|
|
40
|
+
lines.push("", " The agent loop ended before all playbook actions were used.", " This usually means a tool was blocked by a hook or returned early,", " causing fewer streamFn calls than expected.");
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
return "Unknown playbook diagnostic.";
|
|
44
|
+
}
|
|
45
|
+
export function formatToolError(step, toolName, error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
48
|
+
const lines = [
|
|
49
|
+
`Error during tool execution at playbook step ${step} (call "${toolName}"):`,
|
|
50
|
+
` ${message}`,
|
|
51
|
+
];
|
|
52
|
+
if (stack) {
|
|
53
|
+
const stackLines = stack.split("\n").slice(1, 4);
|
|
54
|
+
for (const line of stackLines) {
|
|
55
|
+
lines.push(` ${line.trim()}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
lines.push("", "This error was thrown by the real tool execution, not by the playbook.", "To capture errors as tool results instead of aborting, set:", " createTestSession({ propagateErrors: false })");
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAkD,MAAM,YAAY,CAAC;AAE7F,wBAAgB,oBAAoB,IAAI,UAAU,CAkCjD"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event collection — passively collects all events during a test run.
|
|
3
|
+
*/
|
|
4
|
+
export function createEventCollector() {
|
|
5
|
+
const all = [];
|
|
6
|
+
const toolCalls = [];
|
|
7
|
+
const toolResults = [];
|
|
8
|
+
const messages = [];
|
|
9
|
+
const ui = [];
|
|
10
|
+
return {
|
|
11
|
+
all,
|
|
12
|
+
toolCalls,
|
|
13
|
+
toolResults,
|
|
14
|
+
messages,
|
|
15
|
+
ui,
|
|
16
|
+
toolCallsFor(name) {
|
|
17
|
+
return toolCalls.filter((tc) => tc.toolName === name);
|
|
18
|
+
},
|
|
19
|
+
toolResultsFor(name) {
|
|
20
|
+
return toolResults.filter((tr) => tr.toolName === name);
|
|
21
|
+
},
|
|
22
|
+
blockedCalls() {
|
|
23
|
+
return toolCalls.filter((tc) => tc.blocked);
|
|
24
|
+
},
|
|
25
|
+
uiCallsFor(method) {
|
|
26
|
+
return ui.filter((u) => u.method === method);
|
|
27
|
+
},
|
|
28
|
+
toolSequence() {
|
|
29
|
+
return toolCalls.map((tc) => tc.toolName);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,UAAU,oBAAoB;IACnC,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,MAAM,SAAS,GAAqB,EAAE,CAAC;IACvC,MAAM,WAAW,GAAuB,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,MAAM,EAAE,GAAmB,EAAE,CAAC;IAE9B,OAAO;QACN,GAAG;QACH,SAAS;QACT,WAAW;QACX,QAAQ;QACR,EAAE;QAEF,YAAY,CAAC,IAAY;YACxB,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;QACvD,CAAC;QAED,cAAc,CAAC,IAAY;YAC1B,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;QACzD,CAAC;QAED,YAAY;YACX,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QAC7C,CAAC;QAED,UAAU,CAAC,MAAc;YACxB,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QAC9C,CAAC;QAED,YAAY;YACX,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC3C,CAAC;KACD,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @abdwhb-png/pi-test-harness
|
|
3
|
+
*
|
|
4
|
+
* Test harness for pi extensions — playbook-based model mocking,
|
|
5
|
+
* session testing, sandbox install verification.
|
|
6
|
+
*/
|
|
7
|
+
export { when, calls, says } from "./playbook.js";
|
|
8
|
+
export { createTestSession } from "./session.js";
|
|
9
|
+
export { verifySandboxInstall } from "./sandbox.js";
|
|
10
|
+
export { createMockPi } from "./mock-pi.js";
|
|
11
|
+
export type { TestSession, TestSessionOptions, TestEvents, ToolCallRecord, ToolResultRecord, UICallRecord, MockToolHandler, MockUIConfig, SandboxOptions, SandboxResult, MockPi, MockPiCall, Turn, PlaybookAction, } from "./types.js";
|
|
12
|
+
export { ToolBlockedError } from "./mock-tools.js";
|
|
13
|
+
export { safeRmSync } from "./utils.js";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGpD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,YAAY,EACX,WAAW,EACX,kBAAkB,EAClB,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,cAAc,EACd,aAAa,EACb,MAAM,EACN,UAAU,EACV,IAAI,EACJ,cAAc,GACd,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @abdwhb-png/pi-test-harness
|
|
3
|
+
*
|
|
4
|
+
* Test harness for pi extensions — playbook-based model mocking,
|
|
5
|
+
* session testing, sandbox install verification.
|
|
6
|
+
*/
|
|
7
|
+
// DSL builders
|
|
8
|
+
export { when, calls, says } from "./playbook.js";
|
|
9
|
+
// Session
|
|
10
|
+
export { createTestSession } from "./session.js";
|
|
11
|
+
// Sandbox
|
|
12
|
+
export { verifySandboxInstall } from "./sandbox.js";
|
|
13
|
+
// Mock Pi
|
|
14
|
+
export { createMockPi } from "./mock-pi.js";
|
|
15
|
+
// Errors
|
|
16
|
+
export { ToolBlockedError } from "./mock-tools.js";
|
|
17
|
+
// Utilities
|
|
18
|
+
export { safeRmSync } from "./utils.js";
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAe;AACf,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAElD,UAAU;AACV,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,UAAU;AACV,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,UAAU;AACV,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAoB5C,SAAS;AACT,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,YAAY;AACZ,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Mock pi CLI for integration tests.
|
|
4
|
+
*
|
|
5
|
+
* Reads response queue from MOCK_PI_QUEUE_DIR (set by the shim created by createMockPi).
|
|
6
|
+
* Each invocation consumes the next entry from the queue. When the queue is exhausted,
|
|
7
|
+
* the last entry repeats. If no entries are queued, outputs a default echo response.
|
|
8
|
+
*
|
|
9
|
+
* Queue protocol:
|
|
10
|
+
* {queueDir}/queue.json — JSON array of MockPiCall objects
|
|
11
|
+
* {queueDir}/counter — integer: current call index (auto-incremented)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
|
|
17
|
+
// Safety timeout — prevent hanging tests if something goes wrong
|
|
18
|
+
const TIMEOUT_MS = 30_000;
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
process.stderr.write("mock-pi-script: timeout after 30s\n");
|
|
21
|
+
process.exit(124);
|
|
22
|
+
}, TIMEOUT_MS).unref();
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Queue directory (set by the shim script)
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
const queueDir = process.env.MOCK_PI_QUEUE_DIR;
|
|
28
|
+
if (!queueDir) {
|
|
29
|
+
process.stderr.write("mock-pi-script: MOCK_PI_QUEUE_DIR not set\n");
|
|
30
|
+
process.exit(99);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Parse CLI arguments (matches what pi-subagents passes to pi)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
const args = process.argv.slice(2);
|
|
37
|
+
let task = "";
|
|
38
|
+
let sessionDir = null;
|
|
39
|
+
|
|
40
|
+
let i = 0;
|
|
41
|
+
while (i < args.length) {
|
|
42
|
+
const arg = args[i];
|
|
43
|
+
|
|
44
|
+
// Flags with a value — skip the value
|
|
45
|
+
if (
|
|
46
|
+
arg === "--session-dir" ||
|
|
47
|
+
arg === "--mode" ||
|
|
48
|
+
arg === "--models" ||
|
|
49
|
+
arg === "--tools" ||
|
|
50
|
+
arg === "--extension" ||
|
|
51
|
+
arg === "--append-system-prompt"
|
|
52
|
+
) {
|
|
53
|
+
if (arg === "--session-dir") sessionDir = args[i + 1] ?? null;
|
|
54
|
+
i += 2;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Flags without a value
|
|
59
|
+
if (arg === "-p" || arg === "--no-session" || arg === "--no-extensions") {
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// @file — read task from file
|
|
65
|
+
if (arg?.startsWith("@")) {
|
|
66
|
+
try {
|
|
67
|
+
task = fs.readFileSync(arg.slice(1), "utf-8");
|
|
68
|
+
} catch {
|
|
69
|
+
task = "(could not read " + arg.slice(1) + ")";
|
|
70
|
+
}
|
|
71
|
+
i++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Positional — treat as task text
|
|
76
|
+
if (arg && !arg.startsWith("-")) {
|
|
77
|
+
task = arg;
|
|
78
|
+
}
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Read queue and counter
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
let queue = [];
|
|
86
|
+
const queueFile = path.join(queueDir, "queue.json");
|
|
87
|
+
if (fs.existsSync(queueFile)) {
|
|
88
|
+
try {
|
|
89
|
+
queue = JSON.parse(fs.readFileSync(queueFile, "utf-8"));
|
|
90
|
+
} catch {
|
|
91
|
+
// Malformed queue — treat as empty
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const counterFile = path.join(queueDir, "counter");
|
|
96
|
+
let counter = 0;
|
|
97
|
+
if (fs.existsSync(counterFile)) {
|
|
98
|
+
try {
|
|
99
|
+
counter = parseInt(fs.readFileSync(counterFile, "utf-8").trim(), 10) || 0;
|
|
100
|
+
} catch {
|
|
101
|
+
// Missing or unreadable — start at 0
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Increment counter for next invocation
|
|
106
|
+
fs.writeFileSync(counterFile, String(counter + 1));
|
|
107
|
+
|
|
108
|
+
// Get the current entry (repeat last when exhausted, null if no queue)
|
|
109
|
+
const entry =
|
|
110
|
+
queue.length > 0 ? queue[Math.min(counter, queue.length - 1)] : null;
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Delay
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
if (entry?.delay > 0) {
|
|
116
|
+
await new Promise((r) => setTimeout(r, entry.delay));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Stderr
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
if (entry?.stderr) {
|
|
123
|
+
process.stderr.write(entry.stderr + "\n");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Write files (for chain_dir output simulation)
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
if (entry?.writeFiles) {
|
|
130
|
+
for (const [filePath, content] of Object.entries(entry.writeFiles)) {
|
|
131
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
132
|
+
fs.writeFileSync(filePath, content);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// JSONL output
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
if (entry?.jsonl) {
|
|
140
|
+
for (const event of entry.jsonl) {
|
|
141
|
+
console.log(typeof event === "string" ? event : JSON.stringify(event));
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
const taskClean = task.replace(/^Task:\s*/i, "").slice(0, 500);
|
|
145
|
+
const output = entry?.output ?? "Mock output for: " + taskClean;
|
|
146
|
+
console.log(
|
|
147
|
+
JSON.stringify({
|
|
148
|
+
type: "message_end",
|
|
149
|
+
message: {
|
|
150
|
+
role: "assistant",
|
|
151
|
+
content: [{ type: "text", text: output }],
|
|
152
|
+
model: "mock/test-model",
|
|
153
|
+
usage: {
|
|
154
|
+
input: 100,
|
|
155
|
+
output: 50,
|
|
156
|
+
cacheRead: 0,
|
|
157
|
+
cacheWrite: 0,
|
|
158
|
+
cost: { total: 0.001 },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Session file (if requested)
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
if (sessionDir) {
|
|
169
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
170
|
+
fs.writeFileSync(
|
|
171
|
+
path.join(sessionDir, "session-" + Date.now() + ".jsonl"),
|
|
172
|
+
JSON.stringify({ type: "session_start" }) + "\n",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
process.exit(entry?.exitCode ?? 0);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createMockPi — mock pi CLI for testing extensions that spawn pi as a subprocess.
|
|
3
|
+
*
|
|
4
|
+
* Creates a temp directory with a `pi` shim (`.cmd` on Windows, shell script on Linux)
|
|
5
|
+
* that prepends to PATH. The shim invokes a mock-pi-script.mjs that reads queued
|
|
6
|
+
* responses from a file-based queue. Responses are consumed in order; the last one
|
|
7
|
+
* repeats when the queue is exhausted.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const mockPi = createMockPi();
|
|
11
|
+
* mockPi.install();
|
|
12
|
+
*
|
|
13
|
+
* mockPi.onCall({ output: "Hello from agent" });
|
|
14
|
+
* mockPi.onCall({ stderr: "crashed", exitCode: 1 });
|
|
15
|
+
*
|
|
16
|
+
* // ... test code that spawns pi ...
|
|
17
|
+
*
|
|
18
|
+
* mockPi.reset();
|
|
19
|
+
* mockPi.uninstall();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { MockPi } from "./types.js";
|
|
23
|
+
/**
|
|
24
|
+
* Create a mock pi CLI for testing extensions that spawn pi as a subprocess.
|
|
25
|
+
*
|
|
26
|
+
* **Concurrency constraint**: Designed for serial subprocess spawns within a single test.
|
|
27
|
+
* If your test spawns multiple pi processes concurrently, responses may be consumed
|
|
28
|
+
* out of order. Use separate `createMockPi()` instances for concurrent scenarios,
|
|
29
|
+
* or ensure your test logic doesn't depend on response ordering.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createMockPi(): MockPi;
|
|
32
|
+
//# sourceMappingURL=mock-pi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-pi.d.ts","sourceRoot":"","sources":["../src/mock-pi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,YAAY,CAAC;AAoCrD;;;;;;;GAOG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAkGrC"}
|
package/dist/mock-pi.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createMockPi — mock pi CLI for testing extensions that spawn pi as a subprocess.
|
|
3
|
+
*
|
|
4
|
+
* Creates a temp directory with a `pi` shim (`.cmd` on Windows, shell script on Linux)
|
|
5
|
+
* that prepends to PATH. The shim invokes a mock-pi-script.mjs that reads queued
|
|
6
|
+
* responses from a file-based queue. Responses are consumed in order; the last one
|
|
7
|
+
* repeats when the queue is exhausted.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const mockPi = createMockPi();
|
|
11
|
+
* mockPi.install();
|
|
12
|
+
*
|
|
13
|
+
* mockPi.onCall({ output: "Hello from agent" });
|
|
14
|
+
* mockPi.onCall({ stderr: "crashed", exitCode: 1 });
|
|
15
|
+
*
|
|
16
|
+
* // ... test code that spawns pi ...
|
|
17
|
+
*
|
|
18
|
+
* mockPi.reset();
|
|
19
|
+
* mockPi.uninstall();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as os from "node:os";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
/** Valid keys for MockPiCall — used for runtime validation. */
|
|
28
|
+
const VALID_MOCK_PI_CALL_KEYS = new Set([
|
|
29
|
+
"output",
|
|
30
|
+
"exitCode",
|
|
31
|
+
"stderr",
|
|
32
|
+
"delay",
|
|
33
|
+
"jsonl",
|
|
34
|
+
"writeFiles",
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the mock-pi-script.mjs path.
|
|
38
|
+
*
|
|
39
|
+
* During dev (vitest): src/mock-pi.ts → sibling src/mock-pi-script.mjs
|
|
40
|
+
* After build (dist): dist/mock-pi.js → ../src/mock-pi-script.mjs
|
|
41
|
+
*/
|
|
42
|
+
function findMockPiScript() {
|
|
43
|
+
// Direct sibling (running from src/ via vitest)
|
|
44
|
+
const sibling = path.join(__dirname, "mock-pi-script.mjs");
|
|
45
|
+
if (fs.existsSync(sibling))
|
|
46
|
+
return sibling;
|
|
47
|
+
// One level up to package root, then into src/ (running from dist/)
|
|
48
|
+
const fromDist = path.join(__dirname, "..", "src", "mock-pi-script.mjs");
|
|
49
|
+
if (fs.existsSync(fromDist))
|
|
50
|
+
return fromDist;
|
|
51
|
+
throw new Error("Could not find mock-pi-script.mjs. Searched:\n" +
|
|
52
|
+
` ${sibling}\n` +
|
|
53
|
+
` ${fromDist}`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a mock pi CLI for testing extensions that spawn pi as a subprocess.
|
|
57
|
+
*
|
|
58
|
+
* **Concurrency constraint**: Designed for serial subprocess spawns within a single test.
|
|
59
|
+
* If your test spawns multiple pi processes concurrently, responses may be consumed
|
|
60
|
+
* out of order. Use separate `createMockPi()` instances for concurrent scenarios,
|
|
61
|
+
* or ensure your test logic doesn't depend on response ordering.
|
|
62
|
+
*/
|
|
63
|
+
export function createMockPi() {
|
|
64
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-mock-"));
|
|
65
|
+
let originalPath;
|
|
66
|
+
let installed = false;
|
|
67
|
+
const queueFile = path.join(dir, "queue.json");
|
|
68
|
+
const counterFile = path.join(dir, "counter");
|
|
69
|
+
// Initialize empty queue
|
|
70
|
+
fs.writeFileSync(queueFile, "[]");
|
|
71
|
+
fs.writeFileSync(counterFile, "0");
|
|
72
|
+
const scriptPath = findMockPiScript();
|
|
73
|
+
const nodeExe = process.execPath;
|
|
74
|
+
// Safety net: restore PATH if process exits without uninstall()
|
|
75
|
+
const exitHandler = () => {
|
|
76
|
+
if (originalPath !== undefined) {
|
|
77
|
+
process.env.PATH = originalPath;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
dir,
|
|
82
|
+
install() {
|
|
83
|
+
if (installed)
|
|
84
|
+
return;
|
|
85
|
+
originalPath = process.env.PATH;
|
|
86
|
+
if (process.platform === "win32") {
|
|
87
|
+
// Windows: .cmd batch file shim
|
|
88
|
+
const cmd = [
|
|
89
|
+
"@echo off",
|
|
90
|
+
`set "MOCK_PI_QUEUE_DIR=${dir}"`,
|
|
91
|
+
`"${nodeExe}" "${scriptPath}" %*`,
|
|
92
|
+
].join("\r\n") + "\r\n";
|
|
93
|
+
fs.writeFileSync(path.join(dir, "pi.cmd"), cmd);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Linux/macOS: shell script shim
|
|
97
|
+
const sh = [
|
|
98
|
+
"#!/bin/sh",
|
|
99
|
+
`MOCK_PI_QUEUE_DIR="${dir}" exec "${nodeExe}" "${scriptPath}" "$@"`,
|
|
100
|
+
].join("\n") + "\n";
|
|
101
|
+
const piPath = path.join(dir, "pi");
|
|
102
|
+
fs.writeFileSync(piPath, sh);
|
|
103
|
+
fs.chmodSync(piPath, 0o755);
|
|
104
|
+
}
|
|
105
|
+
process.env.PATH = `${dir}${path.delimiter}${originalPath}`;
|
|
106
|
+
process.on("exit", exitHandler);
|
|
107
|
+
installed = true;
|
|
108
|
+
},
|
|
109
|
+
uninstall() {
|
|
110
|
+
if (!installed)
|
|
111
|
+
return;
|
|
112
|
+
process.removeListener("exit", exitHandler);
|
|
113
|
+
if (originalPath !== undefined) {
|
|
114
|
+
process.env.PATH = originalPath;
|
|
115
|
+
originalPath = undefined;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Best-effort cleanup
|
|
122
|
+
}
|
|
123
|
+
installed = false;
|
|
124
|
+
},
|
|
125
|
+
onCall(response) {
|
|
126
|
+
// Validate keys to catch typos early
|
|
127
|
+
const unknown = Object.keys(response).filter((k) => !VALID_MOCK_PI_CALL_KEYS.has(k));
|
|
128
|
+
if (unknown.length > 0) {
|
|
129
|
+
throw new Error(`Unknown MockPiCall key(s): ${unknown.join(", ")}. ` +
|
|
130
|
+
`Valid keys: ${[...VALID_MOCK_PI_CALL_KEYS].join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
const queue = JSON.parse(fs.readFileSync(queueFile, "utf-8"));
|
|
133
|
+
queue.push(response);
|
|
134
|
+
fs.writeFileSync(queueFile, JSON.stringify(queue));
|
|
135
|
+
},
|
|
136
|
+
reset() {
|
|
137
|
+
fs.writeFileSync(queueFile, "[]");
|
|
138
|
+
fs.writeFileSync(counterFile, "0");
|
|
139
|
+
},
|
|
140
|
+
callCount() {
|
|
141
|
+
try {
|
|
142
|
+
return parseInt(fs.readFileSync(counterFile, "utf-8").trim(), 10) || 0;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=mock-pi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-pi.js","sourceRoot":"","sources":["../src/mock-pi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool execution interceptor — wraps tool.execute() for tools in mockTools.
|
|
3
|
+
*
|
|
4
|
+
* For mocked tools, the mock replaces tool.execute() and returns controlled
|
|
5
|
+
* values. Extension hooks (tool_call / tool_result) are handled by
|
|
6
|
+
* AgentSession 0.83's internal beforeToolCall/afterToolCall — the mock
|
|
7
|
+
* must NOT re-emit them.
|
|
8
|
+
*
|
|
9
|
+
* For non-mocked tools, the real execute() is called and results are
|
|
10
|
+
* collected for event queries.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
13
|
+
import type { MockToolHandler } from "./types.js";
|
|
14
|
+
import type { PlaybookState } from "./playbook.js";
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when an extension hook blocks a tool call.
|
|
17
|
+
* Exported for test assertions — no longer thrown by the mock itself since
|
|
18
|
+
* AgentSession 0.83's beforeToolCall handles blocking before execute().
|
|
19
|
+
*/
|
|
20
|
+
export declare class ToolBlockedError extends Error {
|
|
21
|
+
readonly toolBlocked: true;
|
|
22
|
+
constructor(reason: string);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns true if `err` represents a hook-based tool block.
|
|
26
|
+
*
|
|
27
|
+
* Kept for consumers that catch errors from tool execution flows, though
|
|
28
|
+
* AgentSession 0.83's beforeToolCall blocks before execute() is reached.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isBlockedError(err: unknown): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Intercept tool execution for mocked tools.
|
|
33
|
+
*
|
|
34
|
+
* Unlike the old approach, this does NOT emit tool_call/tool_result hooks
|
|
35
|
+
* manually — AgentSession 0.83's beforeToolCall/afterToolCall handles that.
|
|
36
|
+
* The mock only replaces execute() to return controlled values. Result
|
|
37
|
+
* recording is handled by the session subscriber from tool_execution_end
|
|
38
|
+
* events (which carry the final afterToolCall-modified result).
|
|
39
|
+
*
|
|
40
|
+
* Returns a Set of mocked tool names for the session subscriber to set the
|
|
41
|
+
* mocked flag on recorded results, plus a Set of toolCallIds whose mock
|
|
42
|
+
* returned a ToolResult with isError:true. Pi 0.84's agent loop hardcodes
|
|
43
|
+
* successful execute() as non-error (isError:false), so the subscriber must
|
|
44
|
+
* consult this set to preserve the mock's error intent in collected records.
|
|
45
|
+
*/
|
|
46
|
+
export declare function interceptToolExecution(tools: AgentTool[], mockTools: Record<string, MockToolHandler>, playbookState: PlaybookState, propagateErrors: boolean): {
|
|
47
|
+
tools: AgentTool[];
|
|
48
|
+
mockedNames: ReadonlySet<string>;
|
|
49
|
+
mockedErrorToolCallIds: ReadonlySet<string>;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=mock-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-tools.d.ts","sourceRoot":"","sources":["../src/mock-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,WAAW,EAAG,IAAI,CAAU;gBAEzB,MAAM,EAAE,MAAM;CAI1B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAWpD;AA0BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,SAAS,EAAE,EAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAC1C,aAAa,EAAE,aAAa,EAC5B,eAAe,EAAE,OAAO,GACtB;IACF,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC5C,CAiDA"}
|