Repository navigation
Conversation
…e engine's own key
…redrob-session to the console
Author
|
Added They catch each bug. With
|
This was referenced Oct 9, 2026
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.
What this changes
Adds
POST /api/insights/sessions. A product hands the engine a batch of labeled AI work sessions, and the engine posts it to the Redrob Console's insights using the engine's own Redrob key. The product gets back the Console's{ accepted, updated, rejected }.It's for Redrob Office, whose AGENTS.md rule is "Office never holds a provider key". This keeps that rule: the key never leaves the engine.
${CONSOLE_URL}/insights/sessions, the same constant every Console call here uses, including its developer override. The route takes no URL, and an extraurlfield in the body is ignored.ConsoleKey.resolve()(environment, then the credential store, thenauth.json), as the variants and chat-completions routes do.404 ProviderNotFoundErrorwhen the engine has no Redrob key;401for a refused or over-budget key;400for a refused batch, carrying the Console's message (NestJS's list form included);503for 429, 5xx or an unreachable Console.It follows the variants route's layout:
schema/src/insights.ts,core/src/insights.ts,protocol/src/groups/insights.ts,server/src/handlers/insights.ts, plus registration, the regenerated client, and a section indocs/LOCAL-ENGINE-API.md.Also in this PR:
/v1/chat/completionsworks, and carries the session idOffice's next step is to send its chat through the engine instead of calling Console with a key it holds. On
developthat route does not work:Service not found: @redrob/LLMClient. Neither server assembly provided the LLM client. It now does, next tohttpClient(packages/redrob/.../httpapi/server.tsandpackages/server/src/routes.ts).200(Content-Length: 0).LLM.streamlooks the client up when the body starts, after the handler has returned. The handler now takes the client itself and callsclient.stream.It also forwards
x-redrob-session, so Console can join a labeled session to what its requests cost. Only for the Redrob provider, and only in Console's own id format (1–128 ofA-Za-z0-9._:-), so it is an id, never text, and never goes to another vendor.Why
Office needs to send the labels its sessions get on the person's machine to the Console's insights, with the person's key. The design discussion is in redrob-cowork#149.
GET /providerwould mean Office holds it in memory every time it sends. That's what Office'sEngineIntegrationClientwas built to rule out: "There is no read path for the key itself. A caller that cannot read a key cannot log it, sync it, or leak it."How it was verified
bun test test/insights.test.ts(packages/core): exit 0, 4 pass. A stand-in Console records what it receives:Authorization: Bearer …tohttps://console.redrob.ai/api/backend/v1/insights/sessions, with the batch as sent, and appears nowhere in the result;The real engine, end to end.
bun run ./src/index.ts serve, with a fake Console behindREDROB_CONSOLE_URL, driven with curl:404 ProviderNotFoundError401200 {"accepted":2,"updated":0,"rejected":[]}summaryfield400 "sessions.0.summary is not a field"(the Console's message)400"url": "https://evil.test/collect"The Console received
Bearer rrk_e2e_secretwith onlysessionsin each body. The key appeared in 0 replies and 0 engine log lines.test/server/chat-completion-session-header.test.ts(packages/redrob): 3 pass. Forwarded in the id format to Redrob; dropped when it is text, too long or absent; never sent toopenai/…orollama/….Real engine, chat end to end, with a fake Console answering SSE behind
REDROB_CONSOLE_URLandREDROB_API_KEYset:Service not found: @redrob/LLMClient, and the Console was never called;200with the completion; streamed200withcontent: "hi",finish_reason: "stop", then[DONE];x-redrob-session: of_0123abcdandof_stream1where they were sent, nothing for an invalid or absent one, andBearer rrk_chateach time.bun run test:httpapi(packages/redrob): after the chat fix, all three modes exit 0 (216 pass each). Earlier runs: the new route passes in all three modes, through its not-connected path, as the variants are exercised. The command still exits 1, becausesession.shelltimes out in--mode effect. That also happens ondevelop(baseline--mode effect: exit 1, the samesession.shellfailure). Coverage and auth modes: 216 pass, 0 fail, 0 missing.bun typecheck(root, turbo): exit 0, 18/18 tasks.bun run check:generated(packages/client): exit 0 after committing the regenerated client.bun test(packages/core): 1037 pass, 3 fail. The 3 also fail ondevelophere, because this sandbox runs as root (util.flockandutil.effect-flock"unwritable lock roots", andProjectV2.resolvessh/https remotes).bun install --frozen-lockfilewith the pinned bun 1.3.14: exit 0. The pre-push hook ran.Anything a reviewer should be wary of
packages/sdk/js/src/v2/gen). Regenerating it adds about 650 lines, most of them routes that are missing there already (the browser-request routes, for example). CI doesn't check it, and mixing that catch-up into this PR would hide the change. It's worth doing as its own change.