@basein/runner 0.2.2 → 0.2.3
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/bin/bir.js +19 -0
- package/dist/record/recorder.d.ts +11 -0
- package/dist/record/remote-recorder.js +4 -0
- package/dist/replay/controller.js +58 -18
- package/dist/replay/targets.d.ts +52 -0
- package/dist/replay/targets.js +133 -0
- package/package.json +1 -1
package/dist/bin/bir.js
CHANGED
|
@@ -576,6 +576,17 @@ async function doctor(args) {
|
|
|
576
576
|
problems.push(`BIR_AUTH_URL in this shell (${shellAuthUrl}) is not a BaseIn service: ${shellAuthProblem}. ` +
|
|
577
577
|
`\`bir login\` and \`bir scenario\` here will fail — ${AUTH_URL_HINT}`);
|
|
578
578
|
}
|
|
579
|
+
// THE SCENARIO SERVER IS ON THE CRITICAL PATH OF THE FIRST TURN. A plan armed
|
|
580
|
+
// at `UserPromptSubmit` is delivered through `mcp__bir__run_scenario`, so a
|
|
581
|
+
// `bir` entry that starts with `npx` has to resolve and unpack the package
|
|
582
|
+
// before the tool exists at all — measured at ~13s, against a first tool call
|
|
583
|
+
// ~6s in. The turn does not fail; it just quietly runs the ordinary way, which
|
|
584
|
+
// is the failure this whole command exists to make visible.
|
|
585
|
+
const scenarioEntry = entries.find((e) => e.name === SCENARIO_SERVER_KEY);
|
|
586
|
+
if (scenarioEntry && !isRemote(scenarioEntry.config) && scenarioEntry.config.command === "npx") {
|
|
587
|
+
notes.push("the bir scenario server starts via npx, which can take longer than the first tool call " +
|
|
588
|
+
"of a turn — reinstall with `bir install --replay --global` to pin it to the installed copy");
|
|
589
|
+
}
|
|
579
590
|
const sidecar = readSidecar();
|
|
580
591
|
if (sidecar.controlPort && discovery && !discovery.url.endsWith(`:${sidecar.controlPort}`)) {
|
|
581
592
|
problems.push(`hooks were installed for port ${sidecar.controlPort} but the control server is on ${discovery.url}`);
|
|
@@ -796,6 +807,14 @@ async function replayCommand(args) {
|
|
|
796
807
|
}
|
|
797
808
|
const r = body;
|
|
798
809
|
out(`params ${JSON.stringify(r.params ?? {})}`);
|
|
810
|
+
// The difference between "this scenario works" and "this scenario works on
|
|
811
|
+
// last week's values". A dry replay fills an unnamed target from the
|
|
812
|
+
// recording; a real turn never does, so without this line a green trace here
|
|
813
|
+
// is no evidence at all that a session would steer.
|
|
814
|
+
if (r.targetsFromSamples?.length) {
|
|
815
|
+
out(` ↑ not named by the prompt: ${r.targetsFromSamples.join(", ")}`);
|
|
816
|
+
out(" (a live turn finds these in an earlier step, or does not run)");
|
|
817
|
+
}
|
|
799
818
|
(r.steps ?? []).forEach((s, i) => {
|
|
800
819
|
out(`step ${i} ${s.toolName} ${JSON.stringify(s.input)}`);
|
|
801
820
|
const emitted = Object.keys(s.emitted ?? {});
|
|
@@ -263,6 +263,17 @@ export interface ExecutionReport {
|
|
|
263
263
|
* two baselines of one turn. The segments' own rows are untouched.
|
|
264
264
|
*/
|
|
265
265
|
sharedWith?: string[];
|
|
266
|
+
/**
|
|
267
|
+
* Which gate of the ladder declined, on a `not_steered` report.
|
|
268
|
+
*
|
|
269
|
+
* The one thing the recording page could never learn. "Replay on, scenario
|
|
270
|
+
* ready, prompt matched, nothing steered" has exactly one explanation and it
|
|
271
|
+
* used to live only in a stderr line on the machine that decided it — so the
|
|
272
|
+
* owner of a scenario that never runs had nothing to look at. It travels as
|
|
273
|
+
* the gate's own code rather than the sentence beside it: the prose is for a
|
|
274
|
+
* person reading the audit log, this is for the console.
|
|
275
|
+
*/
|
|
276
|
+
declined?: string;
|
|
266
277
|
}
|
|
267
278
|
/** Optional capability: reporting needs a service, and a NullRecorder has none. */
|
|
268
279
|
export interface ScenarioReporter {
|
|
@@ -212,6 +212,9 @@ export class RemoteRecorder {
|
|
|
212
212
|
fallbackKind: report.fallbackKind,
|
|
213
213
|
baselineEligible: report.baselineEligible,
|
|
214
214
|
sharedWith: report.sharedWith,
|
|
215
|
+
// Why nothing ran, when nothing ran. Additive: an older service drops
|
|
216
|
+
// the field and books the cost exactly as it always did.
|
|
217
|
+
declined: report.declined,
|
|
215
218
|
});
|
|
216
219
|
const r = (body ?? {});
|
|
217
220
|
const failed = report.steps?.filter((s) => s.status === "failed").length ?? 0;
|
|
@@ -228,6 +231,7 @@ export class RemoteRecorder {
|
|
|
228
231
|
stepsFailed: failed || undefined,
|
|
229
232
|
baselineEligible: report.baselineEligible === false ? false : undefined,
|
|
230
233
|
sharedWith: report.sharedWith?.length,
|
|
234
|
+
declined: report.declined,
|
|
231
235
|
});
|
|
232
236
|
});
|
|
233
237
|
}
|
|
@@ -25,6 +25,7 @@ import { ScenarioReplayPlan } from "./plan.js";
|
|
|
25
25
|
import { PRICING_VERSION } from "./pricing.js";
|
|
26
26
|
import { SourceRunOutputs } from "./source-run.js";
|
|
27
27
|
import { clampToFrameStart, flattenChain, } from "./flatten.js";
|
|
28
|
+
import { blockingTargets } from "./targets.js";
|
|
28
29
|
import { toolResultError } from "./tool-error.js";
|
|
29
30
|
import { OUTCOME_RANK, hasTargets, isReadyScenario, } from "./types.js";
|
|
30
31
|
/** The first-party tool a `direct` plan is delivered through (§6.3). */
|
|
@@ -233,29 +234,60 @@ export class ReplayController {
|
|
|
233
234
|
// because it is the only one that spends a model call and waits. A scenario
|
|
234
235
|
// whose parameters are all settings arms at once, as before: there is
|
|
235
236
|
// nothing the turn has to supply.
|
|
237
|
+
//
|
|
238
|
+
// A target the turn did not name stops the plan only when *this* plan could
|
|
239
|
+
// act on its recorded value; a target the chain discovers for itself does
|
|
240
|
+
// not ({@link ./targets.ts}). The unnamed value stays null either way, so
|
|
241
|
+
// nothing here ever runs on last week's.
|
|
236
242
|
if (hasTargets(scenario.paramsObject)) {
|
|
243
|
+
// Of the targets this answer left unnamed, the ones this plan could act
|
|
244
|
+
// on the recorded value of. Empty means the chain finds them for itself.
|
|
245
|
+
const blocking = (missing) => blockingTargets(missing, scenario.paramsObject, planned);
|
|
237
246
|
let result;
|
|
238
247
|
try {
|
|
239
248
|
result = await this.withBudget(derivation, this.budgets.deriveMs, "derivation");
|
|
240
249
|
}
|
|
241
250
|
catch (err) {
|
|
242
|
-
// Out of budget, or the call threw.
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
251
|
+
// Out of budget, or the call threw. The turn has not said what this task
|
|
252
|
+
// is to act on, so every target is unnamed (R-PARAM-3).
|
|
253
|
+
const blocked = blocking(targetKeys(scenario.paramsObject));
|
|
254
|
+
if (blocked.length > 0) {
|
|
255
|
+
return decline(`target ${blocked[0]} not found — ${errText(err)}`, "missing_target");
|
|
256
|
+
}
|
|
257
|
+
logLine("replay.targets_unread", {
|
|
258
|
+
scenario: scenario.id,
|
|
259
|
+
why: "the derivation did not answer, and this chain takes no target from the caller",
|
|
260
|
+
error: errText(err),
|
|
261
|
+
});
|
|
247
262
|
}
|
|
248
|
-
if (!result.derived) {
|
|
263
|
+
if (result && !result.derived) {
|
|
249
264
|
// Nobody read the turn — no key here and no service to ask, or the
|
|
250
265
|
// service declined. Settings can still take their recorded values, but a
|
|
251
266
|
// target is a guess nobody is allowed to make (R-PARAM-5). The reason
|
|
252
267
|
// travels into the line, because "it did not run" without one is how
|
|
253
268
|
// this stayed invisible before.
|
|
254
|
-
|
|
255
|
-
|
|
269
|
+
//
|
|
270
|
+
// Every target is judged, not `missing`: an answer that derived nothing
|
|
271
|
+
// carries the recorded sample for *all* of them, so a chain that reads
|
|
272
|
+
// one would run on last week's value rather than on nothing.
|
|
273
|
+
const blocked = blocking(targetKeys(scenario.paramsObject));
|
|
274
|
+
if (blocked.length > 0) {
|
|
275
|
+
return decline(`${result.reason ?? "no_derive_key"}: target ${blocked[0]}`, "no_derive_key");
|
|
276
|
+
}
|
|
256
277
|
}
|
|
257
|
-
if (result.missing.length > 0) {
|
|
258
|
-
|
|
278
|
+
else if (result && result.missing.length > 0) {
|
|
279
|
+
const blocked = blocking(result.missing);
|
|
280
|
+
if (blocked.length > 0) {
|
|
281
|
+
return decline(`target ${blocked[0]} not found`, "missing_target");
|
|
282
|
+
}
|
|
283
|
+
// Worth a line of its own: this is the difference between a scenario
|
|
284
|
+
// that never runs and one that does, and it is the first thing to look
|
|
285
|
+
// at when a replay acts on the wrong thing.
|
|
286
|
+
logLine("replay.targets_found_by_chain", {
|
|
287
|
+
scenario: scenario.id,
|
|
288
|
+
unnamed: result.missing.join(","),
|
|
289
|
+
why: "the chain computes these from its own steps; none is read from the caller",
|
|
290
|
+
});
|
|
259
291
|
}
|
|
260
292
|
}
|
|
261
293
|
// A plan armed by intent runs inside a task the agent is already doing, so
|
|
@@ -681,10 +713,14 @@ export class ReplayController {
|
|
|
681
713
|
const total = state.deriveCostUsd + d.sessionCostUsd + state.fallbackCostUsd;
|
|
682
714
|
const steps = this.stepResultsOf(state);
|
|
683
715
|
const isBaseline = state.outcome === "not_steered" || state.outcome === "failed";
|
|
684
|
-
// A costless decline is noise on both sides — *unless*
|
|
685
|
-
//
|
|
686
|
-
// service accepts a costless report that
|
|
687
|
-
|
|
716
|
+
// A costless decline is noise on both sides — *unless* it says something the
|
|
717
|
+
// recording page cannot learn any other way: a step that actually broke, or
|
|
718
|
+
// the gate that declined. The service accepts a costless report that carries
|
|
719
|
+
// either (its errorshandling.md).
|
|
720
|
+
if (isBaseline &&
|
|
721
|
+
total <= 0 &&
|
|
722
|
+
!state.declined &&
|
|
723
|
+
!steps?.some((s) => s.status === "failed")) {
|
|
688
724
|
return undefined;
|
|
689
725
|
}
|
|
690
726
|
const blame = this.blameStep(steps);
|
|
@@ -709,6 +745,10 @@ export class ReplayController {
|
|
|
709
745
|
errorStage: blame?.stage,
|
|
710
746
|
errorStepIndex: blame?.stepIndex,
|
|
711
747
|
errorToolName: blame?.toolName,
|
|
748
|
+
// Which gate said no. Only ever set on a decline, and the decline is
|
|
749
|
+
// always `not_steered`, so a steered report carries it byte-identically
|
|
750
|
+
// to before.
|
|
751
|
+
declined: state.declined,
|
|
712
752
|
// Where the plan handed the task to the model (fallbk.md). Only on
|
|
713
753
|
// `fell_back`: a hand-over before any step ran is `failed`, and a parked
|
|
714
754
|
// step found after a divergence is still a divergence.
|
|
@@ -1292,11 +1332,11 @@ export class ReplayController {
|
|
|
1292
1332
|
});
|
|
1293
1333
|
}
|
|
1294
1334
|
}
|
|
1295
|
-
/**
|
|
1296
|
-
function
|
|
1335
|
+
/** Every target of a schema. A parameter with no kind is one (R-PARAM-3). */
|
|
1336
|
+
function targetKeys(schema) {
|
|
1297
1337
|
if (!schema)
|
|
1298
|
-
return
|
|
1299
|
-
return Object.keys(schema).
|
|
1338
|
+
return [];
|
|
1339
|
+
return Object.keys(schema).filter((k) => schema[k]?.kind !== "setting");
|
|
1300
1340
|
}
|
|
1301
1341
|
/**
|
|
1302
1342
|
* Position of the first step whose failure count is above `maxStepFailures`
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which missing targets actually stop a plan (segmented.md R-PARAM-3).
|
|
3
|
+
*
|
|
4
|
+
* The rule a target exists for is "never do the right work on the wrong thing":
|
|
5
|
+
* a value the turn did not name must never be filled in from the recording. The
|
|
6
|
+
* gate that enforces it used to read `missing.length > 0` and decline, which is
|
|
7
|
+
* the same thing only when every target is something the chain *takes from the
|
|
8
|
+
* caller*.
|
|
9
|
+
*
|
|
10
|
+
* It often is not. An analysis routinely lists a value that a later step
|
|
11
|
+
* computes from an earlier step's output and keeps the caller's copy as a bare
|
|
12
|
+
* fallback:
|
|
13
|
+
*
|
|
14
|
+
* step 0 return { fleet: parameters.fleet };
|
|
15
|
+
* step 1 return { id: (respParams.rankedDeviceIds || [])[0] ?? parameters.id };
|
|
16
|
+
*
|
|
17
|
+
* `id` is classified a target — it is what the second call acts on and it does
|
|
18
|
+
* change between requests — but no prompt ever names it, because the chain
|
|
19
|
+
* discovers it. Declining on it meant a scenario that matched, was ready, was
|
|
20
|
+
* enabled and would have run perfectly never ran at all, on every turn, with the
|
|
21
|
+
* reason only in a stderr line.
|
|
22
|
+
*
|
|
23
|
+
* So the question is not "did the turn name every target" but the narrower one
|
|
24
|
+
* the rule actually cares about: **could this plan act on the recorded value of
|
|
25
|
+
* a target the turn did not name?** It could, in exactly two ways:
|
|
26
|
+
*
|
|
27
|
+
* 1. some step reads `parameters.<key>` outright, rather than as the fallback
|
|
28
|
+
* of a value computed at run time; or
|
|
29
|
+
* 2. some step's logic carries the recorded sample as a literal, so the value
|
|
30
|
+
* is baked into the chain whether or not anybody reads the parameter.
|
|
31
|
+
*
|
|
32
|
+
* Neither true means the recorded value cannot reach a tool. The missing target
|
|
33
|
+
* stays `null` all the way through — {@link ./derive.ts} sets it, `paramsLogic`
|
|
34
|
+
* copies it, the `??` passes over it — so a chain that can compute the value
|
|
35
|
+
* runs on this turn's value, and one that cannot calls a tool with `null` and
|
|
36
|
+
* falls back to the model. What never happens, either way, is last week's fleet.
|
|
37
|
+
*
|
|
38
|
+
* Only frame 0 is judged here. A called segment runs on the parameters its
|
|
39
|
+
* call's `paramMapLogic` builds, not on these, so that body is scanned as a
|
|
40
|
+
* consumer and the segment's own steps are not (R-CALL-29).
|
|
41
|
+
*/
|
|
42
|
+
import type { FlatEntry } from "./flatten.js";
|
|
43
|
+
import type { ParamsSchema } from "./types.js";
|
|
44
|
+
/**
|
|
45
|
+
* The subset of `missing` this plan must decline on.
|
|
46
|
+
*
|
|
47
|
+
* Empty means every target the turn left unnamed is one the chain finds for
|
|
48
|
+
* itself. `missing` is returned unchanged whenever the schema or the plan is
|
|
49
|
+
* missing, so a caller that knows less than this one is never made to guess.
|
|
50
|
+
*/
|
|
51
|
+
export declare function blockingTargets(missing: readonly string[], schema: ParamsSchema | null | undefined, planned: readonly FlatEntry[]): string[];
|
|
52
|
+
//# sourceMappingURL=targets.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which missing targets actually stop a plan (segmented.md R-PARAM-3).
|
|
3
|
+
*
|
|
4
|
+
* The rule a target exists for is "never do the right work on the wrong thing":
|
|
5
|
+
* a value the turn did not name must never be filled in from the recording. The
|
|
6
|
+
* gate that enforces it used to read `missing.length > 0` and decline, which is
|
|
7
|
+
* the same thing only when every target is something the chain *takes from the
|
|
8
|
+
* caller*.
|
|
9
|
+
*
|
|
10
|
+
* It often is not. An analysis routinely lists a value that a later step
|
|
11
|
+
* computes from an earlier step's output and keeps the caller's copy as a bare
|
|
12
|
+
* fallback:
|
|
13
|
+
*
|
|
14
|
+
* step 0 return { fleet: parameters.fleet };
|
|
15
|
+
* step 1 return { id: (respParams.rankedDeviceIds || [])[0] ?? parameters.id };
|
|
16
|
+
*
|
|
17
|
+
* `id` is classified a target — it is what the second call acts on and it does
|
|
18
|
+
* change between requests — but no prompt ever names it, because the chain
|
|
19
|
+
* discovers it. Declining on it meant a scenario that matched, was ready, was
|
|
20
|
+
* enabled and would have run perfectly never ran at all, on every turn, with the
|
|
21
|
+
* reason only in a stderr line.
|
|
22
|
+
*
|
|
23
|
+
* So the question is not "did the turn name every target" but the narrower one
|
|
24
|
+
* the rule actually cares about: **could this plan act on the recorded value of
|
|
25
|
+
* a target the turn did not name?** It could, in exactly two ways:
|
|
26
|
+
*
|
|
27
|
+
* 1. some step reads `parameters.<key>` outright, rather than as the fallback
|
|
28
|
+
* of a value computed at run time; or
|
|
29
|
+
* 2. some step's logic carries the recorded sample as a literal, so the value
|
|
30
|
+
* is baked into the chain whether or not anybody reads the parameter.
|
|
31
|
+
*
|
|
32
|
+
* Neither true means the recorded value cannot reach a tool. The missing target
|
|
33
|
+
* stays `null` all the way through — {@link ./derive.ts} sets it, `paramsLogic`
|
|
34
|
+
* copies it, the `??` passes over it — so a chain that can compute the value
|
|
35
|
+
* runs on this turn's value, and one that cannot calls a tool with `null` and
|
|
36
|
+
* falls back to the model. What never happens, either way, is last week's fleet.
|
|
37
|
+
*
|
|
38
|
+
* Only frame 0 is judged here. A called segment runs on the parameters its
|
|
39
|
+
* call's `paramMapLogic` builds, not on these, so that body is scanned as a
|
|
40
|
+
* consumer and the segment's own steps are not (R-CALL-29).
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* The subset of `missing` this plan must decline on.
|
|
44
|
+
*
|
|
45
|
+
* Empty means every target the turn left unnamed is one the chain finds for
|
|
46
|
+
* itself. `missing` is returned unchanged whenever the schema or the plan is
|
|
47
|
+
* missing, so a caller that knows less than this one is never made to guess.
|
|
48
|
+
*/
|
|
49
|
+
export function blockingTargets(missing, schema, planned) {
|
|
50
|
+
if (missing.length === 0)
|
|
51
|
+
return [];
|
|
52
|
+
if (!schema || planned.length === 0)
|
|
53
|
+
return [...missing];
|
|
54
|
+
const bodies = callerBodies(planned);
|
|
55
|
+
// Nothing to read the parameters: a plan with no logic at all cannot act on
|
|
56
|
+
// anything, but it is also not a shape worth reasoning about — keep it strict.
|
|
57
|
+
if (bodies.length === 0)
|
|
58
|
+
return [...missing];
|
|
59
|
+
return missing.filter((key) => bodies.some((body) => readsOutright(body, key) || carriesLiteral(body, schema[key]?.sampleValue)));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Every logic body that runs with the caller's own `parameters`.
|
|
63
|
+
*
|
|
64
|
+
* Both of a step's bodies, because `toolOutputLogic` is handed `parameters`
|
|
65
|
+
* too and a value baked in there travels just as far. Plus the `paramMapLogic`
|
|
66
|
+
* of each segment called directly from frame 0: it reads the caller's
|
|
67
|
+
* parameters to build the segment's.
|
|
68
|
+
*/
|
|
69
|
+
function callerBodies(planned) {
|
|
70
|
+
const bodies = [];
|
|
71
|
+
const seenFrames = new Set();
|
|
72
|
+
for (const entry of planned) {
|
|
73
|
+
if (entry.depth === 0) {
|
|
74
|
+
if (entry.step.toolInputLogic)
|
|
75
|
+
bodies.push(entry.step.toolInputLogic);
|
|
76
|
+
if (entry.step.toolOutputLogic)
|
|
77
|
+
bodies.push(entry.step.toolOutputLogic);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// The frame's own mapping, once, and only when its caller is frame 0.
|
|
81
|
+
const frame = entry.frame;
|
|
82
|
+
if (frame.depth !== 1 || seenFrames.has(frame.id))
|
|
83
|
+
continue;
|
|
84
|
+
seenFrames.add(frame.id);
|
|
85
|
+
if (frame.paramMapLogic)
|
|
86
|
+
bodies.push(frame.paramMapLogic);
|
|
87
|
+
}
|
|
88
|
+
return bodies;
|
|
89
|
+
}
|
|
90
|
+
/** `parameters.key`, `parameters["key"]`, `parameters['key']` — all three forms. */
|
|
91
|
+
function referencePattern(key) {
|
|
92
|
+
const k = escapeRegExp(key);
|
|
93
|
+
return new RegExp(`parameters\\s*(?:\\.\\s*${k}\\b|\\[\\s*["']${k}["']\\s*\\])`, "g");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether `body` reads `key` other than as a fallback.
|
|
97
|
+
*
|
|
98
|
+
* A reference is a fallback when what stands immediately before it is `??` or
|
|
99
|
+
* `||` — the shape an analysis writes when the chain computes the value and
|
|
100
|
+
* keeps the caller's as a last resort. Every other reference is a read: the
|
|
101
|
+
* step wants the caller's value and nothing else will do.
|
|
102
|
+
*/
|
|
103
|
+
function readsOutright(body, key) {
|
|
104
|
+
const pattern = referencePattern(key);
|
|
105
|
+
for (let m = pattern.exec(body); m; m = pattern.exec(body)) {
|
|
106
|
+
const before = body.slice(0, m.index).trimEnd();
|
|
107
|
+
if (!before.endsWith("??") && !before.endsWith("||"))
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Whether `body` carries the recorded sample as a literal.
|
|
114
|
+
*
|
|
115
|
+
* This is the case the parameter reference cannot see: `return { id: 'dev_4411' }`
|
|
116
|
+
* acts on last week's device without mentioning `parameters` at all. Read
|
|
117
|
+
* generously — a sample too short to search for (`1`, `on`) counts as carried,
|
|
118
|
+
* because a false "this plan is safe" is the one answer this file must not give.
|
|
119
|
+
*/
|
|
120
|
+
function carriesLiteral(body, sample) {
|
|
121
|
+
if (sample === null || sample === undefined)
|
|
122
|
+
return false;
|
|
123
|
+
const text = typeof sample === "string" ? sample : JSON.stringify(sample);
|
|
124
|
+
if (typeof text !== "string" || text.length === 0)
|
|
125
|
+
return false;
|
|
126
|
+
if (text.length < 3)
|
|
127
|
+
return true;
|
|
128
|
+
return body.includes(text);
|
|
129
|
+
}
|
|
130
|
+
function escapeRegExp(text) {
|
|
131
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=targets.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basein/runner",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "A recording MCP proxy: sits between any MCP client and its MCP servers, executes each call on the client's behalf, and records the run as a reusable BaseIn scenario.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|