Repository navigation
feat(agent-launch): host-assigned caller identity and a launch record written when the tab exists (unification 1 of 3) - #24934
Conversation
Live QA: base (#24257
|
| Row | Base | This PR | Verdict |
|---|---|---|---|
| 1. Orca restarts while the launch waits for the agent to be ready (2 KB prompt, so it's pasted after readiness) | every retry gets agent_session_operation_unknown |
CLI retry gets the running agent: live handle, prompt unconfirmed. Phone without the capability gets agent_session_operation_unknown, as on base. The same device advertising it gets the running agent |
PASS: one agent per launch, nothing pasted after the restart, on both |
| 1b. Same, but the prompt rode the launch line | handed-to-terminal to every caller |
same | same as base |
| 2a. Restart after the final write | recorded answer, same live handle | same | PASS, same as base |
| 2b. Restart 0–1.6 s after the agent drew its ready screen | the still-waiting launch: unknown. The launch whose prompt was already pasted: handed-to-terminal |
still-waiting launch: unconfirmed (CLI), unknown (phone without the capability). Pasted launch: handed-to-terminal |
PASS. The "pasted but not yet recorded" moment was never hit (6 tries) |
| 3. Reply lost 3 times, same operation id retried | 1 agent per caller, same handle every time | same | PASS |
| 4. Pane closed after the restart, then retried | recorded handle comes back; reading it returns status:"exited" |
same | PASS, but it reads "exited", not "not found" as this body says |
| 5. Two paired devices + the CLI, same operation id; params claiming device A's identity | separate record and agent per caller; the claim is ignored | same | PASS |
| 6. First-launch admission time on a brand-new profile (median ms) | macOS 75 cold / 126 first real launch; Windows 58 / 162 | macOS 52 / 125; Windows 63 / 160 | Same within noise. With a 450-row ledger it's still under 110 ms. Admission doesn't explain the Windows +2.5 s first launch. |
| 7. Phone path | Only a scripted phone-shaped client, before and after a restart (rows 1–5). The phone app's UI was not driven | ||
| SSH | BLOCKED (no accepted key) |
Different from what the body says
- Handles survive a real restart, on both arms. After force-killing the app on macOS, every surviving pane kept its
term_…handle, and base's replay returned a live one. So looking the handle up again by pane key made no visible difference here. The tests model a restart as new handles; this setup didn't produce that. Not checked on Windows or SSH. - A pane that's gone reads back as
exited, not "not found". Both arms behave the same way, and it's still truthful.
What a user would notice (same on both arms)
After a restart during the readiness wait, the agent keeps running with no prompt, and the desktop shows nothing about it. On this PR, a CLI retry now says unconfirmed. In every run here the prompt really hadn't arrived.
Not verified
- A paste that landed before the final write.
- A final write that fails.
- The desktop identity: nothing on this stack calls it yet.
- Rows 1–5 on Windows or SSH.
- The phone app's UI.
- Real agents.
Machine state: the real config files are unchanged on Windows. On the M4Air, ~/.codex/config.toml changed once during the run (15:30). No Codex ran in the rig, and other sessions' Codex processes were live on the box. It was not touched.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Host-assigned caller identity — new
src/main/runtime/rpc/rpc-caller-identity.ts. The dispatcher stampscallerfrom the authenticated transport on both unary and streaming paths; the launch-ledger namespace (agentLaunchOperationCallerKey) and caller navigation read it instead ofclientKind/pairedDeviceId. The desktop IPC is admitted astrusted-local:desktop, which previously could not name itself. - A launch record written when the surface exists —
onSurfacePublishednow carries a complete provisionalAgentLaunchResult;settledAtCreationwriteshanded-to-terminal(prompt rode the launch command),not-delivered(draft), orunconfirmed(submit still owed). Admission exposesrecordbesidesettle, fired un-awaited but ordered by the store's serialized transactions. - Replay presentation — an
unconfirmedprompt reaches only callers advertisingagent.launch.prompt-unconfirmed.v1(the desktop; the CLI is assumed to read it); every other caller keepsagent_session_operation_unknown, nevernot-delivered. A replayed terminal handle is re-derived from its pane key, falling back to the recorded handle. - Record-store slot — the ledger opens on its own without the chat host; the host is later built on the same store;
stopcloses it exactly once. - Tests — new restart-replay and slot suites cover host death mid-paste and after the paste, a failed first/final write, spoofed identity in params, and the desktop's capability path.
ℹ️ The slot header documents two quit-phase edges; a third interleaving is latent
Technical details
# Slot release during `stop`'s await
## Affected sites
- `src/main/runtime/agent-session-record-store-slot.ts:9-12` — header documents (a) a failed host teardown leaving a store open in an emptied slot and (b) a store opened after stop. It does not note a third: an admission or `ensureStructuredAgentSessionHost` that begins during `stop`'s `await pending` still sees the old `opening`, gets the old store, and (for install) can build a host that `stop`'s `outstanding` snapshot does not contain.
- `src/main/runtime/structured-agent-session-runtime.ts:188-204` — `stop` captures `installing`/`pendingTeardown` before awaiting, then releases the slot. A runtime registered after that snapshot is invisible to the identity guard, so the store can be closed under a live host or left open until process exit.
## Required outcome
- No behavior change required for this PR: `stopStructuredAgentSessionRuntime` has a single production caller (`main-process-quit.ts`), so the interleaving is only reachable once quit has begun, like the two edges already documented.
- If `stop` ever gains another caller, either serialize it (re-entrancy flag) or re-check the slot/registry after the await.
## Open questions for the human
- Is it worth documenting this third quit-phase interleaving alongside the existing two, so a future caller knows the invariant?deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior Pullfrog review at 39d15d5, the branch absorbed its base branch's new head in three sync merges (13de37c8, de6df623, c153690). The only authored change in that delta is a capability-list reconciliation:
- Reconciled the desktop capability test with main's restructure — main's #24204 now advertises the structured-chat capabilities to paired hosts, so
LOCAL_ONLY_BY_DECISIONindesktop-renderer-runtime-capabilities.test.tswas reduced to just this branch'sAGENT_LAUNCH_PROMPT_UNCONFIRMED_RUNTIME_CAPABILITY, and the local-only assertion now pins exactly that (the production capability list is unchanged since the prior review). - Absorbed base/main changes — the merges bring in the base branch's worker-brief/Grok readiness work and main's
agent.launchopencode-model guard,getTitleDisplayClear, and error-codepropagation. Per-filegit diffagainst the prior review confirms the launch-record/identity production code (rpc-caller-identity.ts,agent-session-record-store-slot.ts,agent-launch-replay.ts,structured-agent-session-runtime.ts,agent-launch.ts,agent-launch-executor.ts) was not authored by these merges and merged cleanly.
ℹ️ The one red check is inherited from main, not this PR
static analysis and typecheck fails at verify:localization-coverage, flagging src/renderer/src/lib/worktree-creation-hook-preparation.ts:26 and :51 — a local helper named confirm trips the checker on its 'setup'/'vmRecipe' arguments. That file is byte-identical on the base tip (f9bbc16b4b) and main (40f9e7a3fcd) and is not touched by this PR; the regression comes from main's #25472. It needs clearing on main or the base branch before this PR's verify can go green.
Technical details
# Localization-coverage false positive inherited from main
## Affected sites
- `src/renderer/src/lib/worktree-creation-hook-preparation.ts:26` — `await confirm('setup')`
- `src/renderer/src/lib/worktree-creation-hook-preparation.ts:51` — `await confirm('vmRecipe')`
- The local `confirm` helper is defined at line 17; its name collides with the checker's `USER_VISIBLE_FUNCTION_NAMES`, so the non-copy literal args are flagged.
- `config/localization-coverage-allowlist.json` has no entry for this file, on the base tip or on `main`.
## Required outcome
- No change required in this PR. Renaming the local helper (or adding a reviewed allowlist entry) on main/base clears `verify:localization-coverage`.
## Open questions for the human
- Should the fix land on main (#25472's regression) or on the base branch `brennanb2025/agent-launch-host-prompt-delivery` so this PR can go green?deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…o the stack is one chain #24934 was stacked on #24257; it now sits on #24081 (24257 -> 23962 -> 24080 -> 24081 -> 24934). The prompt-delivery table keeps #24080's argv row and this branch's unconfirmed row. The restart replay test pins its stub to the paste path, since #24080's fixture now reports an offered prompt carried unless told otherwise.
… written when the surface exists Step 1 of the agent-launch unification, on main. - The dispatcher stamps every request's caller from what its connection proved (runtime socket: the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it. - The launch record is written twice: once when the tab exists (what creation settled: on the launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is known. A restart in between finds the running agent instead of answering "unknown". - A replay re-derives its terminal handle from the pane key in the running host, and shows `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1. - The record store opens in its own slot, without building the chat host; the chat host is built on that same store. Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the `unconfirmed` row), and main's journal-database open in install(), which now goes through the record-store slot.
7d98ac4 to
82544cb
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (1)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughAgent launches now record a complete creation-time result before prompt delivery. Replay uses transport-assigned caller identity and can report an Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to No merge-blocking issue was identified; the change is ready for normal merge checks. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ed for it An agent.launch now shows its terminal tab before admission and spawn, in the requested placement (group and/or anchor tab), and the pane attaches to the agent as soon as it runs. A pane whose agent can't start, or whose start can't be confirmed, says so instead of becoming a plain shell, and keeps saying so across restarts. A user's close of the tab or its pane during the launch stops it and answers agent_launch_tab_closed. Whose view moves is unchanged from main for every caller. Rebuilt on main (with #24934) from the previous branch head 8f62858.
|
Ready for review: live QA passed, and CI is green on This is step 1 of 3 of the agent-launch unification, built directly on main (#24934, then #25430, then #25624). It doesn't depend on #24080 or #24081. Prompt delivery is unchanged from main: #24257's rule puts a short single line on the agent's command line and pastes anything else once the agent is proven in front. This PR only records what happened. Live QA ran on a macOS box (main R1: the host restarts mid-paste. The prompt was 2,064 B, so it is pasted after ready on both arms. The host was killed about 7.7 s into the readiness wait, then relaunched, and the same operation ids were replayed.
R2: identity can't be spoofed. Two paired devices and the CLI sent the same operation id. Then two of them sent params claiming to be the third ( Nothing was found worse than main. Not verified live:
CI on |
…t-launch-instant-tab Conflicts resolved against #24934's rebuild (82544cb), which this branch was built on, so each keeps main's squashed #24934 plus this branch's change. agent-launch-floating-workspace.test.ts takes main's #22302 routing (a structured preference opens a Codex chat) with this branch's host-derived viewMode on the terminal branch. The record-store slot test gets the resolveLaunchArgs dep #25721 made required, the same line as main's fix.
…ed (#25430) * feat(agent-launch): host-assigned caller identity and a launch record written when the surface exists Step 1 of the agent-launch unification, on main. - The dispatcher stamps every request's caller from what its connection proved (runtime socket: the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it. - The launch record is written twice: once when the tab exists (what creation settled: on the launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is known. A restart in between finds the running agent instead of answering "unknown". - A replay re-derives its terminal handle from the pane key in the running host, and shows `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1. - The record store opens in its own slot, without building the chat host; the chat host is built on that same store. Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the `unconfirmed` row), and main's journal-database open in install(), which now goes through the record-store slot. * feat(agent-launch): show an agent's tab at once, where the caller asked for it An agent.launch now shows its terminal tab before admission and spawn, in the requested placement (group and/or anchor tab), and the pane attaches to the agent as soon as it runs. A pane whose agent can't start, or whose start can't be confirmed, says so instead of becoming a plain shell, and keeps saying so across restarts. A user's close of the tab or its pane during the launch stops it and answers agent_launch_tab_closed. Whose view moves is unchanged from main for every caller. Rebuilt on main (with #24934) from the previous branch head 8f62858. * style(runtime): one-line the tab-order map so the headless browser-tabs runtime stays under its line limit The merge of main (#25724's emitMobileSessionTabsSnapshot metadata) plus this branch's close mark put the file one line over max-lines. Formatting only.
…agent.launch (#25624) * feat(agent-launch): host-assigned caller identity and a launch record written when the surface exists Step 1 of the agent-launch unification, on main. - The dispatcher stamps every request's caller from what its connection proved (runtime socket: the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it. - The launch record is written twice: once when the tab exists (what creation settled: on the launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is known. A restart in between finds the running agent instead of answering "unknown". - A replay re-derives its terminal handle from the pane key in the running host, and shows `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1. - The record store opens in its own slot, without building the chat host; the chat host is built on that same store. Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the `unconfirmed` row), and main's journal-database open in install(), which now goes through the record-store slot. * feat(agent-launch): show an agent's tab at once, where the caller asked for it An agent.launch now shows its terminal tab before admission and spawn, in the requested placement (group and/or anchor tab), and the pane attaches to the agent as soon as it runs. A pane whose agent can't start, or whose start can't be confirmed, says so instead of becoming a plain shell, and keeps saying so across restarts. A user's close of the tab or its pane during the launch stops it and answers agent_launch_tab_closed. Whose view moves is unchanged from main for every caller. Rebuilt on main (with #24934) from the previous branch head 8f62858. * feat(agent-launch): the desktop AI buttons start their agent through agent.launch Part 3 of 3 of the agent-launch unification, on main. The AI buttons (Fix checks, the source-control actions, commit/push recovery, Explain commit, notes and annotation sends, session continuation) started their agent from the window. They now ask the host's agent.launch to start it, with no prompt, into the tab the window makes at the click, under one operation id per click; the window's pane waits for the host's agent instead of spawning a shell. The window then pastes the prompt with main's own paste, moved out verbatim and started only once the host's agent holds the tab, so delivery and follow-ups are exactly main's. A typed new-tab prompt and launches while chat is the default keep main's path. The source-control dialog drops its launch-command preview, which the host builds now. * style(runtime): one-line the tab-order map so the headless browser-tabs runtime stays under its line limit The merge of main (#25724's emitMobileSessionTabsSnapshot metadata) plus this branch's close mark put the file one line over max-lines. Formatting only. * fix(agent-launch): watch an AI button's agent for readiness from its first output The window started watching for the agent's ready signal only after the host answered agent.launch, so an agent that had already turned on bracketed paste by then was never seen ready and its prompt waited out the readiness budget. Readiness is now watched from the moment the tab's terminal exists, as main watches it; the paste is written, and the chat copy seeded, only once the host says its agent started in that tab.

ELI5
When you ask Orca to start an agent with a prompt, Orca writes down "I'm starting this". If the reply gets lost, asking again gives back the same agent instead of starting a second one.
Until now Orca only wrote down the result at the very end, after the prompt was delivered. If Orca restarted while the new agent was still starting, the note said "unknown". The phone then showed "Couldn't confirm the agent started", even though the agent was running.
Now Orca writes the note twice: once the moment the agent's tab exists, and again once the prompt's fate is known. A restart in between finds the running agent. The note says the prompt is "unconfirmed" rather than "not sent", because Orca can't tell whether a paste cut off by the restart arrived, and "not sent" would invite you to send it twice.
The desktop app itself could not use this protection at all, because Orca had no way to name it as a caller. Now Orca gives every caller an identity based on how it is connected, and the desktop gets one too.
What Changed
Before: if the host restarted while a launch waited for the agent to be ready (the prompt waits up to a minute before it is pasted), the launch was recorded as "unknown". The agent kept running in its terminal, but every retry from the phone was refused, and the phone said "Couldn't confirm the agent started".
After: the record already holds the running agent. What a retry gets depends on the caller:
unconfirmed: don't resend.agent_session_operation_unknownanswer as before. It is never told "not delivered", which would invite a duplicate turn. This PR adds no new on-screen notice.Before: a replayed terminal launch carried the recorded handle (
term_…). Usually that handle is still good after a restart: the local terminal daemon and the SSH relay both keep each PTY's handle, and the restarted app takes it back. But it doesn't always take it back. It drops the old handle when the PTY's process was replaced, and it keeps a different handle when it had already issued one for that pane before it read the daemon's list. In those cases the recorded handle points at nothing, even though the pane is still running.After: a replay looks the pane up by its recorded pane key in the running host and returns the handle that host uses for it now. Where the handle survived, which is every live run on macOS, that is the same handle. If the pane is gone, the recorded handle comes back, and
terminal.readon it reports the terminal asexited, which is true. Shipped phones reject a reply with no handle, so it can't be left out.Before: if the final write failed (database busy, disk full), the row kept whatever the first write said. With "not delivered" that told a retrying phone to resend a prompt that had already arrived.
After: the row keeps
unconfirmed, answered per caller as above.Before: a desktop launch naming an operation id would be refused with
agent_session_identity_required, because the desktop window arrives without a paired device. Nothing sends one yet; #25624 (step 3 of the unification) does, for the desktop AI buttons.After: the desktop is admitted under its own identity and gets the same "retry never starts a second agent" guarantee as the phone.
Before: admitting any replay-safe launch, even a plain terminal one, first built the whole chat host (Codex and Claude adapters, the host, the model catalog) just to reach the launch record.
After: admission opens only the record store. The chat host, when something needs it, is built on that same store, so there is still exactly one copy of it.
Mechanism, in four parts:
calleron every request from what the connection proved: the runtime socket is the local CLI, the desktop's IPC channel is the desktop, a paired socket is its device. Request params never set it. A connection that declares a client but can't name it gets no identity, and replay safety refuses it as before. The launch record's namespace and "whose screen moves" both read this one identity.handed-to-terminalif the prompt rode the launch command,not-deliveredfor a draft (the host never delivers drafts), andunconfirmedfor a submit still owed.unconfirmedprompt reaches only a caller that advertisesagent.launch.prompt-unconfirmed.v1; every other caller is refused withagent_session_operation_unknown.unconfirmed.What a retry answers, by where the host stopped:
unconfirmed, to a caller that reads ithanded-to-terminal, to everyoneWire:
unconfirmed, sent only to callers that advertise the new capabilityagent.launch.prompt-unconfirmed.v1. Hosts advertise it too.agent_session_operation_unknown, the same answer it gave before.Why
The common pattern:
This PR moves the launch record and the caller identity onto that pattern.
Alternatives considered:
not-delivered(this PR's first version): rejected, because a host that stopped mid-paste can't know that. Today's phone app reads "not delivered" as "prompt wasn't sent" and offers to resend, which can make a duplicate turn.unconfirmedto every caller: rejected. Shipped phones validate the prompt word and would reject the whole reply. They get the answer they always got for this state instead.How this differs from the common pattern:
unconfirmedget the running agent after a restart mid-delivery. Today's phone app keeps getting "unknown". It gets the new answer once it advertises the capability and has UI for it, which is not part of this plan.reuseTerminal) replays the handle its caller sent, since that launch minted no pane to look up.unconfirmed. A new transport that forgets to stamp its caller would inherit it. Unchanged from the base, but this PR makes it the identity model. Removed before plugins ship (plan §4): the runtime socket stampslocal-cliitself, and no stamp means no identity.agentSession.*still keys phones by their rotating credential rather than this identity. Moving it re-partitions rows already saved on disk, so it needs a migration. Removed by anagentSession.*caller-key migration PR after the unification; it has no ticket yet.Plan deviations for this step:
agent.launchcaller until then.Where this PR sits. This is step 1 of the agent-launch unification, built directly on main: every way of starting an agent goes through one host launch point. The steps:
agent.launch. Note for that step, not built here:This PR does not depend on #24080 (the first prompt on the agent's command line) or #24081 (worker briefs on the launch line). Both are parked and come back later as their own PRs on top. A launch delivers its prompt exactly as main does today (#24257's rule: a short single line rides the command line, anything else is pasted once the agent is proven in front), and this PR only records it.
Scope: the host's launch record, the caller identity, how a replay is presented, and where the record store opens. No renderer change: the desktop does not call
agent.launchuntil #25624.Linked Issue
Part of the agent launch plan; no separate issue.
Visual Proof
N/A: no UI changes. What changes is the answer a retried launch gets after a host restart, which the tests below cover.
Testing
Rebuilt on main (2026-10-06). This PR was previously stacked on #24081. It is now this PR's own changes only (the 4 commits b9adf88, 0d7d9b5, c83f44d and 39d15d5, 32 files) applied fresh to main 842c6c6. Two conflicts, both from main's own changes:
agent-launch-prompt-delivery.ts: the doc table keeps main's "line fits" row and adds theunconfirmedrow.structured-agent-session-runtime.ts: main now opens the journal database ininstall(). The chat host now takes the record store, and the journal database it sits on, from the slot, which opens it with main's ownopenStructuredAgentSessionJournalDatabase. A failed install leaves the store to the slot, which the runtime's stop closes, as before.Checks on the rebuild (82544cb):
tc:node,tc:webandtc:clipass.tests/e2e/cross-version-wire: 404 files, 3,668 passed, 3 skipped by their own guards, 0 failed.The live QA below ran on the stacked base. On main, more prompts are pasted (anything over 512 bytes), so the restart-mid-paste row is being re-run on this rebuild.
Live QA, base vs this PR, on dev builds with stub agents (macOS; admission timing also on Windows; details in #24934 (comment)):
unconfirmed. A phone-shaped caller without the new capability getsagent_session_operation_unknown, as on the base. No second agent, and nothing typed after the restart. On the base every caller got "unknown".exited.New tests run against the real on-disk launch record. A "restart" means the store is reopened from disk and a new runtime takes over. The tests make that runtime issue a new handle for the surviving pane, which is the case where it can't take the old one back.
unconfirmedgets the running agent under the new host's handle, with the promptunconfirmed, and nothing new startsagent_session_operation_unknown, as before; the same row still answers the upgraded callerunconfirmed, or "unknown"; no second pastehanded-to-terminal, a retry isunconfirmedor "unknown", never "not delivered"unconfirmedafter a restart, through its real IPC capability listtrusted-local:desktopand replayunconfirmedand a draftnot-deliveredEach key test was ablated in production code and goes red. Round 2:
not-delivered(this PR's first version) → 8 redunconfirmedsent to every caller → 3 redRound 1:
Measured admission cost on this Mac, fresh profile, 15 rounds × 3 runs (medians):
Checks run:
orca-runtime.test.tsandtests/e2e/cross-version-wire. 399 files: 4,980 passed, 4 skipped, 0 failed.tscpasses, and the tests-typecheck ratchet is OKtc:node,tc:cliandtc:webpass.verifybecause two mobile suites still put the record store inside the chat host. That was the test harness, not the phone:runtime-rpc-websocket-dispatch.ts), so the record key is the same device id as before this PR;AI Disclosure
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Author: @BrennanKB5
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)