@basein/runner 0.2.11 → 0.3.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.
Files changed (144) hide show
  1. package/README.md +540 -350
  2. package/dist/auth/client.d.ts +65 -3
  3. package/dist/auth/client.js +305 -53
  4. package/dist/bin/bir-hooks.d.ts +72 -31
  5. package/dist/bin/bir-hooks.js +176 -284
  6. package/dist/bin/bir-proxy.d.ts +17 -2
  7. package/dist/bin/bir-proxy.js +222 -28
  8. package/dist/bin/bir-scenario.d.ts +61 -10
  9. package/dist/bin/bir-scenario.js +704 -135
  10. package/dist/bin/bir.d.ts +14 -8
  11. package/dist/bin/bir.js +2937 -488
  12. package/dist/bin/investigate.js +41 -16
  13. package/dist/bin/scenario-edit.d.ts +43 -10
  14. package/dist/bin/scenario-edit.js +281 -80
  15. package/dist/bin/scenario-liveness.d.ts +194 -0
  16. package/dist/bin/scenario-liveness.js +459 -0
  17. package/dist/bin/scenario-stderr.d.ts +43 -0
  18. package/dist/bin/scenario-stderr.js +76 -0
  19. package/dist/bin/service-read.d.ts +88 -0
  20. package/dist/bin/service-read.js +128 -0
  21. package/dist/bin/setup.d.ts +19 -0
  22. package/dist/bin/setup.js +146 -20
  23. package/dist/config/adapters/claude-code.d.ts +92 -14
  24. package/dist/config/adapters/claude-code.js +291 -101
  25. package/dist/config/adapters/generic.d.ts +4 -1
  26. package/dist/config/adapters/generic.js +13 -14
  27. package/dist/config/generate.d.ts +392 -14
  28. package/dist/config/generate.js +666 -19
  29. package/dist/config/mcp-approval.d.ts +76 -0
  30. package/dist/config/mcp-approval.js +210 -0
  31. package/dist/config/node-path.d.ts +42 -0
  32. package/dist/config/node-path.js +102 -0
  33. package/dist/config/overrides.d.ts +62 -0
  34. package/dist/config/overrides.js +136 -0
  35. package/dist/config/resolve.d.ts +70 -8
  36. package/dist/config/resolve.js +186 -15
  37. package/dist/control/client.d.ts +22 -0
  38. package/dist/control/client.js +26 -7
  39. package/dist/control/correlation.d.ts +23 -5
  40. package/dist/control/correlation.js +0 -0
  41. package/dist/control/daemon.d.ts +400 -18
  42. package/dist/control/daemon.js +1105 -102
  43. package/dist/control/discovery.d.ts +77 -5
  44. package/dist/control/discovery.js +186 -11
  45. package/dist/control/ensure-hook.d.ts +61 -0
  46. package/dist/control/ensure-hook.js +160 -8
  47. package/dist/control/paths.d.ts +26 -0
  48. package/dist/control/paths.js +34 -0
  49. package/dist/control/pin-guard.d.ts +83 -0
  50. package/dist/control/pin-guard.js +534 -0
  51. package/dist/control/recorder-keeper.d.ts +229 -0
  52. package/dist/control/recorder-keeper.js +721 -0
  53. package/dist/control/recorder-main.d.ts +73 -0
  54. package/dist/control/recorder-main.js +778 -0
  55. package/dist/control/server.d.ts +707 -31
  56. package/dist/control/server.js +2803 -306
  57. package/dist/control/transcript.d.ts +21 -1
  58. package/dist/control/transcript.js +378 -112
  59. package/dist/index.d.ts +5 -1
  60. package/dist/index.js +5 -1
  61. package/dist/jsonrpc/framing.d.ts +76 -12
  62. package/dist/jsonrpc/framing.js +216 -59
  63. package/dist/proxy/intercept.d.ts +49 -5
  64. package/dist/proxy/intercept.js +188 -59
  65. package/dist/proxy/relay.d.ts +16 -0
  66. package/dist/proxy/relay.js +77 -5
  67. package/dist/proxy/revive.d.ts +122 -0
  68. package/dist/proxy/revive.js +251 -0
  69. package/dist/proxy/session.d.ts +222 -14
  70. package/dist/proxy/session.js +708 -99
  71. package/dist/record/housekeeping.d.ts +71 -0
  72. package/dist/record/housekeeping.js +417 -0
  73. package/dist/record/recorder.d.ts +51 -3
  74. package/dist/record/recorder.js +3 -0
  75. package/dist/record/redact.d.ts +103 -9
  76. package/dist/record/redact.js +659 -30
  77. package/dist/record/remote-recorder.d.ts +327 -32
  78. package/dist/record/remote-recorder.js +1517 -187
  79. package/dist/record/switchable-recorder.d.ts +126 -0
  80. package/dist/record/switchable-recorder.js +351 -0
  81. package/dist/record/truncate.d.ts +40 -1
  82. package/dist/record/truncate.js +111 -4
  83. package/dist/replay/bash-channel.d.ts +35 -0
  84. package/dist/replay/bash-channel.js +133 -0
  85. package/dist/replay/bundle.d.ts +10 -0
  86. package/dist/replay/bundle.js +10 -0
  87. package/dist/replay/controller.d.ts +371 -20
  88. package/dist/replay/controller.js +1185 -111
  89. package/dist/replay/derive.d.ts +1 -0
  90. package/dist/replay/derive.js +81 -24
  91. package/dist/replay/executor.d.ts +51 -16
  92. package/dist/replay/executor.js +114 -41
  93. package/dist/replay/handover.d.ts +54 -1
  94. package/dist/replay/handover.js +55 -5
  95. package/dist/replay/logic.d.ts +62 -10
  96. package/dist/replay/logic.js +140 -22
  97. package/dist/replay/plan.d.ts +201 -27
  98. package/dist/replay/plan.js +581 -141
  99. package/dist/replay/safety.d.ts +72 -0
  100. package/dist/replay/safety.js +303 -0
  101. package/dist/replay/source-run.d.ts +10 -0
  102. package/dist/replay/source-run.js +80 -35
  103. package/dist/replay/targets.d.ts +15 -1
  104. package/dist/replay/targets.js +62 -4
  105. package/dist/replay/tool-error.d.ts +15 -1
  106. package/dist/replay/tool-error.js +33 -1
  107. package/dist/replay/types.d.ts +7 -4
  108. package/dist/upstream/client.d.ts +56 -3
  109. package/dist/upstream/client.js +61 -4
  110. package/dist/upstream/headers.d.ts +43 -0
  111. package/dist/upstream/headers.js +90 -0
  112. package/dist/upstream/http-client.d.ts +213 -22
  113. package/dist/upstream/http-client.js +1034 -168
  114. package/dist/upstream/http-transport.d.ts +143 -0
  115. package/dist/upstream/http-transport.js +391 -0
  116. package/dist/upstream/lazy-client.d.ts +8 -0
  117. package/dist/upstream/lazy-client.js +29 -1
  118. package/dist/upstream/sse.d.ts +78 -0
  119. package/dist/upstream/sse.js +189 -0
  120. package/dist/upstream/stdio-client.d.ts +61 -1
  121. package/dist/upstream/stdio-client.js +356 -33
  122. package/dist/util/atomic-write.d.ts +29 -0
  123. package/dist/util/atomic-write.js +96 -0
  124. package/dist/util/journal.d.ts +15 -0
  125. package/dist/util/journal.js +48 -3
  126. package/dist/util/log.d.ts +10 -0
  127. package/dist/util/log.js +28 -1
  128. package/dist/util/off-switch.d.ts +46 -0
  129. package/dist/util/off-switch.js +91 -0
  130. package/dist/util/service-health.d.ts +133 -0
  131. package/dist/util/service-health.js +217 -0
  132. package/docs/BaseInstRunner.md +694 -621
  133. package/docs/calculatedReplay.md +1625 -1244
  134. package/docs/calculatedReplayGuide.md +1212 -879
  135. package/docs/installRun.md +1013 -772
  136. package/docs/loginWeb.md +627 -607
  137. package/docs/mcpmark.md +49 -16
  138. package/docs/my-first-sample.md +569 -545
  139. package/docs/quickstart.md +384 -271
  140. package/docs/t-bench.md +406 -394
  141. package/package.json +58 -57
  142. package/production.md +1371 -0
  143. package/scripts/install.ps1 +97 -6
  144. package/scripts/install.sh +104 -7
@@ -17,6 +17,7 @@
17
17
  * declined and what mode armed; the service knows the baseline, the ledger and
18
18
  * whether a run ever became a recording. Both lists are printed, worst first.
19
19
  */
20
+ import { ReadClock, jsonText, readWithin, serviceProblem, underMcp, unreachable } from "./service-read.js";
20
21
  const num = (v) => {
21
22
  if (v === undefined || v === null || v === "")
22
23
  return undefined;
@@ -170,8 +171,8 @@ export const DECLINES = {
170
171
  fix: "wait for the calculation, or start one with `bir scenario calc <runId>`, then run the prompt again.",
171
172
  },
172
173
  similarity: {
173
- cause: "the prompt was a hit on the recording but below the steering threshold (BIR_REPLAY_MIN_SIMILARITY, default 0.92).",
174
- fix: "phrase the prompt closer to the recorded one, or lower BIR_REPLAY_MIN_SIMILARITY.",
174
+ cause: "the prompt was a hit on the recording but below the steering threshold (BIR_MIN_STEER_SIMILARITY, default 0.92).",
175
+ fix: "phrase the prompt closer to the recorded one, or lower BIR_MIN_STEER_SIMILARITY.",
175
176
  },
176
177
  coverage: {
177
178
  cause: "no step of the scenario could run anywhere: its tools are neither wrapped MCP servers registered in this session nor built-ins.",
@@ -179,7 +180,7 @@ export const DECLINES = {
179
180
  },
180
181
  known_bad_first_step: {
181
182
  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 <runId> --force`.",
183
+ 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
184
  },
184
185
  nondeterministic_first_step: {
185
186
  cause: "the chain's first step needs a judgement the calculation could not write code for, so the plan cannot start.",
@@ -435,16 +436,22 @@ export function renderService(out, s, viewerIsOwner) {
435
436
  renderFindings(out, s.findings ?? [], "Service findings");
436
437
  }
437
438
  // ── the command ───────────────────────────────────────────────────────────────
438
- async function fetchInvestigation(deps, id, limit) {
439
- const reply = await deps.service(`/investigate/${id}${limit ? `?limit=${limit}` : ""}`);
440
- if ("error" in reply)
441
- return { note: `service not consulted: ${reply.error}` };
439
+ async function fetchInvestigation(deps, id, limit, clock) {
440
+ const reply = await clock.within(deps.service(`/investigate/${id}${limit ? `?limit=${limit}` : ""}`));
441
+ if ("error" in reply) {
442
+ return { note: `service not consulted: ${serviceProblem(reply.error)}`, unreachable: unreachable(reply.error) };
443
+ }
442
444
  if (reply.status === 404)
443
445
  return { note: `the service has nothing for ${id} (not yours, or never reached it)` };
444
446
  if (reply.status !== 200)
445
447
  return { note: `service answered HTTP ${reply.status}` };
446
448
  return { data: reply.body };
447
449
  }
450
+ /** A turn without its raw journal lines, which only a person reading the whole record wants. */
451
+ function withoutEvents(turn) {
452
+ const { events, ...rest } = turn;
453
+ return { ...rest, eventCount: events.length };
454
+ }
448
455
  /** Which id the service should be asked about for a turn. */
449
456
  function serviceIdFor(turn) {
450
457
  return (turn.reported?.ticket ??
@@ -463,9 +470,10 @@ export async function investigateCommand(args, deps) {
463
470
  params.set("limit", String(args.limit));
464
471
  if (args.user)
465
472
  params.set("user", args.user);
466
- const reply = await deps.service(`/investigate/executions${params.size ? `?${params}` : ""}`);
473
+ const reply = await readWithin(deps.service(`/investigate/executions${params.size ? `?${params}` : ""}`));
467
474
  if ("error" in reply) {
468
- out(`Could not reach the service: ${reply.error}`);
475
+ const words = serviceProblem(reply.error);
476
+ out(`Could not reach the service: ${words}${words === reply.error ? "" : ` (${reply.error})`}`);
469
477
  return 1;
470
478
  }
471
479
  if (reply.status === 403) {
@@ -482,7 +490,7 @@ export async function investigateCommand(args, deps) {
482
490
  }
483
491
  const body = reply.body;
484
492
  if (args.json) {
485
- out(JSON.stringify(body, null, 2));
493
+ out(jsonText(body));
486
494
  return 0;
487
495
  }
488
496
  const rows = body.executions ?? [];
@@ -503,7 +511,7 @@ export async function investigateCommand(args, deps) {
503
511
  const limit = args.limit ?? 20;
504
512
  const recent = turns.slice(-limit).reverse();
505
513
  if (args.json) {
506
- out(JSON.stringify(recent, null, 2));
514
+ out(jsonText(underMcp() ? recent.map(withoutEvents) : recent));
507
515
  return 0;
508
516
  }
509
517
  if (recent.length === 0) {
@@ -550,14 +558,31 @@ export async function investigateCommand(args, deps) {
550
558
  }
551
559
  const findings = turn ? localFindings(turn) : [];
552
560
  const serviceId = turn ? serviceIdFor(turn) : id;
553
- const service = await fetchInvestigation(deps, serviceId, args.limit);
561
+ // Both reads below share one bound, so a slow "no" followed by a held
562
+ // second try still ends inside the time the `bir` MCP server allows a
563
+ // reading tool — with the journal half, which needed no service at all.
564
+ const clock = new ReadClock();
565
+ const service = await fetchInvestigation(deps, serviceId, args.limit, clock);
554
566
  // A matched turn's own run id is unknown to the service; when the scenario
555
- // lookup fails, the matched run is still worth a try.
556
- const fallbackId = turn && service.note && turn.matched?.runId && serviceId !== turn.matched.runId ? turn.matched.runId : undefined;
557
- const second = fallbackId ? await fetchInvestigation(deps, fallbackId, args.limit) : undefined;
567
+ // lookup fails, the matched run is still worth a try — unless the service
568
+ // could not be reached at all, when a second try only doubles the wait, or
569
+ // the bound is spent.
570
+ const fallbackId = turn &&
571
+ service.note &&
572
+ !service.unreachable &&
573
+ turn.matched?.runId &&
574
+ serviceId !== turn.matched.runId &&
575
+ clock.remaining() > 0
576
+ ? turn.matched.runId
577
+ : undefined;
578
+ const second = fallbackId ? await fetchInvestigation(deps, fallbackId, args.limit, clock) : undefined;
558
579
  const chosen = service.data ? service : second?.data ? second : service;
559
580
  if (args.json) {
560
- out(JSON.stringify({ turn: turn ?? null, findings, service: chosen.data ?? null, note: chosen.note ?? null }, null, 2));
581
+ // For a model (the `bir` MCP server), the turn goes without its raw
582
+ // journal lines: every field they carry is already parsed into the turn,
583
+ // and a long turn's lines are most of the output.
584
+ const shown = turn ? (underMcp() ? withoutEvents(turn) : turn) : null;
585
+ out(jsonText({ turn: shown, findings, service: chosen.data ?? null, note: chosen.note ?? null }));
561
586
  return 0;
562
587
  }
563
588
  if (turn) {
@@ -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;
@@ -139,7 +143,13 @@ export interface ScenarioEditArgs {
139
143
  }
140
144
  /** The `bir scenario` words this module answers; `list` and `replay` stay in bir.ts. */
141
145
  export declare const SCENARIO_EDIT_SUBCOMMANDS: ReadonlySet<string>;
142
- /** The `bir` MCP server's tools that only read: always offered (D2). */
146
+ /**
147
+ * The `bir` MCP server's tools that only read. Offered with scenario editing
148
+ * on, or where the server's environment has `BIR_SCENARIO_TOOLS=read` — no
149
+ * longer in every session: their definitions ride along with every request a
150
+ * session makes, and only somebody working on a scenario ever calls them (the
151
+ * same answers are `bir scenario show` and `bir investigate` in a terminal).
152
+ */
143
153
  export declare const READ_TOOLS: readonly ["scenario_show", "scenario_edits", "investigate"];
144
154
  /** The ones that change a scenario: offered only after `bir scenario editing on` (D2). */
145
155
  export declare const EDIT_TOOLS: readonly ["scenario_check", "scenario_edit", "scenario_undo"];
@@ -155,19 +165,42 @@ export declare function decodeLogic(bytes: Buffer): string;
155
165
  * The report in the lines editSteps.md "What a person sees" shows:
156
166
  * `input`, `output` (when the output logic changed), `later`, `answer` (when
157
167
  * the final answer read anything before) and `mark`.
168
+ *
169
+ * `check` prints every line: it is where a person explores a change, and "step
170
+ * 5 still reproduces" is an answer there. `edit` and `undo` are acts, and print
171
+ * `brief`ly — what changed and what went wrong: `input` always, `output` when
172
+ * it changed, `later` only for a step that broke, `answer` only when it lost its
173
+ * values, `mark` only when it moves. The report is the same one either way (D11);
174
+ * only the lines that say "nothing to see" are left out.
175
+ *
176
+ * An `undo` does not judge the step itself — its code and mark go back exactly
177
+ * as they were (editSteps.md, "Undo") — so its report's input status is
178
+ * `not_checked` by design, and printing the usual reason for that ("the
179
+ * recorded call is plain text") would be false. It says what happened instead.
158
180
  */
159
- export declare function renderReport(out: (line?: string) => void, r: EditReport): void;
181
+ export declare function renderReport(out: (line?: string) => void, r: EditReport, opts?: {
182
+ brief?: boolean;
183
+ undo?: boolean;
184
+ }): void;
160
185
  /**
161
186
  * About {@link CONTEXT} characters either side of `at`. A text already that
162
187
  * short — a window the service cut itself, or a small call — is printed whole.
163
188
  */
164
189
  export declare function around(text: string, at: number): string;
165
190
  /**
166
- * Undo puts one step back as it was before its newest edit (D9). Without
167
- * `--edit`, that is the newest entry nothing has put back and no
168
- * recalculation replaced — which is by construction the newest of its step.
169
- * An undo is itself such an entry, so a second `undo` redoes.
191
+ * What `undo` without `--edit` takes back (D9): the newest **edit** — never an
192
+ * undo entry — that nothing has put back and no recalculation replaced; of
193
+ * step `step` when it is given.
194
+ *
195
+ * Undo entries are skipped on purpose. They used to be picked like any other,
196
+ * so a second `undo` redid the first one, and a step edited twice toggled
197
+ * between its two edits for ever and never got back to the calculation's code
198
+ * (C12). Now a second `undo` goes one further back; a redo is asked for by
199
+ * name, `--edit <the undo's id>`, which the undo prints.
200
+ *
201
+ * Whether the step is still exactly as that edit left it is the service's
202
+ * question, not this list's: it answers `not_latest_edit` when it is not.
170
203
  */
171
- export declare function newestUndoable(edits: EditEntry[]): EditEntry | undefined;
204
+ export declare function newestUndoable(edits: EditEntry[], step?: number): EditEntry | undefined;
172
205
  export {};
173
206
  //# sourceMappingURL=scenario-edit.d.ts.map