Replies: 2 comments 2 replies
|
Related real-world case from the Web surface (0.1.5-rc.2) — exactly the seam this proposal addresses. Symptom: with Repro (same session, same day, 3 attempts):
Mechanism (mirrors the waterfall semantics described above): client plugin This is the opposite end of the same gap as #2544:
In neither state can the outside tell the two apart, and in both the user never learns afterwards that a question was asked. Suggestion: when an answerer declines, being able to tell "no client at all" apart from "a client is online but not on this session" would let the latter keep the request pending (and/or show a visible "1 question waiting" affordance) instead of hard-failing the agent's tool call. Making the Web session view an explicitly registered surface — rather than relying on |
|
Happy to help. Count me in for the web-side verification once the upstream PR exists — I can test on both the desktop Electron shell and a plain browser tab, and report back with exact outcomes (no-client vs. online-but-elsewhere). To sharpen the e2e assertion, here is the exact condition I can reproduce deterministically:
So the assertion is roughly: same request + same online client + different viewed session ⇒ must not fail closed. Happy to also capture CDP traces / main-thread metrics if useful, though this path is a routing decline rather than a perf problem. +1 on the split decline semantics — and the "1 question waiting" affordance is the piece users actually notice: in my case neither the agent nor I could tell afterwards that a question had ever been asked; the tool call just failed. Ping me when the PR is up (or drop the branch name here) and I'll test it. |
Uh oh!
There was an error while loading. Please reload this page.
Summary
v0.1.2-alpha reworked the ask-user answer path: the rc-era single-provider slot is gone, and answerers now compose on the agent-scoped
'user-questions/request'cordis waterfall (@deepseek-ai/dsh-user-questions) — return a value to answer, callnext()to delegate,NO_PROVIDERwhen nobody accepts. That solves coexistence: two UIs in one profile no longer fight over a slot.What it still cannot express is simultaneity. The waterfall is a queue, not a race, and registration order decides who sees the question first. This proposal asks for a first-party multi-surface routing capability on top of that waterfall — fan-out, first-answer-wins, loser dismissal — and offers a shipped, MIT-licensed reference implementation ready to upstream once external PRs are accepted.
The gap, concretely
Consider the common remote-attach deployment (the phone-remote threads #2250 / #2245 / #2097 describe the demand): one dsh process, one live session, two interaction surfaces — a desktop TUI and an IM card channel (Feishu/WeChat/Telegram-style). The human answers wherever they happen to be. Queue semantics fail this in two ways:
What the scenario needs is four primitives the waterfall doesn't carry:
Reference implementation: dsh-ask-router (shipped)
@aiwayds/dsh-ask-router0.2.0 (npm, MIT; repo: https://github.com/fan56/dsh-ask-router) implements exactly that protocol asctx.askSurfaces:{ name, claim(request), ask(request), settled?(request, by) };settled(request, winnerName)→ abort rejects everywhere via the request's own signal;claimclaims nothing, a throwingsettlednever breaks the winner, the outer promise rejects only when every target failed.It speaks both host eras: on rc (≤0.1.1) it owns the single provider slot and multiplexes it (the only way two UIs can coexist at all there); on alpha it registers as exactly one waterfall answerer — with zero registered surfaces it delegates via
next(), stepping aside so a co-present native answerer (e.g. the web UI) serves, or the request failsNO_PROVIDERupstream untouched. Live-verified on an rc host with the dual-surface pair (desktop TUI panel + Feishu interactive card on the same live session, first-answer-wins across a phone and a terminal); the alpha adapter is unit-tested, including the bare-registrynext()delegation.Why there is no conflict with the alpha mechanics
ask()enforces upstream of the waterfall —CALLER_NOT_LIVE/DELEGATED_CALLERidentity guards,BAD_INTENTvalidation, abort mapping — is untouched; the router sits strictly downstream and reuses the same error taxonomy (NO_PROVIDER,ASK_ABORTED).intentif it recognises the tag, renders the generic option list otherwise — same answer fields either way.Request
Two shapes upstream could take, in order of preference:
dsh-user-questions— a registry (e.g.ctx.askSurfacesor an equivalent) plus the fan-out / first-answer-wins / settled protocol as an official multi-surface answerer, so any IM/mobile channel plugin can register as a surface without each rebuilding the race and dismissal logic (and its races —Promise.racesettlement order and pre-aborted-signal edge cases are exactly where a naive implementation breaks).next()delegation is the intended way for a router-style answerer to compose, and what a well-behaved multi-surface layer owes its losers (dismissal) and the host (one slot, taxonomy-preserving errors) — so plugin authors implement it safely.Until external PRs are accepted, this discussion can serve as the tracking thread; the reference implementation is ready to convert as-is.
All reactions