packages/dsh-task-board
zhu1090093659/dsh-web侧边栏多列任务看板:卡片交给真实 DSH 智能体会话执行,支持 cron 定时(Host 侧到点执行,关浏览器也生效)。
在对话里划词引用进输入框(显示为「N 条注释」小框),或在侧栏一次性小窗里就这段内容提问(不留任何记录)。
English | 中文
Select text in the DSH Web UI conversation and either quote it into the composer or ask about it in a throwaway side panel — the two habits Codex users have.
Quote. Selecting text in the conversation (or anywhere outside an input) floats a small bar with
引用 / 侧栏提问. Quoting attaches an annotation to the composer instead of dumping the text
into it:
1 条注释 / 1 quote; hover it to list every annotation (numbered,
with the quoted text), remove one, or clear all with the ×;Ask aside. 侧栏提问 opens a right-sidebar tab pinned to the selected fragment. The tab is a
self-contained chat: each turn is one POST to a route the host half registers, which runs a single
tool-less ctx.llm.stream call. No session is created — no session log, no workspace entry, no
child session. The transcript lives in the tab component's state, so closing it discards everything.
Model and credentials still take the normal path (the selected session's current request config,
falling back to the default selection), so adapters, retries, credentials and metering behave exactly
as they do for an ordinary turn.
A quote has to reach the model without filling the input box, so it rides the composer's own
reference chip mechanism rather than the draft text. Each quote is one chip whose ref is the
quoted text, whose draft projection is a short marker, and whose model form comes from this
package's own codec:
ctx.inputTriggers.registerSource({
trigger: "@",
name: "select-ask",
candidates: async () => [], // no `@` menu group, no plain-text decoration
showGroupTitle: false,
codec: {
clipboardText: () => "[注释]", // what the draft text shows
serialize: (ref) => Promise.resolve("\n\n" + blockquote(ref) + "\n"), // what the model gets
},
});
Insertion and removal use the composer's scoped input events — slash/input-insert-reference to
place a chip, slash/input-consume-token with its span to delete one. Both are CAS'd against the
live draft revision, so a keystroke that lands first wins the race and the edit is retried; a chip's
detect offset is derived from the occurrence list (detectOffset = clipboardOffset − Σ(length−1) of
the chips before it), which keeps the arithmetic correct whether or not other plugins' chips sit in
the same draft.
Because the pill counts the chips in the draft (InputState.occurrences), the count, the hover
list and the model text read the same data and cannot disagree. A serialization failure blocks the
send (slash: no serializer for reference source …) instead of silently sending the marker text.
If no chip can be inserted at all (no session scope, or the revision keeps racing), the plugin degrades to appending the quote to the draft as text and says so on the console — ugly, never lost.
/dsh-select-ask/ask spends model quota, so it is guarded by isTrustedRequest(), a pure decision
over request headers with its own test (node --test test/trusted-request.test.mjs):
x-dsh-select-ask; everything else is refused. The host half injects
the token with webServer.tapIndex; the browser half reads it from
window.__DSH_SELECT_ASK_TOKEN__. A custom header also keeps cross-site form posts out.webServer.renderIndex (observed on DSH Desktop 2.0.15), so the token never reaches a page. Then
any request that declares a browser origin (Origin/Referer) must declare our origin, and a
request with no origin at all is let through — it is no stronger a caller than the rest of the
loopback surface a DSH profile already exposes.GET /dsh-select-ask/status reports which mode is in force (guard) plus the resolved model.
# from a session with Full access:
# plugin_manager action=install_bundle target=<this directory>
Reload the Web UI page afterwards so the new client bundle enters the boot graph. A host-half
change (host.js) needs a DSH restart: the host module generation is cached per process, and a row's
specifier cannot be re-imported in place.
Submissions to the awesome-dsh-plugin
catalog are one YAML entry per plugin and one file per pull request; this repo's entry is
submission/YizhouLouisLu__dsh-select-ask.yml.
Developed and verified against dsh core 0.2.0-rc.2 (DSH Desktop 2.0.15-beta.1). No engines.dsh
range is declared: only this version has actually been run.
Things that cost time while building this, written down because they are not obvious:
install_bundle on an already-installed bundle does
not re-read its cordis.patch.yml. To make an edited host half live: remove_bundle, then
install_bundle, with a row name that differs from the one already imported — or restart DSH.pkg/subpath fails with
failed to import here even though plain Node resolves it.pnpm run dev:web runs).ctx.inject(['webServer'], (scope) => scope.webServer…); ctx.get('webServer') alone does not
make ctx.webServer readable.projectUserText draws runs and turns only @[label](dsh-session:…) mentions, bare @tokens and
/commands into chips; Markdown links, HTML comments and the like are shown verbatim. So a plugin
cannot hide content inside a user message — that would need a structured annotation field on the
message, or the content has to travel outside it.[注释] is never sent as prose). Quotes already sent
are in the message and unaffected.label: "" plus appearance: "session";
without an appearance the chip would render a bare @).MIT — see LICENSE.