@basein/runner 0.2.11 → 0.2.12
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/README.md +33 -14
- package/dist/bin/bir-scenario.d.ts +11 -0
- package/dist/bin/bir-scenario.js +132 -34
- package/dist/bin/bir.d.ts +2 -2
- package/dist/bin/bir.js +11 -8
- package/dist/bin/investigate.js +1 -1
- package/dist/bin/scenario-edit.d.ts +36 -9
- package/dist/bin/scenario-edit.js +240 -61
- package/dist/control/server.js +18 -5
- package/dist/record/housekeeping.d.ts +71 -0
- package/dist/record/housekeeping.js +417 -0
- package/dist/replay/controller.d.ts +3 -1
- package/dist/replay/controller.js +17 -12
- package/docs/calculatedReplay.md +89 -29
- package/docs/calculatedReplayGuide.md +294 -86
- package/docs/quickstart.md +42 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,8 +148,21 @@ between the two is not guaranteed.
|
|
|
148
148
|
- **Change what the model does.** Tool descriptions are never edited. The
|
|
149
149
|
model-visible changes are schema relaxation, below, and — only with
|
|
150
150
|
`--replay` — the first-party `bir` server's tools: `run_scenario`, and three
|
|
151
|
-
that only read a scenario. The three
|
|
152
|
-
`bir scenario editing on`
|
|
151
|
+
that only read a scenario. The three for changing one (`scenario_check`,
|
|
152
|
+
`scenario_edit`, `scenario_undo`) appear only after `bir scenario editing on`
|
|
153
|
+
in that project (not yet released, see below).
|
|
154
|
+
- **Record its own `bir` calls** (next version, not yet released). A session
|
|
155
|
+
that fixes a plan is not a task to replay. The `bir` server's tools other
|
|
156
|
+
than `run_scenario`, and a `Bash` or `PowerShell` command made only of `bir`
|
|
157
|
+
commands, are never recorded as steps and never count as leaving a plan.
|
|
158
|
+
`npm test && bir investigate` is still recorded: `npm test` is real work.
|
|
159
|
+
- **Sandbox scenario code on this machine.** A calculated or hand-edited step
|
|
160
|
+
runs here with `new Function`, next to your sign-in and your wrapped servers.
|
|
161
|
+
The service will sandbox its own runs of the same code (with its next deploy);
|
|
162
|
+
the runner does not. The service's check of a hand edit only judges what the
|
|
163
|
+
code returns for the recording's values, so it cannot prove what the code
|
|
164
|
+
does here: read a hand edit before it is saved. See
|
|
165
|
+
[the design's §13.1](docs/calculatedReplay.md).
|
|
153
166
|
|
|
154
167
|
---
|
|
155
168
|
|
|
@@ -194,22 +207,28 @@ sequence runs through connections the proxies already hold — **zero model toke
|
|
|
194
207
|
scenarios steer the live session instead, pinning each step's arguments. Either
|
|
195
208
|
way the turn reports what it cost, so the savings ledger measures something real.
|
|
196
209
|
|
|
197
|
-
**Fixing a step.**
|
|
198
|
-
`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
`bir scenario
|
|
204
|
-
|
|
205
|
-
|
|
210
|
+
**Fixing a step.** Built 2026-09-25, not yet released: it needs the next
|
|
211
|
+
`@basein/runner` version and a service deploy. A calculated step is code, and
|
|
212
|
+
code can be wrong. `bir investigate` names such a step; `bir scenario edit`
|
|
213
|
+
fixes it, and the service checks the change against the recording before it
|
|
214
|
+
saves it. **A change is saved only if it reproduces the recording and is not a
|
|
215
|
+
copy — or on purpose, with `--force --note "why"`.** Only the owner can change
|
|
216
|
+
a scenario. `bir scenario undo` takes changes back one at a time: run it again
|
|
217
|
+
to go one further back. An undo is checked too, and is refused if it would
|
|
218
|
+
break a later step or the final answer, unless you add `--force --note "why"`.
|
|
219
|
+
Claude can do all of this: in its terminal, or with the `bir` server's tools
|
|
220
|
+
once `bir scenario editing on` was run in the project. Step by step, with real
|
|
221
|
+
output:
|
|
222
|
+
[the guide's §9.2](docs/calculatedReplayGuide.md).
|
|
206
223
|
|
|
207
224
|
```bash
|
|
208
|
-
bir scenario show scn_… --step 3 # read one step
|
|
225
|
+
bir scenario show scn_… --step 3 # read one step (scenarioId and chainRevision first)
|
|
209
226
|
bir scenario check scn_… --step 3 --input-logic step3.js # try a change; nothing is saved
|
|
210
227
|
bir scenario edit scn_… --step 3 --input-logic step3.js --note "why" # the same check, then save
|
|
211
|
-
bir scenario edits scn_… # the history
|
|
212
|
-
bir scenario undo scn_… #
|
|
228
|
+
bir scenario edits scn_… # the history, newest first
|
|
229
|
+
bir scenario undo scn_… # take back the newest edit; again to go further back
|
|
230
|
+
bir scenario undo scn_… --step 3 # take back step 3's newest edit only
|
|
231
|
+
bir scenario calc scn_… --force --discard-edits # recalculate this plan, dropping its hand edits
|
|
213
232
|
bir scenario editing on # let Claude's `bir` tools change scenarios here
|
|
214
233
|
```
|
|
215
234
|
|
|
@@ -26,6 +26,17 @@
|
|
|
26
26
|
* Logic bodies travel through temporary files, never argv, and a non-zero exit
|
|
27
27
|
* is an `isError` result that carries the output, refusal and report included.
|
|
28
28
|
*
|
|
29
|
+
* A CALL IN FLIGHT IS OWNED UNTIL IT ENDS. Each scenario call's `bir` child and
|
|
30
|
+
* its temporary directory are tracked. When the host goes away (stdin closes,
|
|
31
|
+
* SIGINT, SIGTERM) the children are stopped and the directories removed before
|
|
32
|
+
* the process exits — `process.exit` never resumes a pending `finally`, so
|
|
33
|
+
* cleanup left to one was cleanup that did not happen, and an orphaned `bir`
|
|
34
|
+
* went on to save an edit for a session that had ended (C13). A
|
|
35
|
+
* `notifications/cancelled` (the person pressed Esc) stops that call's child
|
|
36
|
+
* the same way, and the call gets no answer, as MCP asks. A request the
|
|
37
|
+
* service already received may still complete there; nothing here can call
|
|
38
|
+
* it back, and `bir scenario edits` shows it.
|
|
39
|
+
*
|
|
29
40
|
* The tools that CHANGE a scenario are offered only where a person ran
|
|
30
41
|
* `bir scenario editing on` (D2): this server runs in every session of every
|
|
31
42
|
* installed project, a fleet included, and a plan must not change because some
|
package/dist/bin/bir-scenario.js
CHANGED
|
@@ -26,6 +26,17 @@
|
|
|
26
26
|
* Logic bodies travel through temporary files, never argv, and a non-zero exit
|
|
27
27
|
* is an `isError` result that carries the output, refusal and report included.
|
|
28
28
|
*
|
|
29
|
+
* A CALL IN FLIGHT IS OWNED UNTIL IT ENDS. Each scenario call's `bir` child and
|
|
30
|
+
* its temporary directory are tracked. When the host goes away (stdin closes,
|
|
31
|
+
* SIGINT, SIGTERM) the children are stopped and the directories removed before
|
|
32
|
+
* the process exits — `process.exit` never resumes a pending `finally`, so
|
|
33
|
+
* cleanup left to one was cleanup that did not happen, and an orphaned `bir`
|
|
34
|
+
* went on to save an edit for a session that had ended (C13). A
|
|
35
|
+
* `notifications/cancelled` (the person pressed Esc) stops that call's child
|
|
36
|
+
* the same way, and the call gets no answer, as MCP asks. A request the
|
|
37
|
+
* service already received may still complete there; nothing here can call
|
|
38
|
+
* it back, and `bir scenario edits` shows it.
|
|
39
|
+
*
|
|
29
40
|
* The tools that CHANGE a scenario are offered only where a person ran
|
|
30
41
|
* `bir scenario editing on` (D2): this server runs in every session of every
|
|
31
42
|
* installed project, a fleet included, and a plan must not change because some
|
|
@@ -37,7 +48,7 @@
|
|
|
37
48
|
* stream, and every log line goes to stderr instead.
|
|
38
49
|
*/
|
|
39
50
|
import { readFrames, writeFrame } from "../jsonrpc/framing.js";
|
|
40
|
-
import { JsonRpcErrorCode, errorResponse, isRequest, paramsObject, } from "../jsonrpc/types.js";
|
|
51
|
+
import { JsonRpcErrorCode, errorResponse, isNotification, isRequest, paramsObject, } from "../jsonrpc/types.js";
|
|
41
52
|
import { spawn } from "node:child_process";
|
|
42
53
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
43
54
|
import { tmpdir } from "node:os";
|
|
@@ -134,7 +145,7 @@ class BadArguments extends Error {
|
|
|
134
145
|
}
|
|
135
146
|
const TARGET = {
|
|
136
147
|
type: "string",
|
|
137
|
-
description: "
|
|
148
|
+
description: "A scn_ id, or the run_ id of a whole-run scenario (a sub-task scenario has only its scn_ id) — as investigate and scenario_show print them.",
|
|
138
149
|
};
|
|
139
150
|
const STEP = {
|
|
140
151
|
type: "integer",
|
|
@@ -185,13 +196,22 @@ function flag(args, key, name) {
|
|
|
185
196
|
throw new BadArguments(`\`${key}\` must be true or false`);
|
|
186
197
|
return [name];
|
|
187
198
|
}
|
|
199
|
+
/** `--note <text>`. The text is one argv element, so no quoting can split it. */
|
|
200
|
+
function note(args) {
|
|
201
|
+
const value = args.note;
|
|
202
|
+
if (value === undefined || value === null)
|
|
203
|
+
return [];
|
|
204
|
+
if (typeof value !== "string")
|
|
205
|
+
throw new BadArguments("`note` must be a string");
|
|
206
|
+
return ["--note", value];
|
|
207
|
+
}
|
|
188
208
|
const SCENARIO_TOOLS = [
|
|
189
209
|
{
|
|
190
210
|
name: "scenario_show",
|
|
191
211
|
changes: false,
|
|
192
212
|
description: "Read a calculated scenario of this account: the whole plan, or with `step` just that step — its input logic, " +
|
|
193
213
|
"output logic, whether it is marked *needs a judgement* (nondeterministic) and the recorded input and output " +
|
|
194
|
-
"it has to reproduce. Read a step here before changing it.",
|
|
214
|
+
"it has to reproduce, with the scenario's `scenarioId` and `chainRevision` at the top. Read a step here before changing it.",
|
|
195
215
|
inputSchema: {
|
|
196
216
|
type: "object",
|
|
197
217
|
properties: { target: TARGET, step: STEP },
|
|
@@ -266,9 +286,11 @@ const SCENARIO_TOOLS = [
|
|
|
266
286
|
changes: true,
|
|
267
287
|
description: "Save a change to one step of a calculated scenario. It runs the same check as scenario_check and saves only " +
|
|
268
288
|
"if the step reproduces the recording and is not a copy of the recorded call; always give a `note` saying why, " +
|
|
269
|
-
"and `revision` (the chainRevision scenario_show
|
|
289
|
+
"and `revision` (the `chainRevision` at the top of a scenario_show answer with `step`) so a plan that changed " +
|
|
290
|
+
"meanwhile is not overwritten. " +
|
|
270
291
|
"`force` saves what the check refused and is only for a recording that was itself wrong — never a way past a " +
|
|
271
|
-
"failing check.
|
|
292
|
+
"failing check. A save can be taken back with scenario_undo, which the service checks too: it refuses an undo " +
|
|
293
|
+
"that would break a later step, and a recalculation with --discard-edits replaces edits for good.",
|
|
272
294
|
inputSchema: {
|
|
273
295
|
type: "object",
|
|
274
296
|
properties: {
|
|
@@ -289,7 +311,7 @@ const SCENARIO_TOOLS = [
|
|
|
289
311
|
revision: {
|
|
290
312
|
type: "integer",
|
|
291
313
|
minimum: 1,
|
|
292
|
-
description: "The chainRevision the change was made against
|
|
314
|
+
description: "The chainRevision the change was made against — at the top of a scenario_show answer with `step`; the save is refused if the plan moved on.",
|
|
293
315
|
},
|
|
294
316
|
},
|
|
295
317
|
required: ["target", "step"],
|
|
@@ -307,11 +329,7 @@ const SCENARIO_TOOLS = [
|
|
|
307
329
|
...flag(a, "unfreeze", "--unfreeze"),
|
|
308
330
|
...flag(a, "force", "--force"),
|
|
309
331
|
];
|
|
310
|
-
|
|
311
|
-
if (typeof a.note !== "string")
|
|
312
|
-
throw new BadArguments("`note` must be a string");
|
|
313
|
-
argv.push("--note", a.note);
|
|
314
|
-
}
|
|
332
|
+
argv.push(...note(a));
|
|
315
333
|
if (a.revision !== undefined && a.revision !== null) {
|
|
316
334
|
if (typeof a.revision !== "number" || !Number.isInteger(a.revision) || a.revision < 1) {
|
|
317
335
|
throw new BadArguments("`revision` must be a whole number from 1");
|
|
@@ -324,13 +342,24 @@ const SCENARIO_TOOLS = [
|
|
|
324
342
|
{
|
|
325
343
|
name: "scenario_undo",
|
|
326
344
|
changes: true,
|
|
327
|
-
description: "
|
|
328
|
-
"edit that can be undone
|
|
345
|
+
description: "Take back a hand edit of a calculated scenario: its step goes back as it was before that edit. Without `edit`, " +
|
|
346
|
+
"the newest edit that can be undone (of `step`, when given); calling it again goes one further back. The service " +
|
|
347
|
+
"checks the undo like a save and refuses it when a later step or the final answer would break — `force` with a " +
|
|
348
|
+
"`note` undoes it anyway. To redo, undo the undo: pass its sedit_ id as `edit` (the answer names it).",
|
|
329
349
|
inputSchema: {
|
|
330
350
|
type: "object",
|
|
331
351
|
properties: {
|
|
332
352
|
target: TARGET,
|
|
333
|
-
edit: { type: "string", description: "The sedit_ id to undo, as scenario_edits lists it." },
|
|
353
|
+
edit: { type: "string", description: "The sedit_ id to undo, as scenario_edits lists it. Not with `step`." },
|
|
354
|
+
step: {
|
|
355
|
+
...STEP,
|
|
356
|
+
description: "Undo the newest edit of this step (its stepIndex, as scenario_show prints it). Not with `edit`.",
|
|
357
|
+
},
|
|
358
|
+
force: {
|
|
359
|
+
type: "boolean",
|
|
360
|
+
description: "Undo although the check refused it (a later step or the answer would break). Needs `note`.",
|
|
361
|
+
},
|
|
362
|
+
note: { type: "string", description: "Why; kept in the history. Required with `force`." },
|
|
334
363
|
},
|
|
335
364
|
required: ["target"],
|
|
336
365
|
additionalProperties: false,
|
|
@@ -341,8 +370,12 @@ const SCENARIO_TOOLS = [
|
|
|
341
370
|
if (typeof a.edit !== "string" || !/^sedit_[\w-]+$/.test(a.edit)) {
|
|
342
371
|
throw new BadArguments("`edit` must be a sedit_… id");
|
|
343
372
|
}
|
|
373
|
+
if (a.step !== undefined && a.step !== null) {
|
|
374
|
+
throw new BadArguments("`edit` names one entry and `step` picks one; give one of them");
|
|
375
|
+
}
|
|
344
376
|
argv.push("--edit", a.edit);
|
|
345
377
|
}
|
|
378
|
+
argv.push(...step(a, false), ...flag(a, "force", "--force"), ...note(a));
|
|
346
379
|
return { argv };
|
|
347
380
|
},
|
|
348
381
|
},
|
|
@@ -372,7 +405,36 @@ function listTools(cwd) {
|
|
|
372
405
|
function birPath() {
|
|
373
406
|
return resolvePath(dirname(fileURLToPath(import.meta.url)), "bir.js");
|
|
374
407
|
}
|
|
375
|
-
|
|
408
|
+
/** Scenario tool calls in flight, by JSON-RPC request id. */
|
|
409
|
+
const inFlight = new Map();
|
|
410
|
+
/** Remove a call's logic directory, once. Never throws: this runs on the way out. */
|
|
411
|
+
function removeDir(flight) {
|
|
412
|
+
const dir = flight.dir;
|
|
413
|
+
if (!dir)
|
|
414
|
+
return;
|
|
415
|
+
flight.dir = undefined;
|
|
416
|
+
try {
|
|
417
|
+
// A child killed a moment ago may still hold a handle on Windows; retry briefly.
|
|
418
|
+
rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
logLine("scenario.tmp_left", { dir, error: errText(err) });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/** Stop a call's `bir` (if it still runs) and remove its logic files. Synchronous: it runs before `process.exit`. */
|
|
425
|
+
function stopFlight(flight) {
|
|
426
|
+
const child = flight.child;
|
|
427
|
+
if (child && child.exitCode === null && child.signalCode === null) {
|
|
428
|
+
try {
|
|
429
|
+
child.kill();
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// Already gone.
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
removeDir(flight);
|
|
436
|
+
}
|
|
437
|
+
function runBir(argv, cwd, flight) {
|
|
376
438
|
return new Promise((resolve) => {
|
|
377
439
|
const child = spawn(process.execPath, [...nodeFlagsToCarry(), birPath(), ...argv], {
|
|
378
440
|
cwd,
|
|
@@ -381,6 +443,8 @@ function runBir(argv, cwd) {
|
|
|
381
443
|
stdio: ["ignore", "pipe", "pipe"],
|
|
382
444
|
windowsHide: true,
|
|
383
445
|
});
|
|
446
|
+
if (flight)
|
|
447
|
+
flight.child = child;
|
|
384
448
|
let stdout = "";
|
|
385
449
|
let stderr = "";
|
|
386
450
|
let timedOut = false;
|
|
@@ -412,9 +476,11 @@ const failed = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
|
412
476
|
* Run one scenario tool. Never throws. The logic bodies are written to a
|
|
413
477
|
* directory of their own under the OS temp directory, owner-only, and the
|
|
414
478
|
* directory is removed whatever happens — the code may carry a table name or a
|
|
415
|
-
* host the person would not want left lying around.
|
|
479
|
+
* host the person would not want left lying around. "Whatever happens"
|
|
480
|
+
* includes the host leaving mid-call: the directory and the child live in
|
|
481
|
+
* `flight`, which `main` stops on the way out ({@link stopFlight}).
|
|
416
482
|
*/
|
|
417
|
-
async function callScenarioTool(tool, args, cwd) {
|
|
483
|
+
async function callScenarioTool(tool, args, cwd, flight = { tool: tool.name, cancelled: false }) {
|
|
418
484
|
let plan;
|
|
419
485
|
try {
|
|
420
486
|
plan = tool.plan(args);
|
|
@@ -422,11 +488,11 @@ async function callScenarioTool(tool, args, cwd) {
|
|
|
422
488
|
catch (err) {
|
|
423
489
|
return failed(`invalid arguments for ${tool.name}: ${errText(err)}`);
|
|
424
490
|
}
|
|
425
|
-
let dir;
|
|
426
491
|
try {
|
|
427
492
|
const argv = [...plan.argv];
|
|
428
493
|
if (plan.inputLogic !== undefined || plan.outputLogic !== undefined) {
|
|
429
|
-
dir = mkdtempSync(join(tmpdir(), "bir-logic-"));
|
|
494
|
+
const dir = mkdtempSync(join(tmpdir(), "bir-logic-"));
|
|
495
|
+
flight.dir = dir;
|
|
430
496
|
if (plan.inputLogic !== undefined) {
|
|
431
497
|
const file = join(dir, "input.js");
|
|
432
498
|
writeFileSync(file, plan.inputLogic, { encoding: "utf8", mode: 0o600 });
|
|
@@ -439,7 +505,11 @@ async function callScenarioTool(tool, args, cwd) {
|
|
|
439
505
|
}
|
|
440
506
|
}
|
|
441
507
|
argv.push("--json");
|
|
442
|
-
|
|
508
|
+
if (flight.cancelled)
|
|
509
|
+
return failed("cancelled before bir started");
|
|
510
|
+
const run = await runBir(argv, cwd, flight);
|
|
511
|
+
if (flight.cancelled)
|
|
512
|
+
return failed("cancelled");
|
|
443
513
|
if (run.timedOut)
|
|
444
514
|
return failed(`bir did not finish in ${BIR_TIMEOUT_MS / 1000} s`);
|
|
445
515
|
const stdout = run.stdout.trim();
|
|
@@ -454,18 +524,46 @@ async function callScenarioTool(tool, args, cwd) {
|
|
|
454
524
|
return failed(`could not run bir: ${errText(err)}`);
|
|
455
525
|
}
|
|
456
526
|
finally {
|
|
457
|
-
|
|
458
|
-
rmSync(dir, { recursive: true, force: true });
|
|
527
|
+
removeDir(flight);
|
|
459
528
|
}
|
|
460
529
|
}
|
|
461
530
|
function main() {
|
|
462
531
|
const cwd = process.cwd();
|
|
463
532
|
let closing = false;
|
|
464
533
|
const reply = (msg) => writeFrame(process.stdout, msg);
|
|
534
|
+
/**
|
|
535
|
+
* The host is gone: stop every `bir` still running for it and remove its
|
|
536
|
+
* logic files, then exit. Everything here is synchronous, because nothing
|
|
537
|
+
* after `process.exit` runs — not a pending call's `finally` either.
|
|
538
|
+
*/
|
|
539
|
+
const shutdown = (why) => {
|
|
540
|
+
if (closing)
|
|
541
|
+
return;
|
|
542
|
+
closing = true;
|
|
543
|
+
if (inFlight.size > 0) {
|
|
544
|
+
logLine("scenario.shutdown", { why, stopped: [...inFlight.values()].map((f) => f.tool) });
|
|
545
|
+
}
|
|
546
|
+
for (const flight of inFlight.values())
|
|
547
|
+
stopFlight(flight);
|
|
548
|
+
inFlight.clear();
|
|
549
|
+
process.exit(0);
|
|
550
|
+
};
|
|
465
551
|
readFrames(process.stdin, {
|
|
466
552
|
onMessage: (msg) => {
|
|
467
|
-
if (!isRequest(msg))
|
|
468
|
-
|
|
553
|
+
if (!isRequest(msg)) {
|
|
554
|
+
// Notifications need no answer; one says to stop a call (MCP
|
|
555
|
+
// `notifications/cancelled`), and a stopped call is answered by nobody.
|
|
556
|
+
if (isNotification(msg) && msg.method === "notifications/cancelled") {
|
|
557
|
+
const requestId = paramsObject(msg.params).requestId;
|
|
558
|
+
const flight = typeof requestId === "string" || typeof requestId === "number" ? inFlight.get(requestId) : undefined;
|
|
559
|
+
if (flight && !flight.cancelled) {
|
|
560
|
+
flight.cancelled = true;
|
|
561
|
+
logLine("scenario.tool_cancelled", { tool: flight.tool, running: flight.child !== undefined });
|
|
562
|
+
stopFlight(flight);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
469
567
|
const { id, method } = msg;
|
|
470
568
|
if (method === "initialize") {
|
|
471
569
|
const params = paramsObject(msg.params);
|
|
@@ -503,7 +601,12 @@ function main() {
|
|
|
503
601
|
return;
|
|
504
602
|
}
|
|
505
603
|
logDetail("scenario.tool", { cwd, tool: name });
|
|
506
|
-
|
|
604
|
+
const flight = { tool: name, cancelled: false };
|
|
605
|
+
inFlight.set(id, flight);
|
|
606
|
+
void callScenarioTool(scenarioTool, paramsObject(params.arguments), cwd, flight).then((result) => {
|
|
607
|
+
inFlight.delete(id);
|
|
608
|
+
if (flight.cancelled || closing)
|
|
609
|
+
return;
|
|
507
610
|
logLine("scenario.tool_answered", { tool: name, isError: result.isError === true });
|
|
508
611
|
reply({ jsonrpc: "2.0", id, result });
|
|
509
612
|
});
|
|
@@ -529,17 +632,12 @@ function main() {
|
|
|
529
632
|
reply(errorResponse(id, JsonRpcErrorCode.MethodNotFound, `unsupported method: ${method}`));
|
|
530
633
|
},
|
|
531
634
|
onParseError: (detail) => logLine("scenario.frame_error", { reason: detail.reason }),
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (closing)
|
|
535
|
-
return;
|
|
536
|
-
closing = true;
|
|
537
|
-
process.exit(0);
|
|
538
|
-
},
|
|
635
|
+
// The host closed our stdin: the session is over.
|
|
636
|
+
onEnd: () => shutdown("stdin closed"),
|
|
539
637
|
});
|
|
540
638
|
logLine("scenario.started", { cwd, pid: process.pid });
|
|
541
|
-
process.on("SIGINT", () =>
|
|
542
|
-
process.on("SIGTERM", () =>
|
|
639
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
640
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
543
641
|
process.on("uncaughtException", (err) => logLine("scenario.uncaught", { error: errText(err) }));
|
|
544
642
|
process.on("unhandledRejection", (err) => logLine("scenario.unhandled_rejection", { error: errText(err) }));
|
|
545
643
|
}
|
package/dist/bin/bir.d.ts
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* bir login | logout
|
|
13
13
|
* bir --version
|
|
14
14
|
*
|
|
15
|
-
* bir scenario list | show <runId|scnId> [--step <n>] | calc <runId> [--force [--discard-edits]]
|
|
15
|
+
* bir scenario list | show <runId|scnId> [--step <n>] | calc <runId|scnId> [--force [--discard-edits]]
|
|
16
16
|
* bir scenario check|edit <runId|scnId> --step <n> --input-logic <file|-> … ← fix one step
|
|
17
|
-
* bir scenario edits | undo <runId|scnId> [--edit <sedit_id>]
|
|
17
|
+
* bir scenario edits | undo <runId|scnId> [--edit <sedit_id> | --step <n>] [--force --note "why"]
|
|
18
18
|
* bir scenario editing on|off|status ← may this project's `bir` MCP server change a scenario?
|
|
19
19
|
* bir scenario replay <scnId> --prompt "…" [--dry] | bir replay status|on|off|allow …
|
|
20
20
|
* bir investigate [<id>] | list | executions
|
package/dist/bin/bir.js
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* bir login | logout
|
|
13
13
|
* bir --version
|
|
14
14
|
*
|
|
15
|
-
* bir scenario list | show <runId|scnId> [--step <n>] | calc <runId> [--force [--discard-edits]]
|
|
15
|
+
* bir scenario list | show <runId|scnId> [--step <n>] | calc <runId|scnId> [--force [--discard-edits]]
|
|
16
16
|
* bir scenario check|edit <runId|scnId> --step <n> --input-logic <file|-> … ← fix one step
|
|
17
|
-
* bir scenario edits | undo <runId|scnId> [--edit <sedit_id>]
|
|
17
|
+
* bir scenario edits | undo <runId|scnId> [--edit <sedit_id> | --step <n>] [--force --note "why"]
|
|
18
18
|
* bir scenario editing on|off|status ← may this project's `bir` MCP server change a scenario?
|
|
19
19
|
* bir scenario replay <scnId> --prompt "…" [--dry] | bir replay status|on|off|allow …
|
|
20
20
|
* bir investigate [<id>] | list | executions
|
|
@@ -72,16 +72,18 @@ Commands:
|
|
|
72
72
|
scenario list recorded runs and their calculated scenarios
|
|
73
73
|
scenario show <runId|scnId> [--step <n>]
|
|
74
74
|
a scenario: intent, params, steps (--step: just that one)
|
|
75
|
-
scenario calc <runId> [--force [--discard-edits]]
|
|
76
|
-
calculate (or re-derive) a run's scenario
|
|
75
|
+
scenario calc <runId|scnId> [--force [--discard-edits]]
|
|
76
|
+
calculate (or re-derive) a run's scenario; a scn_ id
|
|
77
|
+
(a sub-task's too) is recalculated in place, with --force
|
|
77
78
|
scenario check <runId|scnId> --step <n> [--input-logic <file|->] [--output-logic <file|->] [--unfreeze]
|
|
78
79
|
try a change to one step against the recording; saves nothing
|
|
79
80
|
scenario edit <runId|scnId> --step <n> [--input-logic <file|->] [--output-logic <file|->]
|
|
80
81
|
[--freeze | --unfreeze] [--note "why"] [--force --note "why"] [--revision <n>]
|
|
81
82
|
the same check, and save the change if it passes
|
|
82
83
|
scenario edits <runId|scnId> a scenario's hand edits, newest first
|
|
83
|
-
scenario undo <runId|scnId> [--edit <sedit_id>]
|
|
84
|
-
|
|
84
|
+
scenario undo <runId|scnId> [--edit <sedit_id> | --step <n>] [--force --note "why"]
|
|
85
|
+
take back the newest edit (of step n); run it again to go
|
|
86
|
+
one further back; undo an undo's sedit_ id to redo
|
|
85
87
|
scenario editing on|off|status whether this project's bir MCP server offers the tools
|
|
86
88
|
that change a scenario (restart Claude Code after)
|
|
87
89
|
scenario replay <scnId> --prompt "…" [--dry]
|
|
@@ -111,14 +113,15 @@ Options:
|
|
|
111
113
|
--dry replay against recorded outputs only; run no real tools
|
|
112
114
|
--force scenario calc: recalculate in place · scenario edit: save although
|
|
113
115
|
the check refused it (needs --note; only for a recording that was wrong)
|
|
114
|
-
|
|
116
|
+
· scenario undo: undo although a later step or the answer would break
|
|
117
|
+
--step <n> scenario show|check|edit|undo: the step, by the stepIndex \`show\` prints
|
|
115
118
|
--input-logic <file|-> scenario check|edit: the new input logic, from a file or stdin (-)
|
|
116
119
|
--output-logic <file|-> scenario check|edit: the new output logic, from a file or stdin (-)
|
|
117
120
|
--freeze scenario edit: mark the step "needs a judgement" (runs hand over before it)
|
|
118
121
|
--unfreeze scenario check|edit: ask to remove that mark; the check decides
|
|
119
122
|
--note <text> scenario edit|undo: why, kept in the history (required with --force)
|
|
120
123
|
--revision <n> scenario edit|undo: only if the plan is still at this chainRevision
|
|
121
|
-
--edit <id> scenario undo: the sedit_ id to undo (default: the newest that can be)
|
|
124
|
+
--edit <id> scenario undo: the sedit_ id to undo (default: the newest edit that can be)
|
|
122
125
|
--discard-edits scenario calc --force: recalculate a plan with hand edits, replacing them
|
|
123
126
|
--no-browser login: print the link and code, open nothing (SSH, headless)
|
|
124
127
|
--token <value> login: redeem a one-time setup token from the console (no browser)
|
package/dist/bin/investigate.js
CHANGED
|
@@ -179,7 +179,7 @@ export const DECLINES = {
|
|
|
179
179
|
},
|
|
180
180
|
known_bad_first_step: {
|
|
181
181
|
cause: "the chain's first step is parked after repeated failures.",
|
|
182
|
-
fix: "wait: the service retries it once an hour for a day, then repairs the plan from the next run of the prompt. To repair now: `bir scenario calc <
|
|
182
|
+
fix: "wait: the service retries it once an hour for a day, then repairs the plan from the next run of the prompt — unless the plan has steps edited by hand, or is a sub-task, which it does not repair on its own. To repair now: `bir scenario calc <scnId> --force` (add --discard-edits to drop hand edits), or fix the step: `bir scenario edit <scnId> --step 0 --input-logic <file>`. `bir investigate <scnId>` names the scenario id.",
|
|
183
183
|
},
|
|
184
184
|
nondeterministic_first_step: {
|
|
185
185
|
cause: "the chain's first step needs a judgement the calculation could not write code for, so the plan cannot start.",
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* bir scenario edit <runId|scnId> --step <n> [--input-logic <file|->] [--output-logic <file|->]
|
|
8
8
|
* [--freeze | --unfreeze] [--note "why"] [--force --note "why"] [--revision <n>]
|
|
9
9
|
* bir scenario edits <runId|scnId>
|
|
10
|
-
* bir scenario undo <runId|scnId> [--edit <sedit_id>]
|
|
11
|
-
* bir scenario calc <runId> [--force [--discard-edits]]
|
|
10
|
+
* bir scenario undo <runId|scnId> [--edit <sedit_id> | --step <n>] [--force --note "why"]
|
|
11
|
+
* bir scenario calc <runId|scnId> [--force [--discard-edits]]
|
|
12
12
|
* bir scenario editing on|off|status
|
|
13
13
|
*
|
|
14
14
|
* WHY THIS IS A CLIENT AND NOTHING MORE. Whether a change may be saved is
|
|
@@ -70,6 +70,10 @@ export interface EditReport {
|
|
|
70
70
|
before: MarkState;
|
|
71
71
|
after: MarkState;
|
|
72
72
|
};
|
|
73
|
+
/** The logic and mark asked for are what the step already has (rule 10). */
|
|
74
|
+
noChange?: boolean;
|
|
75
|
+
/** The check stopped at the service's sandbox-time budget: *too costly to check* (rule 11). */
|
|
76
|
+
budgetExceeded?: boolean;
|
|
73
77
|
problems: string[];
|
|
74
78
|
}
|
|
75
79
|
interface MarkState {
|
|
@@ -131,7 +135,7 @@ export interface ScenarioEditArgs {
|
|
|
131
135
|
freeze: boolean;
|
|
132
136
|
unfreeze: boolean;
|
|
133
137
|
note?: string;
|
|
134
|
-
/** `undo --edit`: the `sedit_` id to put back. */
|
|
138
|
+
/** `undo --edit`: the `sedit_` id to put back. `undo --step` is {@link step}. */
|
|
135
139
|
edit?: string;
|
|
136
140
|
/** `--revision`: save only if the plan is still at this `chainRevision`. */
|
|
137
141
|
revision?: number;
|
|
@@ -155,19 +159,42 @@ export declare function decodeLogic(bytes: Buffer): string;
|
|
|
155
159
|
* The report in the lines editSteps.md "What a person sees" shows:
|
|
156
160
|
* `input`, `output` (when the output logic changed), `later`, `answer` (when
|
|
157
161
|
* the final answer read anything before) and `mark`.
|
|
162
|
+
*
|
|
163
|
+
* `check` prints every line: it is where a person explores a change, and "step
|
|
164
|
+
* 5 still reproduces" is an answer there. `edit` and `undo` are acts, and print
|
|
165
|
+
* `brief`ly — what changed and what went wrong: `input` always, `output` when
|
|
166
|
+
* it changed, `later` only for a step that broke, `answer` only when it lost its
|
|
167
|
+
* values, `mark` only when it moves. The report is the same one either way (D11);
|
|
168
|
+
* only the lines that say "nothing to see" are left out.
|
|
169
|
+
*
|
|
170
|
+
* An `undo` does not judge the step itself — its code and mark go back exactly
|
|
171
|
+
* as they were (editSteps.md, "Undo") — so its report's input status is
|
|
172
|
+
* `not_checked` by design, and printing the usual reason for that ("the
|
|
173
|
+
* recorded call is plain text") would be false. It says what happened instead.
|
|
158
174
|
*/
|
|
159
|
-
export declare function renderReport(out: (line?: string) => void, r: EditReport
|
|
175
|
+
export declare function renderReport(out: (line?: string) => void, r: EditReport, opts?: {
|
|
176
|
+
brief?: boolean;
|
|
177
|
+
undo?: boolean;
|
|
178
|
+
}): void;
|
|
160
179
|
/**
|
|
161
180
|
* About {@link CONTEXT} characters either side of `at`. A text already that
|
|
162
181
|
* short — a window the service cut itself, or a small call — is printed whole.
|
|
163
182
|
*/
|
|
164
183
|
export declare function around(text: string, at: number): string;
|
|
165
184
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
185
|
+
* What `undo` without `--edit` takes back (D9): the newest **edit** — never an
|
|
186
|
+
* undo entry — that nothing has put back and no recalculation replaced; of
|
|
187
|
+
* step `step` when it is given.
|
|
188
|
+
*
|
|
189
|
+
* Undo entries are skipped on purpose. They used to be picked like any other,
|
|
190
|
+
* so a second `undo` redid the first one, and a step edited twice toggled
|
|
191
|
+
* between its two edits for ever and never got back to the calculation's code
|
|
192
|
+
* (C12). Now a second `undo` goes one further back; a redo is asked for by
|
|
193
|
+
* name, `--edit <the undo's id>`, which the undo prints.
|
|
194
|
+
*
|
|
195
|
+
* Whether the step is still exactly as that edit left it is the service's
|
|
196
|
+
* question, not this list's: it answers `not_latest_edit` when it is not.
|
|
170
197
|
*/
|
|
171
|
-
export declare function newestUndoable(edits: EditEntry[]): EditEntry | undefined;
|
|
198
|
+
export declare function newestUndoable(edits: EditEntry[], step?: number): EditEntry | undefined;
|
|
172
199
|
export {};
|
|
173
200
|
//# sourceMappingURL=scenario-edit.d.ts.map
|