patch-cc 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {patch_cc-0.3.0 → patch_cc-0.3.2}/PKG-INFO +1 -1
- {patch_cc-0.3.0 → patch_cc-0.3.2}/docs/CONDUCT.md +1 -1
- {patch_cc-0.3.0 → patch_cc-0.3.2}/docs/PLAYBOOK.md +98 -33
- {patch_cc-0.3.0 → patch_cc-0.3.2}/docs/corpus.md +10 -5
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/__init__.py +1 -1
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patches/agents.py +15 -9
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patches/base.py +31 -6
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patches/chrome.py +2 -1
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patches/codex.py +156 -40
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patches/streaming.py +77 -32
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patches/thinking.py +7 -5
- {patch_cc-0.3.0 → patch_cc-0.3.2}/.github/workflows/ci.yml +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/.github/workflows/release.yml +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/.gitignore +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/AGENTS.md +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/CLAUDE.md +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/LICENSE +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/README.md +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/docs/INTERNALS.md +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/docs/demo.gif +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/pyproject.toml +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/bun/__init__.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/bun/blob.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/bun/container.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/bun/elf.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/bun/errors.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/bun/macho.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/cache.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/cli.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/codex/__init__.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/codex/gateway.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/codex/models.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/codex/oauth.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/codex/translate.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/doctor.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/js.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/locate.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/menu.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patcher.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patches/__init__.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/patches/output.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/src/patch_cc/ui.py +0 -0
- {patch_cc-0.3.0 → patch_cc-0.3.2}/uv.lock +0 -0
|
@@ -56,7 +56,7 @@ shape that already absorbs every case, found rather than bolted on.
|
|
|
56
56
|
shape this build simply lacks — most patches carry several — not a regression.
|
|
57
57
|
Keep "gone", "already applied", and "not on this build" as distinct signals;
|
|
58
58
|
never collapse them into one number. Which one a sub-step's silence means is
|
|
59
|
-
not guesswork: declare it (`
|
|
59
|
+
not guesswork: declare it (`Outcome.declare`) so a green tick cannot cover a dead
|
|
60
60
|
feature. A step nobody declared is a step that cannot report its own death:
|
|
61
61
|
`branding` carried none, so a name badge that stopped being a bold render was
|
|
62
62
|
a green run, an unchanged banner, and a manifest asserting the new name. See
|
|
@@ -116,6 +116,18 @@ The tree removes the *mechanical* fragility. It does not decide these:
|
|
|
116
116
|
that branch verbatim rather than constructing a flush call; `org-label` builds
|
|
117
117
|
its line from upstream's own no-org string, so nothing here spells the
|
|
118
118
|
separator. What you copy cannot drift from what you copied it from.
|
|
119
|
+
- **Match an identity by the weakest claim that still proves it; hold a rewrite
|
|
120
|
+
to the exact node.** The two jobs pull in opposite directions.
|
|
121
|
+
`thinking-inline`'s null-guard neutralises only a `return` that answers
|
|
122
|
+
exactly `null`, because deleting a conditional answer would delete behaviour
|
|
123
|
+
— for a rewrite, doubt means stop. The codex resolvers' *identity* is the
|
|
124
|
+
opposite case: "rejects an unknown model" was once asked as "the default
|
|
125
|
+
returns exactly `null`", and 2.1.234 wrapping that same rejection in a dead
|
|
126
|
+
recognizer (`return vXu(e)?xVe(t):null`, the stub answering `!1` — behaviour
|
|
127
|
+
identical) read the resolver as gone with every anchor count standing.
|
|
128
|
+
Identity asks what the node *can* answer — membership among its possible
|
|
129
|
+
values, the same rule props and arrays already follow — and exactness there
|
|
130
|
+
is a break waiting on upstream's next harmless addition.
|
|
119
131
|
- **Never anchor on a minified local** (`A_`, `mET`, `wg6`). Unchanged, and now
|
|
120
132
|
structural: you capture the node, so there is never a reason to spell one.
|
|
121
133
|
|
|
@@ -356,13 +368,13 @@ and why `prop-threading` resolves scope itself.
|
|
|
356
368
|
|
|
357
369
|
Counting alone cannot tell "this build lacks that shape" from "the feature is
|
|
358
370
|
dead": a patch whose optional shapes rewrite happily while a load-bearing one
|
|
359
|
-
is gone still changes bytes, and would read as green. So each sub-step
|
|
360
|
-
what its absence *means* (`Outcome.
|
|
371
|
+
is gone still changes bytes, and would read as green. So each sub-step is
|
|
372
|
+
declared with what its absence *means* (`Outcome.declare`):
|
|
361
373
|
|
|
362
374
|
| mark | meaning | absence is |
|
|
363
375
|
|---|---|---|
|
|
364
|
-
|
|
|
365
|
-
| `
|
|
376
|
+
| `optional` | a shape only some builds carry | informational |
|
|
377
|
+
| `required` | the patch does nothing useful without it | a regression |
|
|
366
378
|
|
|
367
379
|
`Outcome.unmet()` turns a violation into a sentence (`required step
|
|
368
380
|
group-routing found nothing`); `Outcome.failures()` adds any exception the
|
|
@@ -392,9 +404,15 @@ Three rules keep the net from having holes:
|
|
|
392
404
|
feature are now checked by the markers their builders emit
|
|
393
405
|
(`streaming._CORE_UPDATES`), which no amount of neighbouring churn can fake.
|
|
394
406
|
- **Declare an expectation before the work, not inside it.** A step created only
|
|
395
|
-
by its own success cannot report its own absence.
|
|
396
|
-
|
|
397
|
-
|
|
407
|
+
by its own success cannot report its own absence. This is the API's shape,
|
|
408
|
+
not a discipline: `Outcome.declare` is the only way a step comes to exist
|
|
409
|
+
(up front, required and optional named apart), and `outcome.step(name)`
|
|
410
|
+
retrieves — an undeclared name raises, so a typo cannot mint a silently
|
|
411
|
+
optional step. Work that is owed conditionally declares under the same
|
|
412
|
+
condition it runs (`codex._register_context`), and a name resolved from the
|
|
413
|
+
bundle is declared the moment it resolves — `agents.bypassed_agents` has no
|
|
414
|
+
step to name until the helper's guard gives up the pinned agent, and a guard
|
|
415
|
+
that vanishes leaves the always-printed note, with no name left to declare.
|
|
398
416
|
|
|
399
417
|
`apply` acts on the verdict: a broken patch is re-run out of the final pass, so
|
|
400
418
|
its orphan edits never reach the binary, the manifest never claims it, and the
|
|
@@ -572,6 +590,20 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
572
590
|
arm's head was baked in as the file's length, `newLines:String(t)`, counted
|
|
573
591
|
and green.
|
|
574
592
|
|
|
593
|
+
**Alive by eye:** ask for a new file *via the Write tool* — the row reads
|
|
594
|
+
`Added N lines` over green `+` rows. Two things legitimately show no diff and
|
|
595
|
+
are not this patch failing: a file created through **Bash** (heredoc, `tee`,
|
|
596
|
+
a script) is Bash output and never enters this renderer; and upstream keeps
|
|
597
|
+
planting early returns *ahead of* the spliced render — plan-directory files
|
|
598
|
+
("/plan to preview"), condensed contexts (subagent progress rows, session
|
|
599
|
+
summaries: "Wrote N lines to path"), and scratchpad/`.workshop.*` files in
|
|
600
|
+
non-verbose re-renders ("Wrote N lines ⧉"). The live conversation renders
|
|
601
|
+
the current message verbose, so the diff shows there today; those guards are
|
|
602
|
+
the patch's effective coverage narrowing upstream-side, which `doctor` —
|
|
603
|
+
counting matchers, not paths — cannot see. Measured on 2.1.234 (live,
|
|
604
|
+
resume, plain/workshop/scratchpad files): every reachable path drew the
|
|
605
|
+
diff.
|
|
606
|
+
|
|
575
607
|
### Thinking — `thinking.py`
|
|
576
608
|
|
|
577
609
|
- **`thinking-summaries`** — stop echoing the account's server-side experiment
|
|
@@ -713,16 +745,28 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
713
745
|
- **`live-thinking`** — the fourteen-step patch above.
|
|
714
746
|
|
|
715
747
|
**`prop-threading`** inserts `streamingThinking:<state>,` before the
|
|
716
|
-
`
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
`
|
|
748
|
+
`conversationId` property of every props bag *handed to a component* — an
|
|
749
|
+
argument — that also carries `messages`. Being an argument is part of the
|
|
750
|
+
identity: a module-level literal, a return-value payload, or a config object
|
|
751
|
+
may legitimately carry the pair, and none of them is a render. Three things
|
|
752
|
+
do the work, and each replaced something that had broken:
|
|
753
|
+
|
|
754
|
+
- **Identity is membership — of the essential props alone.** The props that
|
|
755
|
+
make a render a conversation render, asked of one object rather than of a
|
|
756
|
+
span of text. Four matchers once modelled this — a brace-free
|
|
757
|
+
`createElement` call, an ordered prompt renderer, and two regexes differing
|
|
758
|
+
only in the order two call sites listed the same props. 2.1.229 killed the
|
|
759
|
+
ordered pair at once by inserting `onRateLimitAutoQueueContinue:` between
|
|
760
|
+
two of them. The membership then carried `agentDefinitions` as a third
|
|
761
|
+
conjunct — witness and insertion point in one — and 2.1.235 retired that
|
|
762
|
+
prop from the bag: an identity resting on a *neighbour* read a build that
|
|
763
|
+
plainly drew four conversation renders as drawing none, with every anchor
|
|
764
|
+
count standing (`agentDefinitions` still occurs 97 times — as other
|
|
765
|
+
components' prop). The conversation pair is what the render *is*; the
|
|
766
|
+
neighbour was one more thing upstream had to keep, and upstream owes the
|
|
767
|
+
matcher nothing. Same class as the resolver default on 2.1.234: the
|
|
768
|
+
identity must be the weakest claim that still proves it, and every conjunct
|
|
769
|
+
past that is a break waiting on a harmless refactor.
|
|
726
770
|
- **The insertion point is a prop boundary**, so nothing computes where a
|
|
727
771
|
literal ends. Being an `object` and not an `object_pattern` is what
|
|
728
772
|
separates a prop being *passed* from one being *received*; the regex spelled
|
|
@@ -770,7 +814,17 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
770
814
|
|
|
771
815
|
**`transcript-signature` and `inline-extras`** are one hand-off and two
|
|
772
816
|
steps: the first threads `streamingThinking` into every renderer whose
|
|
773
|
-
signature carries
|
|
817
|
+
signature carries `messages` and `streamingToolUses`, inserting before the
|
|
818
|
+
latter — an insertion point inside the identity, for `prop-threading`'s
|
|
819
|
+
reason. The signature was a trio until `showAllInTranscript` was measured to
|
|
820
|
+
discriminate nothing on any build in the corpus: a third conjunct that was
|
|
821
|
+
find-anchor, identity and insertion point in one, the exact triple role
|
|
822
|
+
`agentDefinitions` held when 2.1.235 retired it. Deriving the renderer from
|
|
823
|
+
its consumer instead — the scope that binds what the extras memo computes
|
|
824
|
+
over — was measured and rejected: the memo's receiver is a react-compiler
|
|
825
|
+
memoized *local* on every current build, with real dataflow between it and
|
|
826
|
+
the signature, and a dataflow pass is a tool this project deliberately does
|
|
827
|
+
not build. The second step rewrites that renderer's
|
|
774
828
|
extras memo to draw the live block in order. What the memo reads is resolved
|
|
775
829
|
in **its own scope** — the prop its enclosing function was handed — rather
|
|
776
830
|
than carried over from whichever renderer the first step happened to see
|
|
@@ -854,10 +908,10 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
854
908
|
`CLAUDE_CODE_DISABLE_EXPLORE_INHERIT_CAP` escape hatch in the middle, which
|
|
855
909
|
silently cost every Explore override until the body matcher learned to skip
|
|
856
910
|
intervening statements. The body is a node now, so there is nothing left to
|
|
857
|
-
skip. Resolving the agent from the guard alone is
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
911
|
+
skip. Resolving the agent from the guard alone is why a future body reshape
|
|
912
|
+
costs nothing at all: the body is replaced whole, however upstream grows it,
|
|
913
|
+
and `bypass:<agent>` stands in the report as the record that an override was
|
|
914
|
+
at stake.
|
|
861
915
|
|
|
862
916
|
If the **guard** goes too there is no step to fail — nothing left names the
|
|
863
917
|
agent — and no way to tell "upstream stopped pinning" from "the guard
|
|
@@ -883,14 +937,14 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
883
937
|
|---|---|---|
|
|
884
938
|
| `enum`\* | the Task tool's `model` enum, so a subagent can be pinned to a Codex id | `agents.model_enums` — the same arrays `discover_models` reads |
|
|
885
939
|
| `validator`\* | the known-model array — it gates *resolution*, not just acceptance | the array whose strings *include* `sonnet`/`opus`/`haiku`/`opusplan` — membership, never their order |
|
|
886
|
-
| `resolver`\* | the override resolver (managed `availableModels` only) | the `"best"` arm whose `switch`
|
|
940
|
+
| `resolver`\* | the override resolver (managed `availableModels` only) | the `"best"` arm whose `switch` *can* answer `null` for an unknown model |
|
|
887
941
|
| `general-resolver`\* | the resolver every ordinary request uses | the `"best"` arm whose `switch` has no answer of its own for one |
|
|
888
942
|
| `redirect`\* | swaps the request origin to `127.0.0.1:<port>` | the `buildRequest` method that builds a URL (the others — two to four across the corpus — only delegate to `super`) |
|
|
889
943
|
| `picker` | the `/model` list | the function every row list is handed to: it loops, adds, and returns its first parameter |
|
|
890
944
|
| `context` | the real context window | the function that *reads* `CLAUDE_CODE_MAX_CONTEXT_TOKENS` as a member and returns what it read, taking the model as a parameter; the table goes before its first statement |
|
|
891
945
|
| `registry` | the binary's own model table — the status-line name, effort capabilities, `/advisor` eligibility | the object carrying both `models` and `aliases`, confirmed by its entries carrying `id`/`family`/`display_name` |
|
|
892
946
|
|
|
893
|
-
\* required
|
|
947
|
+
\* required — without any one of them the feature is dead.
|
|
894
948
|
`picker`, `context` and `registry` are refinements: absent, you can still
|
|
895
949
|
type `/model <id>` and get the 200k default under the model's raw id.
|
|
896
950
|
`context` has no step at all when no chosen model reports a window — there
|
|
@@ -994,15 +1048,26 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
994
1048
|
registered, with the manifest asserting the override. Now the dropped patch
|
|
995
1049
|
takes its pins down with it.
|
|
996
1050
|
|
|
997
|
-
The two resolvers are told apart by **what
|
|
998
|
-
minified names or by a statement form** —
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1051
|
+
The two resolvers are told apart by **what each answers for an unknown
|
|
1052
|
+
model, never by their minified names or by a statement form** — `null` is
|
|
1053
|
+
among what the override one's default *can* answer, read off the grammar's
|
|
1054
|
+
own value routing (a ternary answers with either branch, `||`/`??` with
|
|
1055
|
+
their right side; 2.1.234 wrapped the same rejection in a dead recognizer,
|
|
1056
|
+
`return vXu(e)?xVe(t):null` with the stub answering `!1`, and the exact
|
|
1057
|
+
spelling read a behaviourally identical build as the resolver being gone),
|
|
1058
|
+
while the general one has no answer of its own — nothing in its default
|
|
1059
|
+
scoped to return or throw — and falls through to handing the name back.
|
|
1060
|
+
Both identities are asked positively, of switches whose labels carry the
|
|
1061
|
+
built-in models (the membership `validator` asks of its array, asked of the
|
|
1062
|
+
labels — a throwaway `case"best":` elsewhere is nothing, not an arm to
|
|
1063
|
+
classify), and an arm answering neither *raises* instead of swelling the
|
|
1064
|
+
other side: under the complement this replaced, a reshaped override slid
|
|
1065
|
+
silently into the general list, and only the required step's zero was left
|
|
1066
|
+
to speak. Braces around an arm are no discriminator either: they were, once,
|
|
1067
|
+
and a `let` in the general resolver's `case"best"` was enough to make every
|
|
1068
|
+
family shortcut vanish with the patch still green and seven of seven steps
|
|
1069
|
+
applied. The `buildRequest` methods and the `models`-shaped objects are
|
|
1070
|
+
likewise separated by what they *do* and *carry*.
|
|
1006
1071
|
|
|
1007
1072
|
**Two resolvers, and why both.** The **general** one turns `opus`
|
|
1008
1073
|
into `claude-opus-4-8`, its return value *replaces* the model before the
|
|
@@ -20,7 +20,7 @@ matches means the file changed under you, not that a number moved.
|
|
|
20
20
|
|
|
21
21
|
## On disk now
|
|
22
22
|
|
|
23
|
-
The reproducible set on the machine this was written on —
|
|
23
|
+
The reproducible set on the machine this was written on — 13 distinct binaries
|
|
24
24
|
by content (the SHA-256 is the whole file, `sha256sum <version>.orig`):
|
|
25
25
|
|
|
26
26
|
| version | size | sha256 |
|
|
@@ -36,13 +36,18 @@ by content (the SHA-256 is the whole file, `sha256sum <version>.orig`):
|
|
|
36
36
|
| `2.1.227` | 304 MB | `6832dc3f1797b890b71116e5f2dbbf9a83fd3d0498c235b4b0f9cd0e6e499ad6` |
|
|
37
37
|
| `2.1.228` | 309 MB | `d535985e6941a3eb00179ccd7f52ceb0c6623a0305a518ebc4e6514f84a94c99` |
|
|
38
38
|
| `2.1.233` | 325 MB | `55d281096f57d411ebbdd94dbf5e9ff3accb7c05713e37348c2c11d4b83bf9d9` |
|
|
39
|
+
| `2.1.234` | 328 MB | `3473601ea695d5bf769c5b202844d4cb4fbf723ae995450fcb6973204775c84a` |
|
|
40
|
+
| `2.1.235` | 331 MB | `bfcf0ae2dbf94b2b6a106074aabf3938b9a10889c3b678e4cb5a00c03274d5d5` |
|
|
39
41
|
|
|
40
42
|
The backup directory holds a few more files than rows here, and that is not a
|
|
41
43
|
discrepancy: a pre-0.2.0 backup doubled the version into its name
|
|
42
|
-
(`2.1.216.2.1.216.orig`),
|
|
43
|
-
saved as `claude.unknown-<hash>.orig
|
|
44
|
-
`2.1.
|
|
45
|
-
|
|
44
|
+
(`2.1.216.2.1.216.orig`), a binary installed under a non-version name is
|
|
45
|
+
saved as `claude.unknown-<hash>.orig`, and a binary patched under a
|
|
46
|
+
non-canonical filename keeps that filename (`2.1.235` entered the corpus as a
|
|
47
|
+
downloaded `claude-2.1.235`, so its backup is `claude-2.1.235.orig`). The two
|
|
48
|
+
extra 2.1.216-era files hash-match the `2.1.216` row above — same bytes, a
|
|
49
|
+
different filename — which is why the count of *files* (15) and the count of
|
|
50
|
+
*distinct binaries* (13) differ.
|
|
46
51
|
|
|
47
52
|
## Rebuild or extend it
|
|
48
53
|
|
|
@@ -100,7 +100,8 @@ def _subagent_prompt(source: Source, _options: Options, outcome: Outcome) -> Sou
|
|
|
100
100
|
``transcript && content && ...`` conjunction, which gates the agent's
|
|
101
101
|
*output* and is not this patch's business.
|
|
102
102
|
"""
|
|
103
|
-
|
|
103
|
+
outcome.declare(required=("gate",))
|
|
104
|
+
gate = outcome.step("gate")
|
|
104
105
|
edits: list[Edit] = []
|
|
105
106
|
seen: set[int] = set()
|
|
106
107
|
|
|
@@ -346,7 +347,7 @@ def discover_models(source: Source) -> list[str]:
|
|
|
346
347
|
by a guessed default. A hardcoded fallback (`haiku/sonnet/opus`) read a lost
|
|
347
348
|
enum as those three: `doctor` printed a plausible list, every pin to one of
|
|
348
349
|
them landed, and a pin to a name the real bundle accepts was refused -- the
|
|
349
|
-
same emptiness that `codex-models`' own `enum` step
|
|
350
|
+
same emptiness that `codex-models`' own required `enum` step reports
|
|
350
351
|
as broken in the same run. With nothing to offer, the offer is empty:
|
|
351
352
|
`discover_models` returns `[]`, `doctor` shows `models: inherit` alone, and a
|
|
352
353
|
requested pin fails its required step rather than landing on a name nothing
|
|
@@ -481,13 +482,14 @@ def _subagent_models(source: Source, options: Options, outcome: Outcome) -> Sour
|
|
|
481
482
|
found = {agent.name: agent for agent in agents}
|
|
482
483
|
edits: list[Edit] = []
|
|
483
484
|
|
|
485
|
+
# Required: every override reaching a patch has already been validated
|
|
486
|
+
# against this bundle by its surface (CLI, --from-cache, or the menu), so
|
|
487
|
+
# one that cannot be written is not a shape this build lacks -- it is the
|
|
488
|
+
# asked-for change failing. Left optional, the patch stayed green, the
|
|
489
|
+
# binary shipped without the override, and the manifest claimed it.
|
|
490
|
+
outcome.declare(required=tuple(sorted(options.subagent_models)))
|
|
484
491
|
for name, target in sorted(options.subagent_models.items()):
|
|
485
|
-
|
|
486
|
-
# against this bundle by its surface (CLI, --from-cache, or the menu),
|
|
487
|
-
# so one that cannot be written is not a shape this build lacks -- it is
|
|
488
|
-
# the asked-for change failing. Left optional, the patch stayed green,
|
|
489
|
-
# the binary shipped without the override, and the manifest claimed it.
|
|
490
|
-
step = outcome.step(name, expect=True)
|
|
492
|
+
step = outcome.step(name)
|
|
491
493
|
if target not in offered:
|
|
492
494
|
step.note(f"model {target!r} not offered by this bundle; skipped")
|
|
493
495
|
continue
|
|
@@ -522,7 +524,11 @@ def _subagent_models(source: Source, options: Options, outcome: Outcome) -> Sour
|
|
|
522
524
|
for bypass in bypasses:
|
|
523
525
|
if bypass.agent not in options.subagent_models:
|
|
524
526
|
continue
|
|
525
|
-
|
|
527
|
+
# Declared the moment the guard resolves the agent's name -- the
|
|
528
|
+
# earliest this step *can* exist -- while a vanished guard stays the
|
|
529
|
+
# note above, with no name left to declare.
|
|
530
|
+
outcome.declare(required=(f"bypass:{bypass.agent}",))
|
|
531
|
+
step = outcome.step(f"bypass:{bypass.agent}")
|
|
526
532
|
step.candidates += 1
|
|
527
533
|
step.applied += 1
|
|
528
534
|
edits.append(
|
|
@@ -109,7 +109,7 @@ class Outcome:
|
|
|
109
109
|
notes: list[str] = field(default_factory=list)
|
|
110
110
|
#: Named sub-steps, for patches built from several independent rewrites.
|
|
111
111
|
steps: dict[str, Outcome] = field(default_factory=dict)
|
|
112
|
-
#: What this sub-step's absence means (set via :meth:`
|
|
112
|
+
#: What this sub-step's absence means (set via :meth:`declare`). ``False`` --
|
|
113
113
|
#: a shape some builds simply lack; ``True`` -- the patch is broken without
|
|
114
114
|
#: it.
|
|
115
115
|
expect: bool = False
|
|
@@ -167,18 +167,43 @@ class Outcome:
|
|
|
167
167
|
def note(self, message: str) -> None:
|
|
168
168
|
self.notes.append(message)
|
|
169
169
|
|
|
170
|
-
def
|
|
171
|
-
|
|
170
|
+
def declare(
|
|
171
|
+
self, required: tuple[str, ...] = (), optional: tuple[str, ...] = ()
|
|
172
|
+
) -> None:
|
|
173
|
+
"""Create sub-steps, before any of them does its work.
|
|
174
|
+
|
|
175
|
+
Declaring is the only way a step comes to exist (:meth:`step` only
|
|
176
|
+
retrieves), which makes "declare an expectation before the work" the
|
|
177
|
+
API's shape instead of each patch's discipline: a code path that never
|
|
178
|
+
runs leaves a required step at 0/0 with a verdict to fail, where a step
|
|
179
|
+
created by its own success could never report its own absence --
|
|
180
|
+
`branding`'s badge went unrenamed under exactly that silence, and the
|
|
181
|
+
lazily-created step was the hole the discipline papered over.
|
|
182
|
+
Conditional work declares under the same condition it runs (`context`),
|
|
183
|
+
and a name resolved from the bundle is declared the moment it resolves
|
|
184
|
+
(`bypass:<agent>`).
|
|
185
|
+
"""
|
|
186
|
+
for name in required:
|
|
187
|
+
self.steps.setdefault(name, Outcome()).expect = True
|
|
188
|
+
for name in optional:
|
|
189
|
+
self.steps.setdefault(name, Outcome())
|
|
190
|
+
|
|
191
|
+
def step(self, name: str) -> Outcome:
|
|
192
|
+
"""A declared sub-step, to record work against.
|
|
172
193
|
|
|
173
194
|
A single scalar count cannot distinguish "all twelve rewrites landed"
|
|
174
195
|
from "six landed and six silently drifted" -- which is exactly how
|
|
175
196
|
upstream's live-thinking patch hides its own regressions. Recording each
|
|
176
197
|
rewrite separately turns that into an actionable "reducer.message_stop
|
|
177
198
|
missed".
|
|
199
|
+
|
|
200
|
+
Retrieval only: a name nobody declared is a programming error and
|
|
201
|
+
raises, which :meth:`Patch.run` reports as the patch broken -- loud,
|
|
202
|
+
never a silently-optional step minted by a typo.
|
|
178
203
|
"""
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return
|
|
204
|
+
if name not in self.steps:
|
|
205
|
+
raise RuntimeError(f"step {name!r} was never declared")
|
|
206
|
+
return self.steps[name]
|
|
182
207
|
|
|
183
208
|
def finalize(self) -> Outcome:
|
|
184
209
|
"""Roll sub-step totals up into this outcome."""
|
|
@@ -268,7 +268,8 @@ def _branding(source: Source, options: Options, outcome: Outcome) -> Source:
|
|
|
268
268
|
# rather than a green tick over a binary that still says Claude Code. The
|
|
269
269
|
# other two are sentences upstream may reword or drop, and their absence is
|
|
270
270
|
# a build lacking a shape, not a dead feature.
|
|
271
|
-
|
|
271
|
+
outcome.declare(required=("badge",), optional=("styled", "welcome"))
|
|
272
|
+
badge = outcome.step("badge")
|
|
272
273
|
styled = outcome.step("styled")
|
|
273
274
|
welcome = outcome.step("welcome")
|
|
274
275
|
|
|
@@ -29,7 +29,7 @@ and the subagent pins: the ids to register, and the port to route them to.
|
|
|
29
29
|
Nothing here reads a store of its own, which is what makes the binary's manifest
|
|
30
30
|
the one record of what a patched Claude Code answers to.
|
|
31
31
|
|
|
32
|
-
Required steps
|
|
32
|
+
Required steps: without any one, the feature is dead --
|
|
33
33
|
``enum`` (subagent use), ``validator`` and ``resolver`` (the model is accepted
|
|
34
34
|
and resolves -- an id to itself, a shortcut to the newest id in its family),
|
|
35
35
|
``redirect`` (requests actually reach the gateway). ``general-resolver`` is
|
|
@@ -165,52 +165,153 @@ def claimed_model_names(source: Source) -> set[str]:
|
|
|
165
165
|
# ----------------------------------------------------------------- resolvers
|
|
166
166
|
#
|
|
167
167
|
# The binary has TWO model resolvers and a shortcut needs an arm in both. They
|
|
168
|
-
# are told apart by what
|
|
169
|
-
# statement form: the override resolver (reached only
|
|
170
|
-
# `availableModels` are active)
|
|
171
|
-
#
|
|
172
|
-
# `claude-opus-4-8`
|
|
173
|
-
# passing
|
|
168
|
+
# are told apart by what each *answers* for an unknown model, never by their
|
|
169
|
+
# minified names or by a statement form: the override resolver (reached only
|
|
170
|
+
# when managed `availableModels` are active) has a closed list and rejects --
|
|
171
|
+
# null is among what its default can answer -- while the general resolver (the
|
|
172
|
+
# one every ordinary request uses, which turns `opus` into `claude-opus-4-8`)
|
|
173
|
+
# has no answer of its own and falls through to passing the name straight back.
|
|
174
|
+
# Both identities are asked positively, of every model resolver, and an arm
|
|
175
|
+
# answering neither raises instead of swelling the other side: a complement
|
|
176
|
+
# cannot report its own absence.
|
|
174
177
|
|
|
175
178
|
|
|
176
179
|
def _best_arms(source: Source) -> list[js.Node]:
|
|
177
|
-
"""Every
|
|
180
|
+
"""Every model resolver's ``"best"`` arm -- where new model arms are spliced.
|
|
178
181
|
|
|
179
182
|
The label is the name and the arm is the node; ``case"best":`` written out
|
|
180
|
-
is the two of them with the minifier's spacing in between.
|
|
183
|
+
is the two of them with the minifier's spacing in between. The switch also
|
|
184
|
+
has to *be* a model resolver -- its labels carry the built-in models, the
|
|
185
|
+
same membership `_validators` asks of its array -- because everything here
|
|
186
|
+
applies to every arm of the kind: a throwaway ``case"best":`` in unrelated
|
|
187
|
+
code must be nothing to a registration that would splice into it and to a
|
|
188
|
+
classifier that would be asked what it is (`_resolvers`).
|
|
181
189
|
"""
|
|
182
190
|
found = []
|
|
183
191
|
for node in source.find(_BEST):
|
|
184
192
|
arm = js.up(node, "switch_case")
|
|
185
|
-
if
|
|
193
|
+
if (
|
|
194
|
+
arm is not None
|
|
195
|
+
and arm.child_by_field_name("value") == node
|
|
196
|
+
and set(_BUILT_IN_MODELS) <= _labels(arm)
|
|
197
|
+
):
|
|
186
198
|
found.append(arm)
|
|
187
199
|
return found
|
|
188
200
|
|
|
189
201
|
|
|
190
|
-
def
|
|
191
|
-
"""
|
|
202
|
+
def _labels(arm: js.Node) -> set[str]:
|
|
203
|
+
"""The string labels of this arm's whole ``switch``, as their values.
|
|
192
204
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
205
|
+
A set: which names the switch dispatches on is the question, and neither
|
|
206
|
+
their order nor what sits between them is part of it.
|
|
207
|
+
"""
|
|
208
|
+
return {
|
|
209
|
+
js.text(value)[1:-1]
|
|
210
|
+
for case in js.children(arm.parent)
|
|
211
|
+
if case.type == "switch_case"
|
|
212
|
+
for value in [case.child_by_field_name("value")]
|
|
213
|
+
if value is not None and value.type == "string"
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _answer(statement: js.Node) -> js.Node | None:
|
|
218
|
+
"""The expression a ``return`` answers with -- nothing for a bare ``return;``."""
|
|
219
|
+
parts = [child for child in js.children(statement) if child.type != "comment"]
|
|
220
|
+
return parts[0] if parts else None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _may_answer_null(expr: js.Node | None) -> bool:
|
|
224
|
+
"""Can this expression's value be ``null``?
|
|
225
|
+
|
|
226
|
+
Asked of the grammar's own value routing, so the rejection keeps counting
|
|
227
|
+
however much recognition upstream composes in front of it: a ternary
|
|
228
|
+
answers with either branch, parentheses and a sequence with their last
|
|
229
|
+
expression, ``||`` and ``??`` with their right side (a null on their left
|
|
230
|
+
is exactly what both exist to pass over), ``&&`` with either side (null is
|
|
231
|
+
falsy), an assignment with the value it assigns. Everything else -- a call,
|
|
232
|
+
an identifier, an ``await`` -- is opaque: it may well evaluate to null, but
|
|
233
|
+
the grammar does not say so, and a rejection hidden behind one is a new
|
|
234
|
+
shape to be told about (`_resolvers` raises), never one to absorb.
|
|
235
|
+
|
|
236
|
+
2.1.234 is why this is a question about *possible* answers and not the
|
|
237
|
+
answer's spelling: the override default became ``return vXu(e)?xVe(t):null``
|
|
238
|
+
with ``vXu`` a stub returning ``!1`` -- behaviour identical to ``return
|
|
239
|
+
null`` -- and "exactly a bare null" read that build's rejection as gone.
|
|
240
|
+
"""
|
|
241
|
+
if expr is None:
|
|
242
|
+
return False
|
|
243
|
+
if expr.type == "null":
|
|
244
|
+
return True
|
|
245
|
+
if expr.type == "ternary_expression":
|
|
246
|
+
return any(
|
|
247
|
+
_may_answer_null(expr.child_by_field_name(field))
|
|
248
|
+
for field in ("consequence", "alternative")
|
|
249
|
+
)
|
|
250
|
+
if expr.type in ("parenthesized_expression", "sequence_expression"):
|
|
251
|
+
parts = [child for child in js.children(expr) if child.type != "comment"]
|
|
252
|
+
return bool(parts) and _may_answer_null(parts[-1])
|
|
253
|
+
if expr.type == "assignment_expression":
|
|
254
|
+
return _may_answer_null(expr.child_by_field_name("right"))
|
|
255
|
+
if expr.type == "binary_expression":
|
|
256
|
+
operator = expr.child_by_field_name("operator")
|
|
257
|
+
sides = {"&&": ("left", "right"), "||": ("right",), "??": ("right",)}
|
|
258
|
+
return any(
|
|
259
|
+
_may_answer_null(expr.child_by_field_name(field))
|
|
260
|
+
for field in sides.get("" if operator is None else operator.type, ())
|
|
261
|
+
)
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _resolvers(source: Source) -> tuple[list[js.Node], list[js.Node]]:
|
|
266
|
+
"""Every model resolver, classified: (override, general).
|
|
267
|
+
|
|
268
|
+
Each identity is asked positively of the arm's own ``switch``: the override
|
|
269
|
+
resolver's default *can answer null* (`_may_answer_null`, over the
|
|
270
|
+
default's scoped returns -- a callback's ``return`` answers for the
|
|
271
|
+
callback); the general resolver's default has no answer of its own --
|
|
272
|
+
absent, or with nothing scoped to return or throw -- so an unknown model
|
|
273
|
+
falls out of the switch and back to the caller's passthrough. Whether
|
|
274
|
+
either spells an arm as ``case"best":return f()`` or
|
|
275
|
+
``case"best":{return f()}`` is the minifier's business -- telling them
|
|
198
276
|
apart by that was a silent failure waiting: brace the general resolver (a
|
|
199
277
|
`let` in the arm is enough) and every family shortcut disappeared with the
|
|
200
278
|
patch still green.
|
|
279
|
+
|
|
280
|
+
The identities exclude each other by construction, and an arm holding
|
|
281
|
+
neither -- a default that answers something, never null -- raises, which
|
|
282
|
+
`Patch.run` turns into one broken patch naming the new shape (the promise
|
|
283
|
+
`js.only` keeps for cardinality, kept here for classification). Under the
|
|
284
|
+
complement this replaced, such an arm slid silently into the general list,
|
|
285
|
+
and only a required step's zero was left to say anything at all.
|
|
201
286
|
"""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
287
|
+
override: list[js.Node] = []
|
|
288
|
+
general: list[js.Node] = []
|
|
289
|
+
for arm in _best_arms(source):
|
|
290
|
+
default = next(
|
|
291
|
+
(
|
|
292
|
+
sibling
|
|
293
|
+
for sibling in js.children(arm.parent)
|
|
294
|
+
if sibling.type == "switch_default"
|
|
295
|
+
),
|
|
296
|
+
None,
|
|
297
|
+
)
|
|
298
|
+
answers = js.every(default, js.of_type("return_statement"), scoped=True)
|
|
299
|
+
refusals = js.every(default, js.of_type("throw_statement"), scoped=True)
|
|
300
|
+
if not answers and not refusals:
|
|
301
|
+
general.append(arm)
|
|
302
|
+
elif any(_may_answer_null(_answer(statement)) for statement in answers):
|
|
303
|
+
override.append(arm)
|
|
304
|
+
else:
|
|
305
|
+
answered = [_answer(statement) for statement in answers]
|
|
306
|
+
kinds = sorted(
|
|
307
|
+
{"nothing" if value is None else value.type for value in answered}
|
|
308
|
+
| ({"a throw"} if refusals else set())
|
|
309
|
+
)
|
|
310
|
+
raise ValueError(
|
|
311
|
+
"a model resolver neither rejects an unknown model nor passes "
|
|
312
|
+
f"it through: its default answers {', '.join(kinds)}"
|
|
313
|
+
)
|
|
314
|
+
return override, general
|
|
214
315
|
|
|
215
316
|
|
|
216
317
|
def _existing_arms(arm: js.Node) -> set[str]:
|
|
@@ -241,12 +342,12 @@ def _arms(resolution: dict[str, str]) -> str:
|
|
|
241
342
|
|
|
242
343
|
def _override_resolvers(source: Source) -> list[js.Node]:
|
|
243
344
|
"""Every resolver reached only when managed ``availableModels`` are active."""
|
|
244
|
-
return
|
|
345
|
+
return _resolvers(source)[0]
|
|
245
346
|
|
|
246
347
|
|
|
247
348
|
def _general_resolvers(source: Source) -> list[js.Node]:
|
|
248
349
|
"""Every resolver an ordinary request goes through."""
|
|
249
|
-
return
|
|
350
|
+
return _resolvers(source)[1]
|
|
250
351
|
|
|
251
352
|
|
|
252
353
|
def _register_arms(
|
|
@@ -562,8 +663,8 @@ def _register_context(source: Source, models, outcome: Outcome) -> Source:
|
|
|
562
663
|
# place to bake it -- a drifted window resolver then reads as this step
|
|
563
664
|
# finding nothing, not as no step at all. Created *after* the search, a
|
|
564
665
|
# renamed CLAUDE_CODE_MAX_CONTEXT_TOKENS produced seven steps, no note and no
|
|
565
|
-
# absent-step line, the exact silence
|
|
566
|
-
|
|
666
|
+
# absent-step line, the exact silence `Outcome.declare` now exists to break.
|
|
667
|
+
outcome.declare(optional=("context",))
|
|
567
668
|
step = outcome.step("context")
|
|
568
669
|
resolver = _window_resolver(source)
|
|
569
670
|
body = js.body(resolver) if resolver is not None else None
|
|
@@ -760,6 +861,11 @@ def _codex_models(source: Source, options: Options, outcome: Outcome) -> Source:
|
|
|
760
861
|
return source
|
|
761
862
|
|
|
762
863
|
model_ids = [m.id for m in options.codex_models]
|
|
864
|
+
# Classified once here for the gate and the note; the registrations below
|
|
865
|
+
# re-derive from the source each hands the next, since every batch of edits
|
|
866
|
+
# is a new parse. A count that moves on a green run is the early warning.
|
|
867
|
+
override, general = _resolvers(source)
|
|
868
|
+
outcome.note(f"resolvers: {len(override)} override, {len(general)} general")
|
|
763
869
|
# Family shortcuts (`sol` -> the newest `gpt-<ver>-sol`) resolve to an id
|
|
764
870
|
# before the request is built, so they ride only the surfaces that accept,
|
|
765
871
|
# resolve, and show a model -- validator, both resolvers, picker. They need no
|
|
@@ -770,33 +876,43 @@ def _codex_models(source: Source, options: Options, outcome: Outcome) -> Source:
|
|
|
770
876
|
# so absent it, no shortcut is registered anywhere and the ids (which need none
|
|
771
877
|
# of this) carry on untouched.
|
|
772
878
|
shorts = family_aliases(options.codex_models)
|
|
773
|
-
if shorts and not
|
|
879
|
+
if shorts and not general:
|
|
774
880
|
outcome.note("general model resolver anchor drifted; shortcuts skipped")
|
|
775
881
|
shorts = {}
|
|
776
882
|
|
|
777
|
-
|
|
778
|
-
|
|
883
|
+
# `general-resolver` is owed exactly when a shortcut is; `context` declares
|
|
884
|
+
# itself where its own work is owed (see _register_context).
|
|
885
|
+
outcome.declare(
|
|
886
|
+
required=(
|
|
887
|
+
"enum",
|
|
888
|
+
"validator",
|
|
889
|
+
"resolver",
|
|
890
|
+
*(("general-resolver",) if shorts else ()),
|
|
891
|
+
"redirect",
|
|
892
|
+
),
|
|
893
|
+
optional=("picker", "registry"),
|
|
779
894
|
)
|
|
895
|
+
source = _register(source, model_enums(source), model_ids, outcome.step("enum"))
|
|
780
896
|
source = _register(
|
|
781
897
|
source,
|
|
782
898
|
_validators(source),
|
|
783
899
|
model_ids + list(shorts),
|
|
784
|
-
outcome.step("validator"
|
|
900
|
+
outcome.step("validator"),
|
|
785
901
|
)
|
|
786
902
|
source = _register_arms(
|
|
787
903
|
source,
|
|
788
904
|
_override_resolvers(source),
|
|
789
905
|
{i: i for i in model_ids} | shorts,
|
|
790
|
-
outcome.step("resolver"
|
|
906
|
+
outcome.step("resolver"),
|
|
791
907
|
)
|
|
792
908
|
if shorts:
|
|
793
909
|
source = _register_arms(
|
|
794
910
|
source,
|
|
795
911
|
_general_resolvers(source),
|
|
796
912
|
shorts,
|
|
797
|
-
outcome.step("general-resolver"
|
|
913
|
+
outcome.step("general-resolver"),
|
|
798
914
|
)
|
|
799
|
-
source = _redirect(source, options, outcome.step("redirect"
|
|
915
|
+
source = _redirect(source, options, outcome.step("redirect"))
|
|
800
916
|
source = _register_picker(
|
|
801
917
|
source, options.codex_models, shorts, outcome.step("picker")
|
|
802
918
|
)
|
|
@@ -116,13 +116,22 @@ def _delta(event: str, setter: str, helper: str) -> str:
|
|
|
116
116
|
#: props freely and inserts new ones between them, and neither is a fact about a
|
|
117
117
|
#: set. Two regexes once differed only in the order two call sites listed the
|
|
118
118
|
#: same props, and 2.1.229 killed both at once by inserting
|
|
119
|
-
#: `onRateLimitAutoQueueContinue:` between two of them.
|
|
119
|
+
#: `onRateLimitAutoQueueContinue:` between two of them. The pair is also the
|
|
120
|
+
#: *whole* identity: it once had `agentDefinitions` beside it as witness and
|
|
121
|
+
#: insertion point, and 2.1.235 retired that prop from the bag -- an identity
|
|
122
|
+
#: resting on a neighbour reported no conversation renders on a build that
|
|
123
|
+
#: plainly drew four. The conversation is what the render is *for*; the props
|
|
124
|
+
#: that name it are the weakest claim that still proves it.
|
|
120
125
|
_CONVERSATION = ("conversationId", "messages")
|
|
121
|
-
|
|
122
|
-
_AGENT_DEFINITIONS = "agentDefinitions"
|
|
123
126
|
_STREAMING = "streamingThinking"
|
|
124
127
|
_SETTER = "onStreamingThinking"
|
|
125
|
-
|
|
128
|
+
#: What the transcript renderer's signature *is*: the conversation it draws and
|
|
129
|
+
#: the streaming tool-uses the extras memo computes over. A third conjunct
|
|
130
|
+
#: (`showAllInTranscript`) stood here as find-anchor, identity and insertion
|
|
131
|
+
#: point in one -- the exact triple role `agentDefinitions` held in
|
|
132
|
+
#: `prop-threading` when 2.1.235 retired it -- and discriminates nothing on any
|
|
133
|
+
#: build in the corpus: the pair already names the same one renderer.
|
|
134
|
+
_TRANSCRIPT_SIGNATURE = ("messages", "streamingToolUses")
|
|
126
135
|
_INJECTED = "__cc_streamingThinking"
|
|
127
136
|
|
|
128
137
|
|
|
@@ -140,26 +149,34 @@ def _outermost(scope: js.Node) -> bool:
|
|
|
140
149
|
def _conversation_renders(source: Source) -> list[js.Node]:
|
|
141
150
|
"""Every props bag that is a conversation render, as the object it is.
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
the
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
The identity is one of the identity's own props -- a new property
|
|
153
|
+
immediately before an existing one is valid in any object literal -- so
|
|
154
|
+
nothing here computes where the bag *ends*, and the insertion point cannot
|
|
155
|
+
be absent from a bag the identity admitted. Ending the literal used to be
|
|
156
|
+
the whole difficulty: delimiting one in minified JS means telling a regex
|
|
157
|
+
literal from division, and a hand-rolled scanner that guesses returns a
|
|
158
|
+
`}` that is merely wrong.
|
|
149
159
|
|
|
150
160
|
Being an ``object`` and not an ``object_pattern`` is what separates a prop
|
|
151
161
|
being *passed* from one being *received*; inserting into the latter would
|
|
152
162
|
rebind a local. The regex spelled that as a call-opening it had to match.
|
|
163
|
+
A render bag is also *handed to* a component -- an argument -- which is
|
|
164
|
+
what keeps a module-level literal, a return-value payload, or a config
|
|
165
|
+
object that happens to carry the pair from being read as a render: those
|
|
166
|
+
are the decoys the pair alone admits, and being an argument is a fact
|
|
167
|
+
about what a render *is*, not about how this build spells one.
|
|
153
168
|
"""
|
|
154
169
|
found = []
|
|
155
|
-
for node in source.find(
|
|
170
|
+
for node in source.find(_CONVERSATION[0]):
|
|
156
171
|
pair = js.named(node)
|
|
157
172
|
if pair is None:
|
|
158
173
|
continue
|
|
159
174
|
bag = js.owner(pair)
|
|
160
175
|
if bag is None or bag.type != "object":
|
|
161
176
|
continue
|
|
162
|
-
if
|
|
177
|
+
if bag.parent is None or bag.parent.type != "arguments":
|
|
178
|
+
continue
|
|
179
|
+
if js.carries(bag, *_CONVERSATION[1:]):
|
|
163
180
|
found.append(bag)
|
|
164
181
|
return found
|
|
165
182
|
|
|
@@ -237,7 +254,7 @@ def _state_in_scope(site: js.Node) -> str | None:
|
|
|
237
254
|
|
|
238
255
|
def _step_prop_threading(source: Source, outcome: Outcome) -> Source:
|
|
239
256
|
"""Pass the live-thinking state into the renderers that need it."""
|
|
240
|
-
step = outcome.step("prop-threading"
|
|
257
|
+
step = outcome.step("prop-threading")
|
|
241
258
|
edits = []
|
|
242
259
|
renders = _conversation_renders(source)
|
|
243
260
|
# Printed every run, green ones included: an early warning held back until
|
|
@@ -253,7 +270,7 @@ def _step_prop_threading(source: Source, outcome: Outcome) -> Source:
|
|
|
253
270
|
step.applied += 1
|
|
254
271
|
edits.append(
|
|
255
272
|
Edit.before(
|
|
256
|
-
js.entry(js.props(bag)[
|
|
273
|
+
js.entry(js.props(bag)[_CONVERSATION[0]]), f"{_STREAMING}:{state},"
|
|
257
274
|
)
|
|
258
275
|
)
|
|
259
276
|
return source.apply(edits)
|
|
@@ -327,7 +344,7 @@ def _step_display_mode(source: Source, outcome: Outcome) -> Source:
|
|
|
327
344
|
gains a default. Whatever guards reach it, and in whatever order the
|
|
328
345
|
declaration lists them, is untouched because it is never matched.
|
|
329
346
|
"""
|
|
330
|
-
step = outcome.step("display-mode"
|
|
347
|
+
step = outcome.step("display-mode")
|
|
331
348
|
edits = []
|
|
332
349
|
seen: set[int] = set()
|
|
333
350
|
|
|
@@ -487,11 +504,24 @@ def _step_final_summary(source: Source, outcome: Outcome) -> Source:
|
|
|
487
504
|
|
|
488
505
|
|
|
489
506
|
def _step_transcript_signature(source: Source, outcome: Outcome) -> Source:
|
|
490
|
-
"""Make sure the transcript renderer actually receives the live state.
|
|
491
|
-
|
|
507
|
+
"""Make sure the transcript renderer actually receives the live state.
|
|
508
|
+
|
|
509
|
+
The insertion point is one of the identity's own names, so it cannot be
|
|
510
|
+
absent from a pattern the identity admitted -- the guarantee
|
|
511
|
+
``prop-threading`` moved to after 2.1.235 retired its third conjunct.
|
|
512
|
+
|
|
513
|
+
Deriving the renderer from its consumer instead -- the scope that binds
|
|
514
|
+
what the streaming-extras memo computes over -- was measured and rejected:
|
|
515
|
+
on every current build the memo's receiver is a react-compiler memoized
|
|
516
|
+
*local* (``Te=useMemo(...)``), with real dataflow between it and the
|
|
517
|
+
signature, and a dataflow pass is a tool this project deliberately does
|
|
518
|
+
not build. Membership of the pair is the weakest claim the grammar can
|
|
519
|
+
still prove.
|
|
520
|
+
"""
|
|
521
|
+
step = outcome.step("transcript-signature")
|
|
492
522
|
edits = []
|
|
493
523
|
|
|
494
|
-
for node in source.find(_TRANSCRIPT_SIGNATURE[
|
|
524
|
+
for node in source.find(_TRANSCRIPT_SIGNATURE[1]):
|
|
495
525
|
pattern = js.owner(node)
|
|
496
526
|
# A destructured parameter list, not a props bag being passed: this is
|
|
497
527
|
# the renderer's own signature, and the same property names appear on
|
|
@@ -509,7 +539,7 @@ def _step_transcript_signature(source: Source, outcome: Outcome) -> Source:
|
|
|
509
539
|
continue
|
|
510
540
|
edits.append(
|
|
511
541
|
Edit.before(
|
|
512
|
-
js.entry(carried[_TRANSCRIPT_SIGNATURE[
|
|
542
|
+
js.entry(carried[_TRANSCRIPT_SIGNATURE[1]]),
|
|
513
543
|
f"{_STREAMING}:{_INJECTED},",
|
|
514
544
|
)
|
|
515
545
|
)
|
|
@@ -593,7 +623,7 @@ def _flatmap_extras(source: Source) -> tuple[js.Node, js.Node] | None:
|
|
|
593
623
|
|
|
594
624
|
def _step_inline_extras(source: Source, found: Discovery, outcome: Outcome) -> Source:
|
|
595
625
|
"""Render live thinking inline, ordered with streaming tool-use blocks."""
|
|
596
|
-
step = outcome.step("inline-extras"
|
|
626
|
+
step = outcome.step("inline-extras")
|
|
597
627
|
computed = _flatmap_extras(source)
|
|
598
628
|
if computed is None:
|
|
599
629
|
step.note("no streaming-extras memo in this build")
|
|
@@ -891,7 +921,7 @@ def _step_reducer(source: Source, found: Discovery, outcome: Outcome) -> Source:
|
|
|
891
921
|
away reads as that point's absence by name rather than as a bare count drop
|
|
892
922
|
inside an aggregate.
|
|
893
923
|
"""
|
|
894
|
-
step = outcome.step("reducer"
|
|
924
|
+
step = outcome.step("reducer")
|
|
895
925
|
helper = found.create_message_helper
|
|
896
926
|
handler = _reducer(source)
|
|
897
927
|
if handler is None or helper is None:
|
|
@@ -937,27 +967,27 @@ def _step_reducer(source: Source, found: Discovery, outcome: Outcome) -> Source:
|
|
|
937
967
|
handler,
|
|
938
968
|
(_REQUEST_START,),
|
|
939
969
|
f"{setter}?.(null)",
|
|
940
|
-
outcome.step("request-start"
|
|
970
|
+
outcome.step("request-start"),
|
|
941
971
|
)
|
|
942
972
|
edits += _dispatch(
|
|
943
973
|
handler,
|
|
944
974
|
(_MESSAGE_STOP,),
|
|
945
975
|
ended,
|
|
946
|
-
outcome.step("message-stop"
|
|
976
|
+
outcome.step("message-stop"),
|
|
947
977
|
on="event",
|
|
948
978
|
)
|
|
949
979
|
edits += _dispatch(
|
|
950
980
|
handler,
|
|
951
981
|
("text",),
|
|
952
982
|
cleared,
|
|
953
|
-
outcome.step("text-clear"
|
|
983
|
+
outcome.step("text-clear"),
|
|
954
984
|
on="content_block",
|
|
955
985
|
)
|
|
956
986
|
edits += _dispatch(
|
|
957
987
|
handler,
|
|
958
988
|
("message_delta",),
|
|
959
989
|
cleared,
|
|
960
|
-
outcome.step("message-delta-clear"
|
|
990
|
+
outcome.step("message-delta-clear"),
|
|
961
991
|
on="event",
|
|
962
992
|
)
|
|
963
993
|
edits += _dispatch(
|
|
@@ -982,11 +1012,26 @@ def _step_reducer(source: Source, found: Discovery, outcome: Outcome) -> Source:
|
|
|
982
1012
|
|
|
983
1013
|
def _live_thinking(source: Source, _options: Options, outcome: Outcome) -> Source:
|
|
984
1014
|
found = Discovery()
|
|
985
|
-
#
|
|
986
|
-
# existence once its own rewrite succeeds
|
|
987
|
-
#
|
|
988
|
-
|
|
989
|
-
|
|
1015
|
+
# Every step this patch can take, declared before anything runs: an
|
|
1016
|
+
# expectation that only comes into existence once its own rewrite succeeds
|
|
1017
|
+
# can never report that rewrite missing. The dispatch points are declared
|
|
1018
|
+
# here too, so a build whose reducer is gone fails *them* by name as well,
|
|
1019
|
+
# instead of them never having existed.
|
|
1020
|
+
outcome.declare(
|
|
1021
|
+
required=(
|
|
1022
|
+
"prop-threading",
|
|
1023
|
+
"display-mode",
|
|
1024
|
+
"transcript-signature",
|
|
1025
|
+
"inline-extras",
|
|
1026
|
+
"reducer",
|
|
1027
|
+
"request-start",
|
|
1028
|
+
"message-stop",
|
|
1029
|
+
"text-clear",
|
|
1030
|
+
"message-delta-clear",
|
|
1031
|
+
*(name for name, _marker in _CORE_UPDATES),
|
|
1032
|
+
),
|
|
1033
|
+
optional=("final-summary", "thinking-start", "thinking-append"),
|
|
1034
|
+
)
|
|
990
1035
|
|
|
991
1036
|
source = _step_prop_threading(source, outcome)
|
|
992
1037
|
source = _step_display_mode(source, outcome)
|
|
@@ -999,7 +1044,7 @@ def _live_thinking(source: Source, _options: Options, outcome: Outcome) -> Sourc
|
|
|
999
1044
|
# updates *are* the feature, and every other step can land without them.
|
|
1000
1045
|
for name, marker in _CORE_UPDATES:
|
|
1001
1046
|
if source.count(marker):
|
|
1002
|
-
step = outcome.step(name
|
|
1047
|
+
step = outcome.step(name)
|
|
1003
1048
|
step.candidates += 1
|
|
1004
1049
|
step.applied += 1
|
|
1005
1050
|
return source
|
|
@@ -1018,7 +1063,7 @@ PATCHES = [
|
|
|
1018
1063
|
f'case"{_THINKING_DELTA}"',
|
|
1019
1064
|
f'"{_REQUEST_START}"',
|
|
1020
1065
|
_CONTENT_BLOCK_START,
|
|
1021
|
-
|
|
1066
|
+
_CONVERSATION[0],
|
|
1022
1067
|
),
|
|
1023
1068
|
),
|
|
1024
1069
|
]
|
|
@@ -145,7 +145,7 @@ def _ungrouped(arm: js.Node) -> js.Node | None:
|
|
|
145
145
|
|
|
146
146
|
def _reroute_grouping(source: Source, outcome: Outcome) -> Source:
|
|
147
147
|
"""Send finished thinking messages to the visible list, not the group."""
|
|
148
|
-
step = outcome.step("group-routing"
|
|
148
|
+
step = outcome.step("group-routing")
|
|
149
149
|
edits: list[Edit] = []
|
|
150
150
|
seen: set[int] = set()
|
|
151
151
|
|
|
@@ -255,8 +255,9 @@ def _thinking_inline(source: Source, _options: Options, outcome: Outcome) -> Sou
|
|
|
255
255
|
the value forced to ``!0`` here and the name the early return is keyed on
|
|
256
256
|
there, so neither rewrite is looking for a gate the other cannot see.
|
|
257
257
|
"""
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
outcome.declare(required=("group-routing", "null-guard", "renderer-props"))
|
|
259
|
+
guard = outcome.step("null-guard")
|
|
260
|
+
props = outcome.step("renderer-props")
|
|
260
261
|
source = _reroute_grouping(source, outcome)
|
|
261
262
|
edits: list[Edit] = []
|
|
262
263
|
|
|
@@ -439,8 +440,9 @@ def _whitelist(node: js.Node) -> js.Node | None:
|
|
|
439
440
|
|
|
440
441
|
def _max_effort(source: Source, _options: Options, outcome: Outcome) -> Source:
|
|
441
442
|
"""Let ``/effort max`` save as the default for new sessions."""
|
|
442
|
-
|
|
443
|
-
|
|
443
|
+
outcome.declare(required=("gate", "schema"))
|
|
444
|
+
gate = outcome.step("gate")
|
|
445
|
+
schema = outcome.step("schema")
|
|
444
446
|
edits: list[Edit] = []
|
|
445
447
|
seen: set[int] = set()
|
|
446
448
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|