A reviewer blocked the one-shot boolean access read the stop shortcut used:
the client should not expose a render-time access answer as a boolean API.
The Esc-Esc sequence submits its cancel again, and a Host refusal surfaces as
the composer failure the Stop button would have prevented. Send keeps the same
treatment: the access snapshot withholds the buttons, the Host refuses the
request, and the composer reports it.
The access snapshot still withholds the Send button, Stop, and file intake, and
the Esc-Esc stop path still reads its own answer before sending a cancel. A
keyboard submission, however, once again reaches the Host, which refuses it and
reports the failure to the composer; gating that path needs a larger redesign
of the composer keyboard face.
ui-chat goes back to origin/master as a whole package: the footer branch forks
through ctx.sessions.fork with its own atSeq and analytics again, and Workspace
navigation takes no ForkSessionOptions. The package is identical to
origin/master, which drops the served-page branch assertions and README lines
added on this branch; they pinned behavior master already has.
ui-workspace restores master's forkSession signature and implementation
(allowMigration only, increaseTitle: true, selection untouched) and drops the
ForkSessionOptions type. uiSession leaves the inject list because the removed
session/rename probe was its only consumer. The row fork keeps passing
{ allowMigration: false }, which master already does, so forking from a shared
page never starts source-format migration.
Regenerate the client API catalog, restore the README fork contract (fork does
not navigate; allowMigration carries migration permission), re-record the
translation pairing, and return the ui-chat unknown-cast baseline count to 2.
`increaseTitle` renames the child, not the source, and whoever may fork a Session also holds `session/rename` on the child it creates; the source's access answer therefore never decides the child's title. Remove the source-rename probe, the listed-branch wait, and its refresh-failure notice: `selected` opens the child immediately and `listed` leaves the selection alone. Update the public READMEs and the client API catalog.
Enter, Mod+Enter, and accelerated queue steering now read the same prompt
answer as the Send button, and the fixed Esc-Esc sequence reads the live
session/cancel answer before it consumes a press. The composer locks for a
denied prompt, the README states both paths, and tests pin each gesture.
Read the branch-naming answer through uiSession.access instead of the
cross-kind source, drop the unused openListedBranch method, give the branch
list refresh its own notice, and gate the search result restore control like
the archive row. The forkSession contract now states its navigation and throws
behaviour, present documents its 2x2 landing table, and the README describes
the real branch policy and row gating.
The Session kind owns its remoteAccess source, so other plugins get one
non-Hook read from this service instead of reading the source themselves.
Navigation uses it for the branch-naming answer.
The client inspect catalog follows the seam signatures, the unknown-cast
baseline drops the two casts this branch retired, and the README pairing
records match the merged prose.
Master's rows and menus carry no answer of their own, so the served-page seams
thread the Slot-standard access Hooks through the workspace browser and the
session actions: New Session, workspace rename/delete/reorder, and a Session's
rename, pin, archive, and reorder appear only while their own resource allows
them. The rows spec keeps master's pointer-armed rename helper and supplies the
allow-all answers as props.
- The Conversation binding tracking returns to master: the retainInfo subscription and the
plugin-owned binding effects are a separate fix, not a served-page seam.
- The deliverables opener drops the ownerHostRoutes parameter no caller passes any more.
- Assertions on existing connection and HMR behavior, the menu-open-state spec, and the
settings-general roster splice return to master.
- Workspace shortcuts no longer re-read Session access through source(); the Host refuses a
denied rename, fork, or archive.
- The sidebar New Session button, the window-chrome New Session control, and the expanded
brand shortcut render only while useRemoteAccess('session/create') allows it.
- The composer decides file intake from the Session-scope useAccess('fileUploads/upload')
answer, like Send and Stop. The optional fileIntake hook seat, the InputHub fileIntake
entries, and the ui-slots optional-hook typing are removed.
- The fixed Stop shortcut no longer re-reads session/cancel through a shared stop; the
button stays hidden by useAccess and the Host refuses a withdrawn cancel.
- uiConversation.noticeBranchRefusal, branch.ts, ForkRefusal, and message.branchRefused are
removed; a failed Chat branch leaves the source view unchanged, as on master.
The owner-baseline gating commits are superseded by master's whenAccess seams,
so the remaining records revert to the master shapes: the deliverables opener
takes no owner answer, the Japanese dictionaries carry the surviving copy, and
the test splice and owner assertions match the current services.
Read the slot injection through its real rest-parameter function type and invoke it directly, so the static gate sees no new assertion to unknown. The client inspect catalog's expected uiWorkspace signature follows the generated catalog after the fork options became ForkSessionOptions.
P1 keeps only the client/host extension seams; the requestId deduplication,
the cancel recheck, and the named fork child are deferred to a later change.
The package, its README pair, and the persistence schema lines return to the
master state, so the branch adds no session-controller behaviour.
Type the tracked binding disposers by their real shape and let a disposed
context's shared stop surface its own loud unavailable error instead of
reporting a withdrawn permission.
The button re-read the live session/cancel answer but the fixed shortcut
received the raw stop, so a withdrawal still sent Esc-Esc cancellation. Build
one gated stop used by both entry points and cover the shared gate.
A rejection may be null, undefined, or any non-object; reading rpcError through
it threw a TypeError and replaced the branch-refusal notice with a crash. Narrow
the value first and cover the non-object rejections.
The long-lived plugin effect wrapped every Session-scope effect, so a released
binding's Context stayed referenced until plugin unload. Track the binding-scope
effects in a map that the binding cleanup and the plugin unload both drain;
nothing long-lived closes over a released binding.
The recheck used allows(), the list-filter view that never sees call arguments,
so a policy narrowing cancel to args.request refused a legitimate caller as
access-withdrawn forever. Ask decide() with the principal and the request, keep
the policy's own refusal reason, and cover both an args-independent role and an
args-dependent condition.
A first attempt that materialized the child and then failed at attachSession
was reported as failed, but its retry adopted the stored child and returned
success before attaching it, leaving the child ungrouped. The adoption path now
completes the missing attach and reports the same session/workspace-attach-failed
if that still rejects.
The Gateway already admits each call; P1 no longer re-reads the caller's access
after prompt admission. Cancellation still refuses delivery when the caller
aborts during admission, and the requestId dedup/conflict, cancel recheck, and
fork naming behavior stay. The README pair follows.
The published UiWorkspace no longer requires openListedBranch: that wait is an
internal navigation helper, and the service forkSession takes the branch
boundary and presentation choice as optional inputs. The Chat branch surface
now supplies only its branch point and analytics and lets Workspace navigation
decide whether the child may be named and when it becomes presentable.
A refused branch now reports itself on the owner's own page: the Chat
injection writes the notice on the addressed Session and the composer bar
renders it as an alert, so the owner-visible change has an assertion at both
seams.
The composer hook source and the drop gate now resolve the same live intake
answer from InputHub, so the policy and its two readers cannot drift; the
caller keeps ownership of the rejection copy.
A bar registration that cannot prove live file-intake permission omits the
source; the bar then offers no file controls, matching the no-session
placeholder's fail-closed answer. PropsHooks keeps required hook seats intact
and lets an optional source leave its seat undefined.
The composer notice store has no production reader through the published
SessionInput seam: the bar renders it through ComposerBarInjected.hooks.notices
and the only other reader was a test. Leave the store on SessionInputShell,
which the hook wiring uses, and assert the bar's hook in the Chat spec so the
contract surface only publishes members with real consumers.
The model-visible catalog inherited two versions of the same JSDoc sentence,
one of which contradicted the other. Describe the blank reuse once: the first
reusable listed blank, the requested preset or its default, one shared attempt
per Workspace and preset. The generated client inspect catalog follows.
Close the Session Controller's remaining per-file coverage debt: a named
fork adopting the child its earlier attempt stored, refusing a name another
Session holds or one seeded at a different cut, and the named-child
observation failures; prompt admission sharing one in-flight requestId and
refusing a duplicate from another caller; the retry read from a drained
splice; the access-withdrawn and aborted-admission delivery paths; cancel
after the caller's grant is gone; and the Assistant settlement that appends
past the last entry. Test-only: no production behavior changed.
The Gateway admits a prompt before the controller sees it, so a binding narrowed while the prompt waited must not still deliver: the Remote method passes a predicate that re-reads the caller's access after every await and immediately before delivery, and cancel re-reads it in the same synchronous step. One requestId still produces one message; a retry from another principal is refused as session/request-conflict. Fork callers may name childSessionId so an interrupted attempt resumes the child it stored; any other occupant of that id is refused as session/fork-conflict. The agent-controller admission pipeline this previously built on is not part of the current master, so the prompt keeps the direct admission path and the tool-execution turn carrier is gone.
The Workspace tree gains the Host-published branch projection plus the
access-snapshot reads its rows, menu entries, and shortcuts consult; the
Conversation package moves branch handling into its own module and exposes the
input and slot declarations a page-local plugin composes. Chat, sidebar,
deliverables, and open-in-app publish the slot surfaces those readers fill, and
the document-relative route and HMR endpoint seams keep the served page's own
base.
* refactor(llm-deepseek): share the /models listing machinery
Move the account route's `/models` request and decoding, its
credential-scoped listing cache, and listed-catalog resolution with the
version gate into the shared DeepSeek Messages library, so a second
route can follow the same listing. Names drop the account prefix, the
cache takes each access's endpoint settings with the call, and it gains
`invalidate()` for a forced reload. `isBuiltInCatalog()` recognizes the
catalog an uncustomized `models` setting resolves to.
The account route keeps its behavior; only its imports and the
override diagnostic wording ("model listing") change.
* feat(llm-deepseek-api-key): follow the official /models listing
While `models` equals the built-in catalog and the Messages origin is
https://api.deepseek.com, `deepseek-official` serves the official
`GET /models` listing, authenticated with the API key in `x-api-key`.
Loading, refresh, fallback, merging, and the client-version gate match
the account route: the first access waits up to `modelsTimeoutMs`, a
listing older than `modelsMaxAgeMs` refreshes in the background on the
next access, a failure keeps the last good listing or the built-in
catalog, and listed models this build cannot serve are refused before
network I/O.
A customized `models` list replaces the catalog and requests nothing.
Switching between a customized catalog and the listing discards the
cached listing, so Restore defaults reloads it on the next access.
Suites that boot the route on the official endpoint now answer the
listing themselves instead of reaching the network.
* docs: describe the official route model listing
Document when `deepseek-official` follows the official `/models`
listing, its timing fields, customization precedence, and the forced
reload after Restore defaults in the API-key README, the shared
transport README, and the provider guide. Record the decision in a new
Agent Note, keep the account note's statement of where the shared
listing code lives current, and regenerate the configuration catalog.
* test(llm-deepseek-api-key): pin the once-per-listing hidden-entry diagnostic
* docs: regenerate module and event graphs for the shared listing
* fix(llm-deepseek-api-key): report credential failures on the model request
On the official endpoint, `connect` waited for the listing view, whose
credential lookup rethrew a malformed key or a credential-store failure
before the agent loop recorded the prompt. A failed lookup now resolves
the connection from the current listing view, so `resolveAuth` reports
the failure on the model request and the turn keeps the prompt and the
failed attempt. A caller abort still rejects.
* docs(llm-deepseek-api-key): name which hidden listing entries are logged
* test: keep unit suites off the official model listing
Suites that boot the DeepSeek routes with a dummy or inherited key now
request `GET https://api.deepseek.com/models` on the official endpoint.
A unit-lane setup file rejects that request at once, so such a suite
serves the built-in catalog instead of reaching production or waiting
out the listing deadline on a network that never answers; suites that
exercise the listing answer `fetch` themselves. Other requests pass
through unchanged.
* feat(llm-deepseek): read tool_update from the model listing
Both DeepSeek routes now take a listed model's Messages tool update mode
from `api_capabilities.anthropic_messages.tool_update`, alongside the
system prompt mode. `in-history` and `addition-only` replace the
bundled mode; any other value reads as `leading-only` and removes it.
An absent or `null` mode keeps the bundled one, so a listing that does
not declare the field leaves `deepseek-flash` on `addition-only`.
`system_prompt_update: null` likewise keeps the bundled mode now; it
previously read as `leading-only`, which a serializer writing empty
values as null would have turned into a cache-unfriendly downgrade.
* fix(llm-deepseek): sync the built-in context window with the official listing
The official `/models` listing declares a 1,048,576-token context window
for `deepseek-flash` and `deepseek-v4-pro`; the built-in catalog said
1,000,000, a decimal-versus-binary mismatch. Both built-in entries now
carry the listed value, so offline starts and listing failures size the
context the same way the listing does. The Models page shows the exact
count for these rows, since 1,048,576 has no decimal K/M spelling.
`defaultContextWindow`, the fallback for model ids the catalog does not
describe, stays 1,000,000.
* test(llm-deepseek-api-key): reject the stubbed listing request with an Error
* test(cli): keep the built SDK smoke off the official model listing
The SDK profile's initialize now resolves the deepseek-official catalog,
which requests GET https://api.deepseek.com/models on the official
endpoint. With the smoke's fixture key that request answers 401 and the
route warns on stderr, so the test's empty-stderr assertion failed in the
e2e and snapshots lanes. Point the run at a loopback mock and assert it
received no model call.
* test(llm-deepseek): compose the malformed-key turn without the native executor
release/dsh-0.2.1 predates @deepseek-ai/dsh-tool-executor-native; its tool
runtime executes tools itself. Compose the official-route turn the way
account-routing.spec.ts does on this branch.
* fix(llm-deepseek-api-key): gate nothing when the credential lookup fails
The listing cache switches scope only after it reads a credential, so a
failed lookup left `connect` serving the previous credential's listing.
After a key whose listing refuses a model was replaced by a malformed one,
`admit` refused that model with CLIENT_UPGRADE_REQUIRED before the turn
recorded the prompt, instead of `resolveAuth` reporting the credential
error on the request. Serve the built-in catalog without listing gates
when the lookup fails.
* test(llm-deepseek-api-key): pin the once-per-listing diagnostic across request preparation
The hidden-entry diagnostic test repeated the same discovery assertion to
prove a second pass reports nothing new, which read as a duplicate. Assert
the single warning after the request preparations instead; they resolve
the same listing, so dropping the per-listing deduplication still fails
the test.
* docs(llm-deepseek-api-key): note that a list matching an earlier built-in catalog counts as customized
Customization is judged against the running version's built-in catalog,
so a saved `models` list equal to an earlier version's catalog stops
following the listing once the built-in catalog changes, until Restore
defaults. Record the consequence in the Agent Note and the README's known
limitations.
* fix(ui): separate navigation pins from selection and show menu icons
* fix(ui): simplify More menus and fold conversation views on resize
* test(ui): fix navigation CI regressions and refresh catalogs
* refactor(ui): render sidebar panel icons directly
* fix(ui): keep Chat visible and stop persisting navigation pins
* refactor(ui): drop unreachable pin validation from in-memory stores
The branch merge from master brought four scenarios added after the prompt
refresh (background-wait-steer, session-resume, model-switch-compaction,
web-search-unconfigured); their system-prompt sidecars still show the
guidance without the restored sentence, so the keyless replay fails. Apply
the same sentence to those six expected lines.