@basein/runner 0.3.20 → 0.3.22
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 +6 -5
- package/dist/bin/bir-hooks.d.ts +17 -19
- package/dist/bin/bir-hooks.js +17 -19
- package/dist/bin/bir-scenario.js +1 -1
- package/dist/bin/bir.js +1 -21
- package/dist/bin/investigate.d.ts +0 -9
- package/dist/bin/investigate.js +15 -43
- package/dist/bin/scenario-edit.js +13 -44
- package/dist/control/recorder-main.d.ts +0 -15
- package/dist/control/recorder-main.js +22 -42
- package/dist/control/server.d.ts +24 -137
- package/dist/control/server.js +107 -1001
- package/dist/record/recorder.d.ts +11 -142
- package/dist/record/recorder.js +0 -3
- package/dist/record/remote-recorder.d.ts +1 -9
- package/dist/record/remote-recorder.js +3 -49
- package/dist/record/switchable-recorder.js +0 -2
- package/dist/replay/builtins.d.ts +45 -9
- package/dist/replay/builtins.js +131 -59
- package/dist/replay/bundle.d.ts +2 -2
- package/dist/replay/bundle.js +3 -3
- package/dist/replay/controller.d.ts +34 -125
- package/dist/replay/controller.js +171 -528
- package/dist/replay/derive.d.ts +0 -10
- package/dist/replay/derive.js +0 -3
- package/dist/replay/handover.d.ts +2 -37
- package/dist/replay/handover.js +38 -106
- package/dist/replay/logic.d.ts +0 -11
- package/dist/replay/logic.js +0 -24
- package/dist/replay/plan.d.ts +43 -163
- package/dist/replay/plan.js +144 -556
- package/dist/replay/safety.d.ts +4 -5
- package/dist/replay/safety.js +7 -7
- package/dist/replay/source-run.d.ts +15 -24
- package/dist/replay/source-run.js +25 -58
- package/dist/replay/targets.d.ts +11 -21
- package/dist/replay/targets.js +30 -69
- package/dist/replay/types.d.ts +9 -96
- package/dist/replay/types.js +3 -16
- package/dist/util/service-headers.d.ts +1 -2
- package/dist/util/service-headers.js +1 -2
- package/docs/calculatedReplay.md +30 -81
- package/docs/calculatedReplayGuide.md +17 -34
- package/package.json +1 -1
- package/production.md +7 -36
- package/dist/replay/condition-words.d.ts +0 -28
- package/dist/replay/condition-words.js +0 -59
- package/dist/replay/flatten.d.ts +0 -161
- package/dist/replay/flatten.js +0 -222
- package/dist/replay/reads-only.d.ts +0 -29
- package/dist/replay/reads-only.js +0 -261
package/production.md
CHANGED
|
@@ -594,31 +594,6 @@ Truncation is always marked with the original size.
|
|
|
594
594
|
- An Esc-interrupted turn is never booked as a saving (`measured:false`).
|
|
595
595
|
- A step that a host other than Claude Code (§3.2) stopped itself has two stop reasons of its own. `host_rejected`: a person said no, or answered in the tool's place, or a call limit blocked the call. The note says "Step N of M (tool) was not run by the host. The result above was written by the host, not by the tool. Do not run the step again unless the user asks." `host_edited`: a person changed the call. The note says "Step N of M (tool) ran with arguments a person chose, not the scenario's. Its result above is real and its side effects are in place. Do not run it again." The report's `fallbackKind` stays `step_failed`.
|
|
596
596
|
|
|
597
|
-
**The decision stop (0.3.19).** The service may serve a step with `stop: {kind: "decision"}`: the task has an "if … otherwise" there (the service's segmentsbreakdown.md). The plan never decides:
|
|
598
|
-
|
|
599
|
-
- It runs only the steps before the decision, then hands the task back, in steer and direct mode and after a divergence.
|
|
600
|
-
- The note says the plan "ran the steps before a decision, and stopped there", says which steps ran, and quotes the request's own sentences with a condition word (`if`, `otherwise`, `unless` and the others the service uses, Hebrew too; at most 600 characters). It ends "Decide from the results above which way applies, then continue the task". It has no intent line, no remaining steps and no parameters, and never the condition the service keeps (`stop.why`): each would hint at the half the recording took. The directive at the prompt leaves the intent out too, and says the task comes back "at a decision".
|
|
601
|
-
- A plan whose chain has a decision keeps these protections when it stops earlier for another reason (a parked or failed step, a judgement, the budget). Its note has no intent line and no parameters, and lists the remaining steps only up to the decision. Its directive leaves the intent out. An agent sent on to the recorded half's steps would start that half's segment, and the plan would have decided after all.
|
|
602
|
-
- The report is `fell_back` (or `failed` when no step ran) with `fallbackKind: "decision"` and `fallbackStepIndex` the decision step. The decision step gets no verdict, so nothing is counted against it.
|
|
603
|
-
- When a step ran, the hand-over takes the plan's usage mark: the plan is billed up to the stop, and the agent's decision and the half it took are not on its bill. This holds for a plan the prompt started and for one an intent match started. Every other hand-over still takes no mark.
|
|
604
|
-
- The agent's own next call opens the fragment, marked `metadata.decisionOf = {scenarioId, stepIndex, request}` (`request`, the turn's prompt, redacted and cut to 4,000 characters, from 0.3.20): the service makes a calculated scenario of that way from it, calculated with the request (the service's offer1.md O2). When a plan the intent check armed, or a next scenario (below), takes that call instead, the fragment is dropped (`run.fragment_dropped`).
|
|
605
|
-
- `ranThrough` sends one entry per plan, with the plan's own position (the decision step itself for a decision). The service maps it onto the recording.
|
|
606
|
-
- `bir replay` stops in front of a decision and says so (`stopped before step N …: the plan's decision`).
|
|
607
|
-
- A runner up to 0.3.18 reads the unknown kind as `known_bad_step`: it stops there too, but its note and report name a failure.
|
|
608
|
-
|
|
609
|
-
**What can follow a decision (0.3.20, the service's offer1.md).** The first time a way is taken after a decision, the agent does it and its fragment becomes a calculated scenario of that decision. The next time:
|
|
610
|
-
|
|
611
|
-
- When a plan stops at its decision, the runner asks the service once, in the background, which calculated scenarios can follow it (`POST /scenarios/:planId/next`, body `{turnId, supportsCalls}`; at most 4, each handed out with its own ticket and `key: "decision"`). An older service, a 404 or any failure is "none". It arms each one while the agent reads the note and decides: its values are read from the turn's request and this turn's results (the reading carries `atDecision: {scenarioId, stepIndex}`), and settled. `replay.next_ready` says how many are ready.
|
|
612
|
-
- The agent's next calls are checked against them before the intent probe. A call that is a next scenario's first step, **value for value** (every argument the step's own; the pin guard's "plausibly the step" is not enough), is a hit: it is answered in its place with that scenario's directive (`replay.next_hit`), the agent calls `run_scenario`, and the scenario runs every step, that one included, once. It is booked on its own ticket. A scenario whose first step is inside a sub-task (a call row) can never be the agent's call in place: it is never compared.
|
|
613
|
-
- Each scenario is checkable as soon as the service answers; its arm runs on its own. A call waits only for the arm of a scenario that starts with the call's own tool (at most 6 s, `BIR_NEXT_HIT_WAIT_MS`), and not at all for the others. While the service has not answered yet, a call waits for that answer only, at most 1 s.
|
|
614
|
-
- When the background reading left a value of the first step empty, the call waits while it is read once more with the agent's call as `liveCall`; the service fills such a value from the call. A value read differently is never read again: an e-mail to carlos is never the recorded e-mail to olga.
|
|
615
|
-
- **A call that only reads keeps the set** (by the service's own rule, mirrored in `src/replay/reads-only.ts`: Claude Code's read tools, a name with a reading word and no writing word, a Bash command that only looks). The set ends unused (`replay.next_miss`) at the agent's first call that may change something and is no hit: `other_values` when the call has a scenario's first tool with other values, `own_way` when it starts none of them — the agent is doing a way of its own, and no scenario may start in the middle of it — and `not_ready` when the scenario that starts with that tool was not armed in time. It also ends after 5 calls that start none (`calls_limit`), or at the turn's end. A scenario not armed in time for a call that only reads misses that call only. Unused ones never run and are never reported; the agent's own work is recorded as the fragment, as before.
|
|
616
|
-
- A later decision in the same turn starts a new set: what could follow the earlier one is dropped, never checked again.
|
|
617
|
-
- A hit after the agent's own looks had opened the fragment ends that fragment there (`run.fragment_closed`, finished with what the agent did itself): the scenario's own calls, pinned in steer mode, are never recorded into it.
|
|
618
|
-
- `/health` shows the set per session: `next: {entries, armed, calls, ended}`.
|
|
619
|
-
|
|
620
|
-
**Never twice (0.3.20, offer1.md O8).** A plan an intent match arms after the agent's call — not in its place — whose steps call the same tool is not started: it would make that call a second time (issue fx2: the agent's e-mail, then the plan's own). `replay.decision code=would_repeat`. A tool that only reads, by the same rule as above (`crm_search_customers`, `Read`), may repeat. For Bash and PowerShell the call is its command: only a step that would run that very command counts.
|
|
621
|
-
|
|
622
597
|
**Measured cost.**
|
|
623
598
|
|
|
624
599
|
- A turn's cost is measured only when tokens were found and every model of the turn has a price in the runner's table. This holds for every host, Claude Code too. A model outside the table used to cost $0, and that zero was called measured.
|
|
@@ -735,7 +710,7 @@ Truncation is always marked with the original size.
|
|
|
735
710
|
| Remote servers without a literal credential header (OAuth, headersHelper, `${VAR}`) are left unwrapped. Earlier unasked wraps are undone. | The proxy has none of Claude Code's OAuth credentials; wrapped, these servers answered 401 to every call. | `--server <name>` (remembered). |
|
|
736
711
|
| A flag-less install from an installed package writes absolute-path entries and the self-starting SessionStart hook, for `bir wrap` too. | No npx registry round trip per server start (about 1 s), and it works offline. npx installs had nothing to start the recorder. | `--npx`. A checkout, or `bir` run through npx, still writes npx. |
|
|
737
712
|
| Hook timeouts: SessionStart command 15 s (was 30), HTTP 5 (10), UserPromptSubmit 10 (15), PreToolUse 15 (30), PostToolUse and PostToolUseFailure 5 (30), Subagent 3 (5), Stop 5 (10), SessionEnd 3 (30). | The timeout is the user's worst case during an incident. The server's deadlines sit inside it. | Not configurable; `bir setup` rewrites them. |
|
|
738
|
-
|
|
|
713
|
+
| Mid-turn intent arming is opt-in, and no intent probe is sent by default. | 0.2.12 held every PreToolUse up to 4 s for the probe. | `BIR_INTENT_ARM=1` with `BIR_INTENT_ARM_BUDGET_MS=4000`. |
|
|
739
714
|
| The prompt match budget is 3 s (was 2.5 s), and there is a match gate: over 2 s, prompts stop waiting for 5 min, doubling to 30 min. | A healthy service answers in 300-450 ms. A slower one would otherwise cost every prompt. A false trip costs one hold of replay, never latency. | `BIR_MATCH_BUDGET_MS=2500`. Raising `BIR_MATCH_SLOW_MS` above the budget effectively disables the gate. |
|
|
740
715
|
| Recorded outputs no longer stand in for a step that produced no result; the plan hands over at that step. | Last week's output presented as this turn's result is wrong data. | `BIR_REPLAY_RECORDED_OUTPUTS=1`, only for steps that could not be sent. `bir replay` keeps the stand-ins. |
|
|
741
716
|
| The direct plan budget is 100 s (was 120 s), and direct runs are clamped to 270 s. | Keeps a direct run under Claude Code's 2-minute auto-background of an MCP call, and inside `run_scenario`'s client timeout. | `BIR_REPLAY_PLAN_BUDGET_MS=120000` (or `BIR_REPLAY_BUDGET_MS`). |
|
|
@@ -870,7 +845,6 @@ Truncation is always marked with the original size.
|
|
|
870
845
|
- where recording goes, and as whom;
|
|
871
846
|
- when the service last answered;
|
|
872
847
|
- who approves wrapped MCP calls (the recorder's correlation mode);
|
|
873
|
-
- whether segments run mid-task (the default from 0.3.19) or are observed only, and whether whole plans may start mid-task too; a recorder older than this `bir` is named, since it keeps its old default until `bir up --restart`;
|
|
874
848
|
- superseded recorders;
|
|
875
849
|
- an npx-started scenario server;
|
|
876
850
|
- other installed projects.
|
|
@@ -919,9 +893,7 @@ In a folder that is set up for another host (§3.2), doctor prints `Host
|
|
|
919
893
|
| `registeredProxies[]`, `wrappedServers` | proxies that registered (`serverName`, `pid`, `cwd`, `version`) and the wrapped server names |
|
|
920
894
|
| `hooksVersion`, `pid`, `cwd`, `url` | the recorder's version and identity |
|
|
921
895
|
| `hostFields` | `2`: how much of another host's protocol (§3.2) this recorder understands. An adapter reads it before it relies on a field: with 1 it records, and from 2 it may also ask for a plan and step through it. A recorder that does not know another host answers no `hostFields`. |
|
|
922
|
-
| `
|
|
923
|
-
| `intentArm` | whether a step's intent may start a plan in its own PreToolUse, which then waits for the probe. It follows `segmentArm` unless `BIR_INTENT_ARM` is set. Absent from a recorder older than 0.3.19. |
|
|
924
|
-
| `sessionCount`, `sessions[]` | at most 50, active ones first: `{sessionId, runId, active, idleMs, ended, steps, recording, replay, intent, fragment}`. `replay` includes `armed`, `unannounced`, `running`, `outcome` and `handover` (`{kind, stepIndex}`; `kind` may be `decision`). `fragment` is `{state}`, with `decision: true` for one scheduled at a plan's decision. |
|
|
896
|
+
| `sessionCount`, `sessions[]` | at most 50, active ones first: `{sessionId, runId, active, idleMs, ended, steps, recording, replay, intent, fragment}`. `replay` includes `armed`, `unannounced`, `running`, `outcome` and `handover`. |
|
|
925
897
|
|
|
926
898
|
### 8.4 Logs and audit lines
|
|
927
899
|
|
|
@@ -943,7 +915,7 @@ Lines read `[bir] <ISO time> <event> key=value …`, with each field clipped at
|
|
|
943
915
|
| Hooks | `control.listening` (port, `recorder=<state>`, `correlation=`). `control.correlation_allow` (auto-approval opted in). `control.hook_deadline`, `control.hook_unauthorized` (a token from another install), `control.hook_unknown`, `control.hook_oversize`, `control.hook_event_mismatch`, `control.route_failed`, `control.drain_abandoned`. |
|
|
944
916
|
| Recorder lifecycle | `daemon.restart` (replaced an older recorder), `daemon.restart_deferred` (mid-run: next start), `daemon.start_alongside`, `daemon.newer_kept`, `daemon.revived`. `control.port_busy` (holder and fix), `control.already_running`, `control.superseded`, `control.discovery_reclaimed`, `control.log_reopened`, `control.fatal`. `overrides.stale` (a local copy no longer matches its source: run `bir setup`). |
|
|
945
917
|
| Proxy (MCP log) | `proxy.upstream_gone` (exiting with the server), `proxy.recorder_revive`, `tier.downgrade`, `tier.rebound`, `relay.undeliverable`. `upstream.auth_required` (the remote refused the handshake: credentials), `upstream.needs_sign_in`, `upstream.headers_invalid`, `upstream.proxy_env_ignored` (this Node cannot use HTTPS_PROXY), `upstream.proxy_env_invalid`, `upstream.standing_stream_failed` (at most hourly). Verbose only: `proxy.started`, `tier.bound`, `tier.passthrough`, `run.start` / `run.finish`, `upstream.stream_*`. |
|
|
946
|
-
| Replay | `replay.enabled` (at start: servers, threshold, who derives). `plan.armed`. `replay.decision` (also `arm_timeout`, `arm_unannounced`, `no_directive`, `no_delivery_tool`, `missing_target`, `host_records_only`, `host_tool_missing`). `replay.pin`, `replay.pin_declined`, `replay.pin_abandoned`, `replay.pin_overran`, `replay.diverge`, `replay.diverge_overran`, `replay.handover`, `replay.vetoed`, `replay.unsafe_param`, `replay.derive_timeout`, `replay.budget_clamped`, `replay.concurrent_call`, `replay.no_session_proxy`, `replay.run_ambiguous`, `replay.run_abandoned`.
|
|
918
|
+
| Replay | `replay.enabled` (at start: servers, threshold, who derives). `plan.armed`. `replay.decision` (also `arm_timeout`, `arm_unannounced`, `no_directive`, `no_delivery_tool`, `missing_target`, `host_records_only`, `host_tool_missing`). `replay.pin`, `replay.pin_declined`, `replay.pin_abandoned`, `replay.pin_overran`, `replay.diverge`, `replay.diverge_overran`, `replay.handover`, `replay.vetoed`, `replay.unsafe_param`, `replay.derive_timeout`, `replay.budget_clamped`, `replay.concurrent_call`, `replay.no_session_proxy`, `replay.run_ambiguous`, `replay.run_abandoned`. |
|
|
947
919
|
| Run cost | `run.unmeasured` (the turn's cost is not measured: `why` gives the reason, and `models` the models that have no price in the runner's table). |
|
|
948
920
|
| A host other than Claude Code (§3.2) | `run.start … host=langchain` (`bir investigate` reads the host from it, and raises no `steer_mode` finding for such a turn: steer is the full mode there), `plan.armed … mode=steer host=langchain`. `run.finish … ended=<reason>` (the run did not end with an ordinary stop). `run.unmeasured` (`why="the host sent no usage"`, a model with no price, or a run that made no model call). `run.incomplete` (the host says the recording is not the whole run: its finish carries the `incomplete` mark). `run.host_failed` (the host says the run failed: its recording is closed with that error). The service's new build never picks a run with an `error` or that mark for automatic calculation, and such a run no longer catches its prompt, unless its owner preferred it or it already has a plan (built, not deployed yet). `replay.decision verdict=no-steer code=host_records_only` (the adapter asked for no plan with its prompt) and `code=host_tool_missing` (the plan names a tool that the agent which asked does not have). `replay.own_call` (the model made a call of its own while the plan was live: the plan is handed over). `replay.step_host_stopped` (`stop=rejected` or `stop=edited`). `session.host_refused` (a scope that is empty, longer than 64 characters or not a text: nothing of that session is recorded or replayed). |
|
|
949
921
|
| `bir-scenario` (MCP log) | `scenario.answered` (`ok` means results were delivered; also `partial`, `bound`), `scenario.recorder_revive`, `scenario.switched`, `scenario.hello_failed`, `scenario.tool_timeout`, `scenario.tool_truncated`. |
|
|
@@ -1071,17 +1043,17 @@ Defaults were checked in `src/`.
|
|
|
1071
1043
|
| `BIR_STEP_TIMEOUT_MS` | recorder | 60000 | One direct `tools/call`. |
|
|
1072
1044
|
| `BIR_LOGIC_TIMEOUT_MS` | recorder | 1000 (capped at 10000) | Synchronous time limit for one scenario logic body. |
|
|
1073
1045
|
| `BIR_REPLAY_RECORDED_OUTPUTS` | recorder | unset | `1`: a step that could not be sent may use its recorded output, labelled as such. |
|
|
1074
|
-
| `BIR_BUILTIN_TOOLS` | recorder | `Read,Glob,Grep,Edit,Write,Bash` | Built-ins the runner executes itself during a replay,
|
|
1046
|
+
| `BIR_BUILTIN_TOOLS` | recorder | `Read,Glob,Grep,Edit,Write,Bash` | Built-ins the runner executes itself during a replay, from the session's project directory; which folders the file tools may touch is `BIR_PATH_ALLOW` (up to 0.3.7 the default was `Read,Glob,Grep`). Runner-side steps bypass the host's permission prompts; leave a tool off to have the model call it. `none` turns it off. Not in a session of another host (§3.2): the runner runs no step itself there, whatever a tool is named. |
|
|
1075
1047
|
| `BIR_BASH_ALLOW` | recorder | unset (any command) | Command prefixes a runner-side `Bash` step may run (`*` for all). Unset means any command; `none` means no command, and `Bash` steps are steered. A plan's Bash steps share a working directory, as the host's do; variables do not persist, also as the host's. |
|
|
1048
|
+
| `BIR_PATH_ALLOW` | recorder | unset (any folder) | Folders a runner-side `Read`, `Glob`, `Grep`, `Edit` or `Write` may touch (`*` for all). Unset means any path, as the agent's own tools (from 0.3.21; up to 0.3.20 the project directory alone). `project` is the session's project directory, `~` the home folder: `project,~/.claude`. Under a list, a path outside it, symlinks included, is `not_run` (hand-over). `none` means no folder, and file steps are steered. |
|
|
1076
1049
|
| `BIR_DERIVE_RECENT_RESULTS` | recorder | 5 | How many of this prompt's tool results derivation may read (`0`: none). Never for plans with a command sink, and never for a plan started at the prompt: a prompt just submitted has no results yet, and an earlier prompt's are not its own. So in a session of another host, where a plan starts at the prompt only, none are read. |
|
|
1077
1050
|
| `BIR_INTENT_MATCH` | recorder | on | `0` turns mid-turn intent probes off. |
|
|
1078
|
-
| `BIR_INTENT_MATCH_BUDGET_MS` | recorder |
|
|
1051
|
+
| `BIR_INTENT_MATCH_BUDGET_MS` | recorder | 1500 | One probe's own ceiling. A probe is sent only with `BIR_INTENT_ARM=1`. |
|
|
1079
1052
|
| `BIR_INTENT_MATCHES_PER_TURN` | recorder | 3 | Plans intent may arm per turn. |
|
|
1080
1053
|
| `BIR_INTENT_REQUESTS_PER_TURN` | recorder | 40 | Probes per turn. |
|
|
1081
1054
|
| `BIR_INTENT_SOURCES` | recorder | `text,thinking,tool` | Which text an intent may be read from. |
|
|
1082
|
-
| `BIR_INTENT_ARM` | recorder | unset (
|
|
1055
|
+
| `BIR_INTENT_ARM` | recorder | unset (off) | `1`: a call the agent explained sends an intent probe, and a whole plan it matches may arm in that call's own PreToolUse, which waits for it. Unset or `0`: off — no probe is sent and nothing arms mid-turn, as up to 0.3.18. Never in a session of another host (§3.2). |
|
|
1083
1056
|
| `BIR_INTENT_ARM_BUDGET_MS` | recorder | 500 | How long that PreToolUse waits for the probe. |
|
|
1084
|
-
| `BIR_SEGMENT_ARM` | recorder | on (from 0.3.19; unset or `1`) | A segment the service hands out runs mid-task: one found by repeated work, or a half after a plan's decision. `0`: observe only, where what would have armed is logged (`replay.segment_would_arm`) and nothing is replaced. Up to 0.3.18 the default was observe only. Not in a session of another host. |
|
|
1085
1057
|
| `BIR_CONTROL_URL` | proxy, scenario | written into wrapped entries | The project's control server. Together with `BIR_CONTROL_TOKEN` it is the SDK channel and wins over discovery. |
|
|
1086
1058
|
| `BIR_CONTROL_TOKEN` | proxy, scenario | unset | Bearer token for the SDK channel. |
|
|
1087
1059
|
| `BIR_PROXY_STAY_UP` | proxy | unset | `1`: keep answering errors after the upstream dies (0.2.12). |
|
|
@@ -1139,7 +1111,6 @@ Defaults were checked in `src/`.
|
|
|
1139
1111
|
- `langChainGuide.md`, which is in the repository's root and not in the npm package: its "Where it stands" table and its part 5 describe the time before the release.
|
|
1140
1112
|
|
|
1141
1113
|
The install scripts cannot set a folder up for such a host, so there is no one-line install for it.
|
|
1142
|
-
8. **A plan's decision (0.3.19): the service first.** Deploy the service build that accepts `fallbackKind: "decision"` before 0.3.19 is published (the service's segmentsbreakdown.md section 8, step 1). An older service answers such a report 400, and the run's cost and saving are lost. Then test 0.3.18 against a plan with a decision (it stops there, reported as `known_bad_step`). The deployed service makes no split while its `SCENARIO_SPLIT` is false, so run this check on a sandbox or local service started with `SCENARIO_SPLIT=true`, with bir 0.3.18 pointed at it: set `BIR_AUTH_URL` to its address, run `bir login` (with an account on that service), then `bir up --restart` in the folder you test in. bir keeps one session, for the service that issued it: without that sign-in nothing is recorded on the local service, and while bir is signed in there, nothing on this PC is recorded on the deployed one. The service's segmentsbreakdown.md section 8, step 2, has the steps. Then point bir back: set `BIR_AUTH_URL` to the deployed service's address (removing it is not enough, since the local sign-in stored the local address), run `bir login` again, then `bir up --restart` in that folder. Then publish 0.3.19, and only then turn the deployed service's `SCENARIO_SPLIT` on.
|
|
1143
1114
|
|
|
1144
1115
|
**Manual end-to-end checks with the real Claude Code.** These are behaviours of Claude Code itself, which the automated tests simulate but cannot prove. Use the Claude Code version you will support; the approval rules and hook schema were read from 2.1.280.
|
|
1145
1116
|
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* condition-words — the request's own words for its decision (the service's
|
|
3
|
-
* segmentsbreakdown.md SB-18).
|
|
4
|
-
*
|
|
5
|
-
* When a plan stops at its decision, the hand-over note quotes the sentences
|
|
6
|
-
* of the live request that hold a word for a condition ("if … otherwise"), so
|
|
7
|
-
* the agent sees which choice is its own to make. Only the request's words:
|
|
8
|
-
* never the condition the service keeps, which is written from the recorded
|
|
9
|
-
* run and would hint at the half the recording took.
|
|
10
|
-
*
|
|
11
|
-
* The word lists are the service's (its src/scenarios/condition.ts), copied
|
|
12
|
-
* exactly. Keep the two the same: the service decides which requests may hold
|
|
13
|
-
* a condition with them, and this quotes the sentences they find.
|
|
14
|
-
*/
|
|
15
|
-
/** The longest quote a decision note carries, in characters. */
|
|
16
|
-
export declare const MAX_DECISION_QUOTE = 600;
|
|
17
|
-
/** Whether a text has a word for a condition in it: the service's `mayHoldCondition`. */
|
|
18
|
-
export declare function mayHoldCondition(text: string): boolean;
|
|
19
|
-
/**
|
|
20
|
-
* The sentences of `request` that hold a word for a condition, in order,
|
|
21
|
-
* each on one line of single spaces, joined by a space. Cut to at most `max`
|
|
22
|
-
* characters, ending in "…" when cut. Null when no sentence holds one.
|
|
23
|
-
*
|
|
24
|
-
* Lines are sentences of their own: the request is split at line breaks
|
|
25
|
-
* first, then at {@link SENTENCE_BREAK}.
|
|
26
|
-
*/
|
|
27
|
-
export declare function conditionSentences(request: string | null | undefined, max?: number): string | null;
|
|
28
|
-
//# sourceMappingURL=condition-words.d.ts.map
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* condition-words — the request's own words for its decision (the service's
|
|
3
|
-
* segmentsbreakdown.md SB-18).
|
|
4
|
-
*
|
|
5
|
-
* When a plan stops at its decision, the hand-over note quotes the sentences
|
|
6
|
-
* of the live request that hold a word for a condition ("if … otherwise"), so
|
|
7
|
-
* the agent sees which choice is its own to make. Only the request's words:
|
|
8
|
-
* never the condition the service keeps, which is written from the recorded
|
|
9
|
-
* run and would hint at the half the recording took.
|
|
10
|
-
*
|
|
11
|
-
* The word lists are the service's (its src/scenarios/condition.ts), copied
|
|
12
|
-
* exactly. Keep the two the same: the service decides which requests may hold
|
|
13
|
-
* a condition with them, and this quotes the sentences they find.
|
|
14
|
-
*/
|
|
15
|
-
/** Words that say a part of a task depends on something: English, and the Hebrew the service reads. */
|
|
16
|
-
const CONDITION_WORDS = /\b(if|otherwise|else|unless|only when|only if|in case|whether)\b/i;
|
|
17
|
-
const HEBREW_CONDITION = /(^|[\s,.;:!?"'()\-])(אם|אחרת|אלא אם|במקרה ש|רק אם|רק כאשר)($|[\s,.;:!?"'()\-])/;
|
|
18
|
-
/** The longest quote a decision note carries, in characters. */
|
|
19
|
-
export const MAX_DECISION_QUOTE = 600;
|
|
20
|
-
/** Whether a text has a word for a condition in it: the service's `mayHoldCondition`. */
|
|
21
|
-
export function mayHoldCondition(text) {
|
|
22
|
-
return CONDITION_WORDS.test(text) || HEBREW_CONDITION.test(text);
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Where one sentence ends and the next begins: after `.`, `!`, `?` or `…` and
|
|
26
|
-
* white space, in front of a capital letter, a letter of a script with no
|
|
27
|
-
* capitals (Hebrew), a quote mark or a bracket. Never in front of a digit or a
|
|
28
|
-
* small letter, so "Nov. 20" and "e.g. the" stay in one sentence.
|
|
29
|
-
*/
|
|
30
|
-
const SENTENCE_BREAK = /(?<=[.!?…])\s+(?=[\p{Lu}\p{Lo}"'“‘(\[])/u;
|
|
31
|
-
/**
|
|
32
|
-
* The sentences of `request` that hold a word for a condition, in order,
|
|
33
|
-
* each on one line of single spaces, joined by a space. Cut to at most `max`
|
|
34
|
-
* characters, ending in "…" when cut. Null when no sentence holds one.
|
|
35
|
-
*
|
|
36
|
-
* Lines are sentences of their own: the request is split at line breaks
|
|
37
|
-
* first, then at {@link SENTENCE_BREAK}.
|
|
38
|
-
*/
|
|
39
|
-
export function conditionSentences(request, max = MAX_DECISION_QUOTE) {
|
|
40
|
-
if (typeof request !== "string" || request.trim() === "")
|
|
41
|
-
return null;
|
|
42
|
-
const sentences = request
|
|
43
|
-
.replace(/\r\n?/g, "\n")
|
|
44
|
-
.split("\n")
|
|
45
|
-
.flatMap((line) => line.split(SENTENCE_BREAK))
|
|
46
|
-
.map((s) => s.replace(/\s+/g, " ").trim())
|
|
47
|
-
.filter((s) => s !== "" && mayHoldCondition(s));
|
|
48
|
-
if (sentences.length === 0)
|
|
49
|
-
return null;
|
|
50
|
-
const quote = sentences.join(" ");
|
|
51
|
-
if (quote.length <= max)
|
|
52
|
-
return quote;
|
|
53
|
-
let cut = quote.slice(0, Math.max(0, max - 1));
|
|
54
|
-
// Never half of a character written as two code units (an emoji).
|
|
55
|
-
if (/[\uD800-\uDBFF]$/.test(cut))
|
|
56
|
-
cut = cut.slice(0, -1);
|
|
57
|
-
return `${cut.trimEnd()}…`;
|
|
58
|
-
}
|
|
59
|
-
//# sourceMappingURL=condition-words.js.map
|
package/dist/replay/flatten.d.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Flattening calls into frames (segmented.md 10.6).
|
|
3
|
-
*
|
|
4
|
-
* A chain may hold a **call**: one row that says "here the agent does this
|
|
5
|
-
* sub-task", answered by a recorded scenario the service inlines into the
|
|
6
|
-
* payload. Running it is not a second plan. It is one plan over one flat list
|
|
7
|
-
* of steps, in which some steps belong to another scenario and run with *its*
|
|
8
|
-
* parameters, *its* intent and *its* accumulated values.
|
|
9
|
-
*
|
|
10
|
-
* That is what a **frame** is: the scope one stretch of steps runs in. Frame 0
|
|
11
|
-
* is the caller's own. Entering a called frame evaluates the call's
|
|
12
|
-
* `paramMapLogic` to build the segment's parameters; leaving it evaluates
|
|
13
|
-
* `resultMapLogic` and merges the result back into the caller's `respParams`
|
|
14
|
-
* (R-CALL-29).
|
|
15
|
-
*
|
|
16
|
-
* Everything downstream reads the flat list rather than the chain: the gates
|
|
17
|
-
* judge the steps that will really run (R-CALL-30), recorded outputs come from
|
|
18
|
-
* the right recording and range (R-CALL-31), and a failure is reported on the
|
|
19
|
-
* scenario that owns the step (R-CALL-13).
|
|
20
|
-
*
|
|
21
|
-
* The flattener itself decides nothing about *which* segment answers a call —
|
|
22
|
-
* the service did that long before this payload was built. It only lays out
|
|
23
|
-
* what it was served, and stops in front of anything it cannot run.
|
|
24
|
-
*/
|
|
25
|
-
import type { SerializedScenario, SerializedScenarioStep, CallUnusable, StepTest } from "./types.js";
|
|
26
|
-
import type { ParamsSchema } from "./types.js";
|
|
27
|
-
/** The scope one stretch of steps runs in. Frame 0 is the caller's own. */
|
|
28
|
-
export interface Frame {
|
|
29
|
-
id: number;
|
|
30
|
-
/** 0 for the caller, 1 for a segment it calls, 2 for one that segment calls. */
|
|
31
|
-
depth: number;
|
|
32
|
-
scenarioId: string;
|
|
33
|
-
runId: string;
|
|
34
|
-
/** The recorded range the frame's outputs come from. Null means the whole run. */
|
|
35
|
-
stepFrom: number | null;
|
|
36
|
-
stepTo: number | null;
|
|
37
|
-
/** The intent the frame's step logic runs with (R-INTENT-12). */
|
|
38
|
-
intent: string;
|
|
39
|
-
parent?: Frame;
|
|
40
|
-
/** The caller's chain `stepIndex` of the call row that opened this frame. */
|
|
41
|
-
callerStepIndex?: number;
|
|
42
|
-
callIntent?: string;
|
|
43
|
-
paramMapLogic?: string;
|
|
44
|
-
resultMapLogic?: string;
|
|
45
|
-
segmentId?: string;
|
|
46
|
-
/** The inlined segment's `chainRevision` as served (R-CALL-13). */
|
|
47
|
-
chainRevision?: number;
|
|
48
|
-
/** The segment's own parameters, for the settings a mapping omits (R-CALL-21). */
|
|
49
|
-
paramsObject?: ParamsSchema | null;
|
|
50
|
-
/**
|
|
51
|
-
* The call row that opened this frame, when it carries a test (the
|
|
52
|
-
* service's autocorrect.md AC5): the whole sub-task runs only when the test
|
|
53
|
-
* says so. It is evaluated in the *caller's* frame, before this one is
|
|
54
|
-
* entered.
|
|
55
|
-
*/
|
|
56
|
-
when?: StepTest;
|
|
57
|
-
/** That call row itself, for the verdict a sub-task left out gets. */
|
|
58
|
-
callStep?: SerializedScenarioStep;
|
|
59
|
-
/** Filled by the plan when the frame is entered. */
|
|
60
|
-
parameters?: Record<string, unknown>;
|
|
61
|
-
respParams: Record<string, unknown>;
|
|
62
|
-
}
|
|
63
|
-
/** One step that will really run, with the frame it runs in. */
|
|
64
|
-
export interface FlatEntry {
|
|
65
|
-
scenarioId: string;
|
|
66
|
-
runId: string;
|
|
67
|
-
stepFrom: number | null;
|
|
68
|
-
stepTo: number | null;
|
|
69
|
-
/** The owning scenario's own `scenario_steps.step_index`, not a flat position. */
|
|
70
|
-
stepIndex: number;
|
|
71
|
-
/** Always a tool row: a call row becomes a frame, or a stop. */
|
|
72
|
-
step: SerializedScenarioStep;
|
|
73
|
-
frame: Frame;
|
|
74
|
-
depth: number;
|
|
75
|
-
/**
|
|
76
|
-
* First / last of its frame's *own* entries. Where a frame is entered and
|
|
77
|
-
* left is {@link frameEdges}: a frame whose chain begins or ends with a
|
|
78
|
-
* call begins or ends with that call's steps.
|
|
79
|
-
*/
|
|
80
|
-
first: boolean;
|
|
81
|
-
last: boolean;
|
|
82
|
-
}
|
|
83
|
-
/** Why the flat list stops short, and which call row said so. */
|
|
84
|
-
export interface FlatStop {
|
|
85
|
-
kind: "unusable";
|
|
86
|
-
reason: CallUnusable;
|
|
87
|
-
/** The scenario whose chain holds the call row. */
|
|
88
|
-
scenarioId: string;
|
|
89
|
-
/** That call row's own `stepIndex`. */
|
|
90
|
-
stepIndex: number;
|
|
91
|
-
segmentId?: string | null;
|
|
92
|
-
callIntent?: string | null;
|
|
93
|
-
}
|
|
94
|
-
export interface FlatChain {
|
|
95
|
-
entries: FlatEntry[];
|
|
96
|
-
/** Flat position the plan stops in front of. Undefined means the whole chain. */
|
|
97
|
-
stopAt?: number;
|
|
98
|
-
stopReason?: FlatStop;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Mirrors the service's `SEGMENT_MAX_DEPTH`. A constant, not a setting: one
|
|
102
|
-
* call row holds one answer, which is only correct because a call inside a
|
|
103
|
-
* level-two segment is never answered (R-CALL-12).
|
|
104
|
-
*/
|
|
105
|
-
export declare const MAX_CALL_DEPTH = 2;
|
|
106
|
-
/**
|
|
107
|
-
* Lay a served chain out as one flat list of steps, with a frame per call.
|
|
108
|
-
*
|
|
109
|
-
* Stops at the first call it cannot run. The stop is *clamped* to the start of
|
|
110
|
-
* the outermost called frame that contains it: a plan must never run half a
|
|
111
|
-
* sub-task and leave its `resultMapLogic` unevaluated, because the caller's
|
|
112
|
-
* later steps read what that body returns (R-CALL-30).
|
|
113
|
-
*/
|
|
114
|
-
export declare function flattenChain(scenario: SerializedScenario, opts?: {
|
|
115
|
-
maxDepth?: number;
|
|
116
|
-
/** The range a segment armed on its own covers (its own recording). */
|
|
117
|
-
range?: {
|
|
118
|
-
stepFrom: number;
|
|
119
|
-
stepTo: number;
|
|
120
|
-
} | null;
|
|
121
|
-
}): FlatChain;
|
|
122
|
-
/**
|
|
123
|
-
* Move a stop back to the start of the outermost called frame it falls in
|
|
124
|
-
* (R-CALL-30, 10.6 step 3).
|
|
125
|
-
*
|
|
126
|
-
* Used for the known-bad-step gate, which judges entries: a parked step in the
|
|
127
|
-
* middle of a sub-task stops the plan in front of the *sub-task*, not in front
|
|
128
|
-
* of the step, because stopping inside it would skip the body that hands its
|
|
129
|
-
* results back. The sub-task starts at its first entry *or its callee's*: one
|
|
130
|
-
* whose chain begins with a call starts where that call's steps do.
|
|
131
|
-
*/
|
|
132
|
-
export declare function clampToFrameStart(entries: readonly FlatEntry[], index: number): number;
|
|
133
|
-
/**
|
|
134
|
-
* Where each called frame begins and ends in the flat list, its callees'
|
|
135
|
-
* entries included — not where its *own* entries do (`first` / `last`).
|
|
136
|
-
*
|
|
137
|
-
* The two differ when a sub-task's chain begins or ends with a call: for
|
|
138
|
-
* F = [call G, f1] the flat order is g0, g1, f1, and F begins at g0. Entering
|
|
139
|
-
* F there, before G, is what gives G's mapping (and G's test) F's parameters;
|
|
140
|
-
* deciding F's own test there is what keeps G from running when F is not
|
|
141
|
-
* needed (the service's autocorrect.md AC5). Leaving F after the last of
|
|
142
|
-
* them is what hands G's results on through F.
|
|
143
|
-
*/
|
|
144
|
-
export interface FrameEdges {
|
|
145
|
-
/** Per frame id, the flat position of its first entry, its callees' included. */
|
|
146
|
-
start: Map<number, number>;
|
|
147
|
-
/** Per frame id, the flat position of its last entry, its callees' included. */
|
|
148
|
-
end: Map<number, number>;
|
|
149
|
-
/** Per flat position, the called frames that begin there, outermost first. */
|
|
150
|
-
startsAt: Map<number, Frame[]>;
|
|
151
|
-
/** Per flat position, the called frames that end there, innermost first. */
|
|
152
|
-
endsAt: Map<number, Frame[]>;
|
|
153
|
-
}
|
|
154
|
-
export declare function frameEdges(entries: readonly FlatEntry[]): FrameEdges;
|
|
155
|
-
/**
|
|
156
|
-
* One frame per chain, for a payload with no calls in it and for the callers
|
|
157
|
-
* that build a plan by hand (tests, `bir replay`). Byte for byte the old
|
|
158
|
-
* behaviour: one frame, one scenario, one range.
|
|
159
|
-
*/
|
|
160
|
-
export declare function flatEntriesOf(scenario: SerializedScenario): FlatEntry[];
|
|
161
|
-
//# sourceMappingURL=flatten.d.ts.map
|
package/dist/replay/flatten.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Flattening calls into frames (segmented.md 10.6).
|
|
3
|
-
*
|
|
4
|
-
* A chain may hold a **call**: one row that says "here the agent does this
|
|
5
|
-
* sub-task", answered by a recorded scenario the service inlines into the
|
|
6
|
-
* payload. Running it is not a second plan. It is one plan over one flat list
|
|
7
|
-
* of steps, in which some steps belong to another scenario and run with *its*
|
|
8
|
-
* parameters, *its* intent and *its* accumulated values.
|
|
9
|
-
*
|
|
10
|
-
* That is what a **frame** is: the scope one stretch of steps runs in. Frame 0
|
|
11
|
-
* is the caller's own. Entering a called frame evaluates the call's
|
|
12
|
-
* `paramMapLogic` to build the segment's parameters; leaving it evaluates
|
|
13
|
-
* `resultMapLogic` and merges the result back into the caller's `respParams`
|
|
14
|
-
* (R-CALL-29).
|
|
15
|
-
*
|
|
16
|
-
* Everything downstream reads the flat list rather than the chain: the gates
|
|
17
|
-
* judge the steps that will really run (R-CALL-30), recorded outputs come from
|
|
18
|
-
* the right recording and range (R-CALL-31), and a failure is reported on the
|
|
19
|
-
* scenario that owns the step (R-CALL-13).
|
|
20
|
-
*
|
|
21
|
-
* The flattener itself decides nothing about *which* segment answers a call —
|
|
22
|
-
* the service did that long before this payload was built. It only lays out
|
|
23
|
-
* what it was served, and stops in front of anything it cannot run.
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* Mirrors the service's `SEGMENT_MAX_DEPTH`. A constant, not a setting: one
|
|
27
|
-
* call row holds one answer, which is only correct because a call inside a
|
|
28
|
-
* level-two segment is never answered (R-CALL-12).
|
|
29
|
-
*/
|
|
30
|
-
export const MAX_CALL_DEPTH = 2;
|
|
31
|
-
/**
|
|
32
|
-
* Lay a served chain out as one flat list of steps, with a frame per call.
|
|
33
|
-
*
|
|
34
|
-
* Stops at the first call it cannot run. The stop is *clamped* to the start of
|
|
35
|
-
* the outermost called frame that contains it: a plan must never run half a
|
|
36
|
-
* sub-task and leave its `resultMapLogic` unevaluated, because the caller's
|
|
37
|
-
* later steps read what that body returns (R-CALL-30).
|
|
38
|
-
*/
|
|
39
|
-
export function flattenChain(scenario, opts = {}) {
|
|
40
|
-
const maxDepth = opts.maxDepth ?? MAX_CALL_DEPTH;
|
|
41
|
-
const entries = [];
|
|
42
|
-
let frameId = 0;
|
|
43
|
-
let stopAt;
|
|
44
|
-
let stopReason;
|
|
45
|
-
const root = {
|
|
46
|
-
id: frameId++,
|
|
47
|
-
depth: 0,
|
|
48
|
-
scenarioId: scenario.id,
|
|
49
|
-
runId: scenario.runId,
|
|
50
|
-
stepFrom: opts.range?.stepFrom ?? null,
|
|
51
|
-
stepTo: opts.range?.stepTo ?? null,
|
|
52
|
-
intent: scenario.intent ?? "",
|
|
53
|
-
respParams: {},
|
|
54
|
-
};
|
|
55
|
-
/** The entry a frame's steps start at, for the clamp. */
|
|
56
|
-
const frameStart = new Map();
|
|
57
|
-
const walk = (steps, frame, stack) => {
|
|
58
|
-
for (const step of steps) {
|
|
59
|
-
if (stopAt !== undefined)
|
|
60
|
-
return;
|
|
61
|
-
if (step.kind !== "segment") {
|
|
62
|
-
entries.push({
|
|
63
|
-
scenarioId: frame.scenarioId,
|
|
64
|
-
runId: frame.runId,
|
|
65
|
-
stepFrom: frame.stepFrom,
|
|
66
|
-
stepTo: frame.stepTo,
|
|
67
|
-
stepIndex: step.stepIndex,
|
|
68
|
-
step,
|
|
69
|
-
frame,
|
|
70
|
-
depth: frame.depth,
|
|
71
|
-
first: false,
|
|
72
|
-
last: false,
|
|
73
|
-
});
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
// A call the service already marked unrunnable, a third level, or a
|
|
77
|
-
// segment already on this stack (which would be a cycle): all the same
|
|
78
|
-
// thing to a plan — it stops here and the agent takes over (R-CALL-29).
|
|
79
|
-
const cycles = !!step.segment && stack.includes(step.segment.id);
|
|
80
|
-
const tooDeep = frame.depth + 1 > maxDepth;
|
|
81
|
-
const reason = step.unusable
|
|
82
|
-
? step.unusable
|
|
83
|
-
: !step.segment || cycles || tooDeep
|
|
84
|
-
? "depth"
|
|
85
|
-
: undefined;
|
|
86
|
-
if (reason) {
|
|
87
|
-
// The clamp: a stop inside a called frame moves back to the first entry
|
|
88
|
-
// of the outermost called frame containing it, so the caller never runs
|
|
89
|
-
// part of a sub-task (R-CALL-30).
|
|
90
|
-
let outermost = frame;
|
|
91
|
-
while (outermost && outermost.depth > 1)
|
|
92
|
-
outermost = outermost.parent;
|
|
93
|
-
stopAt =
|
|
94
|
-
frame.depth > 0 && outermost
|
|
95
|
-
? (frameStart.get(outermost.id) ?? entries.length)
|
|
96
|
-
: entries.length;
|
|
97
|
-
stopReason = {
|
|
98
|
-
kind: "unusable",
|
|
99
|
-
reason,
|
|
100
|
-
scenarioId: frame.scenarioId,
|
|
101
|
-
stepIndex: step.stepIndex,
|
|
102
|
-
segmentId: step.segmentId ?? step.segment?.id ?? null,
|
|
103
|
-
callIntent: step.callIntent ?? null,
|
|
104
|
-
};
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const segment = step.segment;
|
|
108
|
-
const child = {
|
|
109
|
-
id: frameId++,
|
|
110
|
-
depth: frame.depth + 1,
|
|
111
|
-
scenarioId: segment.id,
|
|
112
|
-
runId: segment.runId,
|
|
113
|
-
stepFrom: segment.stepFrom,
|
|
114
|
-
stepTo: segment.stepTo,
|
|
115
|
-
intent: segment.intent ?? "",
|
|
116
|
-
parent: frame,
|
|
117
|
-
callerStepIndex: step.stepIndex,
|
|
118
|
-
callIntent: step.callIntent ?? undefined,
|
|
119
|
-
paramMapLogic: step.paramMapLogic ?? undefined,
|
|
120
|
-
resultMapLogic: step.resultMapLogic ?? undefined,
|
|
121
|
-
segmentId: segment.id,
|
|
122
|
-
chainRevision: segment.chainRevision,
|
|
123
|
-
paramsObject: segment.paramsObject ?? null,
|
|
124
|
-
// A sub-task may run only when a test says so (AC5): the plan decides
|
|
125
|
-
// the whole frame at its first entry, in the caller's scope.
|
|
126
|
-
...(step.when ? { when: step.when } : {}),
|
|
127
|
-
callStep: step,
|
|
128
|
-
respParams: {},
|
|
129
|
-
};
|
|
130
|
-
frameStart.set(child.id, entries.length);
|
|
131
|
-
walk(segment.steps ?? [], child, [...stack, segment.id]);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
frameStart.set(root.id, 0);
|
|
135
|
-
walk(scenario.steps ?? [], root, [scenario.id]);
|
|
136
|
-
// `first` / `last` per frame, marked once the whole list is known: they are
|
|
137
|
-
// where the two mapping bodies run. A frame's entries are not necessarily
|
|
138
|
-
// contiguous — a call sitting in the middle of a chain interleaves its
|
|
139
|
-
// callee's entries with its own — so these are the frame's first and last
|
|
140
|
-
// entries overall, never the edges of a run of them.
|
|
141
|
-
const firstOf = new Map();
|
|
142
|
-
const lastOf = new Map();
|
|
143
|
-
entries.forEach((entry, index) => {
|
|
144
|
-
if (!firstOf.has(entry.frame.id))
|
|
145
|
-
firstOf.set(entry.frame.id, index);
|
|
146
|
-
lastOf.set(entry.frame.id, index);
|
|
147
|
-
});
|
|
148
|
-
entries.forEach((entry, index) => {
|
|
149
|
-
entry.first = firstOf.get(entry.frame.id) === index;
|
|
150
|
-
entry.last = lastOf.get(entry.frame.id) === index;
|
|
151
|
-
});
|
|
152
|
-
return { entries, stopAt, stopReason };
|
|
153
|
-
}
|
|
154
|
-
/** Whether `frame` is `outer` or lies inside it. */
|
|
155
|
-
function within(frame, outer) {
|
|
156
|
-
for (let f = frame; f; f = f.parent)
|
|
157
|
-
if (f.id === outer.id)
|
|
158
|
-
return true;
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Move a stop back to the start of the outermost called frame it falls in
|
|
163
|
-
* (R-CALL-30, 10.6 step 3).
|
|
164
|
-
*
|
|
165
|
-
* Used for the known-bad-step gate, which judges entries: a parked step in the
|
|
166
|
-
* middle of a sub-task stops the plan in front of the *sub-task*, not in front
|
|
167
|
-
* of the step, because stopping inside it would skip the body that hands its
|
|
168
|
-
* results back. The sub-task starts at its first entry *or its callee's*: one
|
|
169
|
-
* whose chain begins with a call starts where that call's steps do.
|
|
170
|
-
*/
|
|
171
|
-
export function clampToFrameStart(entries, index) {
|
|
172
|
-
const at = entries[index];
|
|
173
|
-
if (!at || at.depth === 0)
|
|
174
|
-
return index;
|
|
175
|
-
let outermost = at.frame;
|
|
176
|
-
while (outermost && outermost.depth > 1)
|
|
177
|
-
outermost = outermost.parent;
|
|
178
|
-
if (!outermost)
|
|
179
|
-
return index;
|
|
180
|
-
for (let i = 0; i < entries.length; i += 1) {
|
|
181
|
-
if (within(entries[i].frame, outermost))
|
|
182
|
-
return i;
|
|
183
|
-
}
|
|
184
|
-
return index;
|
|
185
|
-
}
|
|
186
|
-
export function frameEdges(entries) {
|
|
187
|
-
const start = new Map();
|
|
188
|
-
const end = new Map();
|
|
189
|
-
const frames = new Map();
|
|
190
|
-
entries.forEach((entry, index) => {
|
|
191
|
-
for (let f = entry.frame; f; f = f.parent) {
|
|
192
|
-
frames.set(f.id, f);
|
|
193
|
-
if (!start.has(f.id))
|
|
194
|
-
start.set(f.id, index);
|
|
195
|
-
end.set(f.id, index);
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
const startsAt = new Map();
|
|
199
|
-
const endsAt = new Map();
|
|
200
|
-
for (const f of frames.values()) {
|
|
201
|
-
if (f.depth === 0)
|
|
202
|
-
continue;
|
|
203
|
-
const s = start.get(f.id);
|
|
204
|
-
const e = end.get(f.id);
|
|
205
|
-
startsAt.set(s, [...(startsAt.get(s) ?? []), f]);
|
|
206
|
-
endsAt.set(e, [...(endsAt.get(e) ?? []), f]);
|
|
207
|
-
}
|
|
208
|
-
for (const list of startsAt.values())
|
|
209
|
-
list.sort((a, b) => a.depth - b.depth);
|
|
210
|
-
for (const list of endsAt.values())
|
|
211
|
-
list.sort((a, b) => b.depth - a.depth);
|
|
212
|
-
return { start, end, startsAt, endsAt };
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* One frame per chain, for a payload with no calls in it and for the callers
|
|
216
|
-
* that build a plan by hand (tests, `bir replay`). Byte for byte the old
|
|
217
|
-
* behaviour: one frame, one scenario, one range.
|
|
218
|
-
*/
|
|
219
|
-
export function flatEntriesOf(scenario) {
|
|
220
|
-
return flattenChain(scenario).entries;
|
|
221
|
-
}
|
|
222
|
-
//# sourceMappingURL=flatten.js.map
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Whether a tool call may change something, judged by its name alone — the
|
|
3
|
-
* runner's copy of the service's own rule (BaseIn src/scenarios/correctionCheck.ts,
|
|
4
|
-
* `readsOnly`, `commandReadsOnly`, `callMayChange`), kept word for word so the
|
|
5
|
-
* two never disagree about the same call.
|
|
6
|
-
*
|
|
7
|
-
* Used where a call after a plan's decision is the agent starting a way of its
|
|
8
|
-
* own (the service's offer1.md O7), and where a plan started after a call
|
|
9
|
-
* would make it a second time (O8). When in doubt, a call is a change: that
|
|
10
|
-
* only ends a set of scenarios early or keeps a plan from starting, never lets
|
|
11
|
-
* a second write through.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* A tool that only reads: Claude Code's read-only tools, then the name — a
|
|
15
|
-
* reading word and no writing word (`calendar_search_events`, `getJiraIssue`;
|
|
16
|
-
* `run_sql` no: `run` writes). A name with both (`get_or_create_contact`)
|
|
17
|
-
* changes something.
|
|
18
|
-
*/
|
|
19
|
-
export declare function readsOnly(toolName: string): boolean;
|
|
20
|
-
/** Whether a shell command only looks: every part of it is a looking command, and nothing is written to a file. */
|
|
21
|
-
export declare function commandReadsOnly(command: string): boolean;
|
|
22
|
-
/**
|
|
23
|
-
* Whether one call may change something. A Bash call changes nothing only
|
|
24
|
-
* when its command is there and only looks; any other tool is a change unless
|
|
25
|
-
* {@link readsOnly} says it only reads — a tool it cannot tell about counts as
|
|
26
|
-
* a change. `input` is the call's arguments, as an object or as JSON text.
|
|
27
|
-
*/
|
|
28
|
-
export declare function callMayChange(toolName: string, input: unknown): boolean;
|
|
29
|
-
//# sourceMappingURL=reads-only.d.ts.map
|