@basein/runner 0.2.4 → 0.2.5
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/dist/control/server.js +13 -0
- package/dist/replay/tool-error.d.ts +10 -9
- package/dist/replay/tool-error.js +13 -21
- package/docs/mcpmark.md +752 -752
- package/package.json +1 -1
package/dist/control/server.js
CHANGED
|
@@ -715,6 +715,15 @@ export class ControlServer {
|
|
|
715
715
|
logDetail("tool.pre.skipped", { run: run.runId, tool: toolName, why: "host housekeeping" });
|
|
716
716
|
return {};
|
|
717
717
|
}
|
|
718
|
+
// `run_scenario` with no live plan does nothing but answer "no scenario is
|
|
719
|
+
// armed". Recorded, that answer is a failed tool call, and a recording with
|
|
720
|
+
// one is never picked for calculation — observed in production on every
|
|
721
|
+
// first run in a project with the scenario server installed. With a live
|
|
722
|
+
// plan it is still recorded below, tagged `pinnedBy`.
|
|
723
|
+
if (this.replay.isDirectTool(toolName) && (!run.replay?.plan || run.replay.retired)) {
|
|
724
|
+
logDetail("tool.pre.skipped", { run: run.runId, tool: toolName, why: "no scenario armed" });
|
|
725
|
+
return {};
|
|
726
|
+
}
|
|
718
727
|
const agentId = payload.agent_id ?? session.agentId;
|
|
719
728
|
// The real intent, read from every line sharing this message's id
|
|
720
729
|
// (segmented.md R-INTENT-3). `context` stays the *written* text only, with
|
|
@@ -1236,6 +1245,10 @@ export class ControlServer {
|
|
|
1236
1245
|
// `tool_response` with no `tool_selected` before it.
|
|
1237
1246
|
if (isHousekeeping(toolName))
|
|
1238
1247
|
return {};
|
|
1248
|
+
// Nor for an unarmed `run_scenario`. The plan may have retired since the
|
|
1249
|
+
// pre hook, so the test is whether that hook opened a step, not the plan.
|
|
1250
|
+
if (this.replay.isDirectTool(toolName) && !run.builtIns.has(toolUseId))
|
|
1251
|
+
return {};
|
|
1239
1252
|
const failed = payload.hook_event_name === "PostToolUseFailure" || payload.error !== undefined;
|
|
1240
1253
|
// ── replay threading (docs/calculatedReplay.md §7.2) ─────────────────────
|
|
1241
1254
|
// `toolOutputLogic` was authored against the bytes that were *recorded* for
|
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A step's tool call resolving is not the same as the step's work happening.
|
|
5
5
|
* `executeStep` rejects only when a tool could not be run here; a tool that ran
|
|
6
|
-
* and
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `ok=true` each, the plan reported `steered_full`, and the ledger booked its
|
|
10
|
-
* largest saving of the day on a replay that created nothing (docs/mcpmark.md §13).
|
|
6
|
+
* and reported a failure resolves normally, with that failure as its response —
|
|
7
|
+
* which is right for threading (the output logic may want to see it) and wrong
|
|
8
|
+
* for the verdict (docs/mcpmark.md §13).
|
|
11
9
|
*
|
|
12
|
-
* The signal is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* The signal is `isError: true` on the MCP result, per the spec, and nothing
|
|
11
|
+
* else — the same rule the recorder uses (`proxy/intercept.ts`), so a step is a
|
|
12
|
+
* success or an error in the recording and in its replay alike. A server that
|
|
13
|
+
* reports failures as ordinary text with `isError: false`, `postgres-mcp` among
|
|
14
|
+
* them, is taken at its word on both sides: its `Error: relation does not exist`
|
|
15
|
+
* is a successful call here, as it was when it was recorded. A built-in's
|
|
16
|
+
* hook-shaped response is never judged.
|
|
16
17
|
*/
|
|
17
18
|
/**
|
|
18
19
|
* The error a serialized `CallToolResult` reports, or undefined when it does
|
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A step's tool call resolving is not the same as the step's work happening.
|
|
5
5
|
* `executeStep` rejects only when a tool could not be run here; a tool that ran
|
|
6
|
-
* and
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `ok=true` each, the plan reported `steered_full`, and the ledger booked its
|
|
10
|
-
* largest saving of the day on a replay that created nothing (docs/mcpmark.md §13).
|
|
6
|
+
* and reported a failure resolves normally, with that failure as its response —
|
|
7
|
+
* which is right for threading (the output logic may want to see it) and wrong
|
|
8
|
+
* for the verdict (docs/mcpmark.md §13).
|
|
11
9
|
*
|
|
12
|
-
* The signal is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* The signal is `isError: true` on the MCP result, per the spec, and nothing
|
|
11
|
+
* else — the same rule the recorder uses (`proxy/intercept.ts`), so a step is a
|
|
12
|
+
* success or an error in the recording and in its replay alike. A server that
|
|
13
|
+
* reports failures as ordinary text with `isError: false`, `postgres-mcp` among
|
|
14
|
+
* them, is taken at its word on both sides: its `Error: relation does not exist`
|
|
15
|
+
* is a successful call here, as it was when it was recorded. A built-in's
|
|
16
|
+
* hook-shaped response is never judged.
|
|
16
17
|
*/
|
|
17
18
|
const MAX_ERROR_CHARS = 500;
|
|
18
19
|
/**
|
|
@@ -27,17 +28,12 @@ export function toolResultError(serialized) {
|
|
|
27
28
|
catch {
|
|
28
29
|
return undefined;
|
|
29
30
|
}
|
|
30
|
-
if (typeof parsed === "string")
|
|
31
|
-
return looksLikeError(parsed) ? clip(parsed) : undefined;
|
|
32
31
|
if (!parsed || typeof parsed !== "object")
|
|
33
32
|
return undefined;
|
|
34
33
|
const result = parsed;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (text !== undefined && looksLikeError(text))
|
|
39
|
-
return clip(text);
|
|
40
|
-
return undefined;
|
|
34
|
+
if (result.isError !== true)
|
|
35
|
+
return undefined;
|
|
36
|
+
return clip(firstText(result.content) ?? "the tool reported an error");
|
|
41
37
|
}
|
|
42
38
|
function firstText(content) {
|
|
43
39
|
if (!Array.isArray(content))
|
|
@@ -49,10 +45,6 @@ function firstText(content) {
|
|
|
49
45
|
}
|
|
50
46
|
return undefined;
|
|
51
47
|
}
|
|
52
|
-
/** "Error: …" / "ERROR: …" / "error occurred" — not "Errors were fixed". */
|
|
53
|
-
function looksLikeError(text) {
|
|
54
|
-
return /^\s*error\b/i.test(text);
|
|
55
|
-
}
|
|
56
48
|
function clip(text) {
|
|
57
49
|
const trimmed = text.trim();
|
|
58
50
|
return trimmed.length > MAX_ERROR_CHARS ? `${trimmed.slice(0, MAX_ERROR_CHARS)}…` : trimmed;
|