Promote develop to main for v0.3.0 - #45
Merged
Merged
Conversation
…preferred one
Every release published so far has told readers:
Windows binaries are signed through Azure Trusted Signing.
That was not true. This repository signs Windows with the organization Authenticode certificate — the
Azure steps skip, the PFX steps run. Checked against the published v0.2.0 run: `Sign with Azure Trusted
Signing` skipped, `Sign with the organization Authenticode certificate` succeeded, and the notes still
named Azure.
The workflow already forbids exactly this, in the comment above the outputs it carries to `publish`:
Notes that claim a signature the run did not produce are worse than no claim: a reader has no way to
tell, and the whole point of saying "signed" is that it can be relied on.
The mechanism was one output short of being able to honour it. `signed` is a boolean about SUCCESS, so it
cannot say which of the two configured methods produced the signature, and the notes filled that gap with
the preferred method rather than the used one. `sign-windows` now also exports `method`, `publish` receives
it, and the notes name what ran.
An unrecognised value is described without naming a method rather than guessed at — "signed, verify with
`signtool verify /pa` or against the provenance attestation" is vaguer but true, and a wrong provenance
claim is the worse failure because a reader cannot detect it.
v0.2.0's published notes were corrected in place, since the false claim is already out there.
Found while dispatching the release: the dispatch path itself was fine — repository id matches the pinned
guard, the workflow is active and present on the default branch, and a dry run signed and verified both
the binaries and the installer before the real publish.
…-method Name the signing method the release actually used
…cost
Two commands, `variants.paraphrase` and `variants.compare`, plus the route,
service and schema they need. Both are gated on the Redrob provider being
connected: the route answers 404 when it is not, so a user on a local runtime or
another vendor is told the feature is unavailable rather than watching a request
fail for reasons that look like their own.
THE COST IS ALWAYS SHOWN. One of these requests makes several charges, one per
model. A user who believes they made one request will be surprised by the bill
unless the surprise happens immediately, while they can still decide not to do it
again. So every result prints the model that actually answered -- `routedModel`,
which under `auto` is the only thing that names it -- with its own cost, its
latency, and the total.
`compare` offers the answers in a selection list with previews, and only the
pick enters the conversation. Session history cannot be pruned: `Revert.State`
rewinds to a point and restores FILES with it, and `fork` makes a new session,
so neither can remove "these two of three messages". An answer appended to try
it out would be permanent.
Driven by hand in a terminal, which is the only thing that found three of these:
- The result was a toast. The toast is absolutely positioned, capped at sixty
columns and dismisses on a timer, so it clipped the cost total and the whole
answer and kept only the first few slot rows -- the two things a reader most
needs were the two it dropped. Now a dialog, dismissed by the person reading
it.
- `compare`'s chooser never opened. `DialogAlert` calls `onConfirm` and then
clears the dialog stack itself, so a chooser pushed synchronously from that
callback is opened and immediately wiped. Deferred by a tick.
- Picking an answer appeared to do nothing. The insert was a `void`ed promise
with no catch, so its rejection was discarded. A user who has just been
charged for several models must be told when the thing they paid for failed
to land.
`CONSOLE_URL` is now resolved once, in the constants module, rather than at each
call site. A per-consumer `Flag.REDROB_CONSOLE_URL ?? CONSOLE_URL` made the
override HALF apply: the variants service honoured it while the session's own
inference path, the catalog fetch and the provider registration still went to
production. Pointing the CLI at a local console then produced one that answered
`/variants/paraphrase` locally and 401'd every chat turn against the real one --
a split that reads as a credential bug and is not. Measured after the change:
the session's own turn reaches the local console.
The HTTP client is provided inside the variants service instead of being
demanded from callers. The requirement was propagating out through the route
handler into the API's own requirement type and out to every entry point that
builds the API; `serve` failed to typecheck with `Type 'HttpClient' is not
assignable to type 'Service'`, which reads as a problem with `serve` and is not
one. The repository's usual shape is a service node with
`deps: () => [..., httpClient]`, sharing one client process-wide; these are two
plain functions, so they take their own. That is a real difference and is
recorded at the call site.
`console-key.ts` extracts the credential resolution both consumers need. There
are three origins and nothing bridges them -- `REDROB_API_KEY`, the Integration
store, `auth.json` -- with different shapes (`type:"key"` versus `type:"api"`).
Reading only some of them already shipped once as a bug: desktop-app users saw
six fallback models.
Also `loop.start` / `loop.stop`: a goal-driven autopilot. The first version
re-sent a fixed instruction, which cannot notice circling because the input
never changes. Each cycle now sends the goal, the ledger of approaches already
rejected, and an instruction to assess against the goal before taking the next
step. It stops on goal met, blocked, a repeated plan (three times, since two is
patience -- waiting on a build looks identical), a cycle backstop, or a session
failure. Driven by `session.idle` rather than a timer, because a timer fires
into a session that is still working and stacks turns.
The HttpApi exerciser gate fails on any route with no scenario, and it caught both of mine. Exercised through their NOT-CONNECTED path, which is the honest state in CI: there is no console credential, so the assertion is the 404 saying the Redrob provider is not connected. A scenario that needed a real key would either skip -- and this gate fails on skip, correctly, since a skipped route is an unexercised route -- or send a paid request to the live console on every run. 404 rather than 401 is the contract being pinned: nothing is configured to ask, so nothing refused us.
…autopilot Reach the console's multi-model endpoints, and print what they cost
A 429 and a 402 from the console arrived as prose. The 429 rendered as a message
with a countdown and no way to see the ceiling it hit; the 402 rendered as a
sentence in a red box, telling the user their budget was gone and leaving them to
find the page that raises it.
Split by what actually helps, because the two are not the same kind of problem:
- 429 goes through `retryable()` and gets the existing retry card, linking to
the console's limits page. Retrying IS the remedy here, and `Retry-After`
already paces it.
- 402 does NOT. The console answers 402 rather than 429 specifically so that
clients stop -- its own comment says retrying an out-of-credit workspace
"just turns one refusal into six" -- and a card that reads "Retrying in 4s"
over something that will never succeed is worse than a plain message. These
go through a new `blocking()` instead.
So `SessionStatus` gains a `blocked` variant: the turn ended, no retry will fix
it, but there is something to click. It is distinct from `retry`, which means a
retry is IN FLIGHT and is drawn with a spinner and a countdown, and distinct from
a plain session error, which travels as message text and therefore cannot carry a
link at all -- which is why the user was told "budget exhausted" and left to go
looking.
The two 402s are told apart by `code`, and they need different pages: a key over
its cap is fixed on the key, an empty balance by topping up. `code` is the only
thing that distinguishes them on the OpenAI-compatible path, because the
console's fuller refusal body does not survive that envelope (fixed console-side
in the same change set).
Everything is gated on the provider being `redrob`. `rate_limit_exceeded` and
`insufficient_quota` are OpenAI's generic codes, so any vendor may send one, and
offering a link to our console for somebody else's rate limit would send the user
to a page that cannot help them.
Turn the console's refusals into cards the user can act on
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes the tree v0.3.0 was cut from.
release.ymlpublishes fromdevelopand deliberately does not push tomain— it prints this link instead, so the promotion is a reviewable step rather than a silent one.Contents:
variants.paraphrase/variants.compare, the console-key extraction,REDROB_CONSOLE_URLapplying to the whole CLI, and the goal-driven autopilot loop.Release v0.3.0 is already published with 21 assets, Windows installer signed, notes verified against the run's own steps.