@yadsh/dsh-qa-surface 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +162 -14
  3. package/SPEC-dsh-qa-surface.md +600 -96
  4. package/capability-policy.json +5 -0
  5. package/compatibility.json +7 -1
  6. package/cordis.patch.yml +11 -0
  7. package/docs/ARCHITECTURE.md +49 -7
  8. package/docs/COMPATIBILITY.md +5 -4
  9. package/docs/CONFIGURATION.md +96 -2
  10. package/lib/attestation.js +10 -0
  11. package/lib/attestation.js.map +1 -0
  12. package/lib/client/QaConfigController.js +111 -4
  13. package/lib/client/QaConfigController.js.map +1 -1
  14. package/lib/client/QaSessionController.js +501 -119
  15. package/lib/client/QaSessionController.js.map +1 -1
  16. package/lib/client/QaSurface.js +137 -20
  17. package/lib/client/QaSurface.js.map +1 -1
  18. package/lib/client/QaTranscriptAdapter.js +534 -45
  19. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  20. package/lib/client/attestation.js +60 -0
  21. package/lib/client/attestation.js.map +1 -0
  22. package/lib/client/chat-index.js +86 -0
  23. package/lib/client/chat-index.js.map +1 -0
  24. package/lib/client/components/Markdown.js +75 -5
  25. package/lib/client/components/Markdown.js.map +1 -1
  26. package/lib/client/components/QaAgentsDrawer.js +40 -0
  27. package/lib/client/components/QaAgentsDrawer.js.map +1 -0
  28. package/lib/client/components/QaComposer.js +132 -15
  29. package/lib/client/components/QaComposer.js.map +1 -1
  30. package/lib/client/components/QaMessage.js +184 -10
  31. package/lib/client/components/QaMessage.js.map +1 -1
  32. package/lib/client/components/QaSidebar.js +124 -0
  33. package/lib/client/components/QaSidebar.js.map +1 -0
  34. package/lib/client/components/QaSourcesDrawer.js +46 -0
  35. package/lib/client/components/QaSourcesDrawer.js.map +1 -0
  36. package/lib/client/components/QaWorkGroup.js +135 -0
  37. package/lib/client/components/QaWorkGroup.js.map +1 -0
  38. package/lib/client/components/VariantSwitcher.js +29 -0
  39. package/lib/client/components/VariantSwitcher.js.map +1 -0
  40. package/lib/client/components/format.js +48 -0
  41. package/lib/client/components/format.js.map +1 -0
  42. package/lib/client/index.js +77 -34
  43. package/lib/client/index.js.map +1 -1
  44. package/lib/client/styles.js +308 -43
  45. package/lib/client/styles.js.map +1 -1
  46. package/lib/client/types.js +13 -0
  47. package/lib/client/types.js.map +1 -0
  48. package/lib/client/wait-for.js +49 -0
  49. package/lib/client/wait-for.js.map +1 -0
  50. package/lib/client.js +8228 -383
  51. package/lib/client.js.map +1 -1
  52. package/lib/config.js +61 -9
  53. package/lib/config.js.map +1 -1
  54. package/lib/index.js +166 -70
  55. package/lib/index.js.map +1 -1
  56. package/lib/lockdown-policy.js +20 -0
  57. package/lib/lockdown-policy.js.map +1 -0
  58. package/lib/resolve-config.js +122 -10
  59. package/lib/resolve-config.js.map +1 -1
  60. package/lib/secure-session.js +170 -0
  61. package/lib/secure-session.js.map +1 -0
  62. package/lib/typert.host.d.ts +3 -0
  63. package/lib/typert.host.js +191 -0
  64. package/lib/typert.remote-client.d.ts +24 -0
  65. package/lib/typert.remote-client.js +129 -0
  66. package/lib/types/attestation.d.ts +17 -0
  67. package/lib/types/client/QaConfigController.d.ts +34 -2
  68. package/lib/types/client/QaSessionController.d.ts +106 -19
  69. package/lib/types/client/QaSurface.d.ts +5 -4
  70. package/lib/types/client/QaTranscriptAdapter.d.ts +17 -2
  71. package/lib/types/client/attestation.d.ts +25 -0
  72. package/lib/types/client/chat-index.d.ts +28 -0
  73. package/lib/types/client/components/Markdown.d.ts +7 -3
  74. package/lib/types/client/components/QaAgentsDrawer.d.ts +25 -0
  75. package/lib/types/client/components/QaComposer.d.ts +12 -2
  76. package/lib/types/client/components/QaMessage.d.ts +14 -1
  77. package/lib/types/client/components/QaSidebar.d.ts +41 -0
  78. package/lib/types/client/components/QaSourcesDrawer.d.ts +8 -0
  79. package/lib/types/client/components/QaWorkGroup.d.ts +19 -0
  80. package/lib/types/client/components/VariantSwitcher.d.ts +19 -0
  81. package/lib/types/client/components/format.d.ts +9 -0
  82. package/lib/types/client/index.d.ts +1 -5
  83. package/lib/types/client/types.d.ts +29 -0
  84. package/lib/types/client/wait-for.d.ts +11 -0
  85. package/lib/types/index.d.ts +22 -6
  86. package/lib/types/lockdown-policy.d.ts +18 -0
  87. package/lib/types/secure-session.d.ts +22 -0
  88. package/lib/types/types.d.ts +148 -1
  89. package/package.json +29 -3
package/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ ## 0.2.0 (2026-09-10)
2
+
3
+ ### 🚀 Features
4
+
5
+ - Make the QA surface a complete end-user assistant shell: image attachments ([b9af082](https://github.com/xarleyn/dsh-plugins/commit/b9af082))
6
+ (drag & drop, paste, and a picker with removable previews; base64 prompt
7
+ parts the Host promotes to durable attachments, rendered back as clickable
8
+ thumbnails), subagent delegation presentation (launch work items with the
9
+ durable child id, settlement notices collapsed into titled expandable rows,
10
+ an agents panel listing the chat's subagents, and live read-only subagent
11
+ transcripts with a one-click return), source cards with a full-output detail
12
+ pane, clickable links and an open action, answer regeneration with
13
+ ChatGPT-style variant switching, message ratings with hover response
14
+ metadata (duration, TTFT, tokens per second), a data-usage disclaimer
15
+ plate under the composer, per-browser chat history
16
+ ordered by host updates with search and a collapsible sidebar, lazy draft
17
+ chats that create nothing until the first prompt, directory/workspace
18
+ pinning with a dedicated `workspace-unavailable` refusal, the company
19
+ interaction palette as `--dsh-qa-*` tokens, and a split of the surface into
20
+ focused drawer, switcher, and formatting modules.
21
+
22
+
23
+ ### 🩹 Fixes
24
+
25
+ - Pack the generated Typert host and remote-client entrypoints: the `files` ([0059cb4](https://github.com/xarleyn/dsh-plugins/commit/0059cb4))
26
+ allowlist only kept declarations under `lib/types/`, so the `./remote` and
27
+ `./typert` exports previously shipped without their implementation modules
28
+ and type definitions.
29
+
30
+ ### ❤️ Thank You
31
+
32
+ - xarleyn @xarleyn
33
+
1
34
  ## 0.1.1 (2026-09-06)
2
35
 
3
36
  ### 🩹 Fixes
@@ -23,3 +56,38 @@
23
56
  - Added Host settings registration and safety filtering for internal events.
24
57
  - Added a narrow Host navigation redirect for DSH releases whose static
25
58
  frontend returns 404 for direct `/qa` requests.
59
+ - Added fail-closed Host policy attestation, the `qa-read-only` permission pin,
60
+ an inherited-tool allow-list, and a monotonic execution guard.
61
+ - Disabled session reset by default and added capability-regression gates.
62
+ - Refined the QA surface with first-party-style conversation chrome, plain
63
+ assistant flow, user bubbles, copy actions and a floating two-row composer.
64
+ - Added opt-in reasoning and tool-call details grouped into a live turn work
65
+ disclosure that collapses to a `Worked for …` summary before the final answer.
66
+ - Added an optional minimal chat-history sidebar (`ui.showSessionList`) with a
67
+ per-browser localStorage chat index, attested switching and a gated
68
+ New chat control.
69
+ - Send-time policy attestation now survives a Host that idled the session's
70
+ agent out: the client re-binds the session (re-materializing the agent) and,
71
+ when the refused session is still blank, continues in a fresh attested
72
+ session instead of surfacing an error.
73
+ - Added GFM table, ordered-list and horizontal-rule rendering to the safe
74
+ Markdown output.
75
+ - Added a two-click chat delete control to the chat-history sidebar; it
76
+ removes the chat from the per-browser index only (DSH has no
77
+ session-deletion seam).
78
+ - Added attestation diagnostics: the Host folds a coarse reason code into the
79
+ refusal and the browser console prints one operator hint instead of a
80
+ duplicate stack trace.
81
+ - Fixed repeated policy attestation for tools contributed by an agent preset:
82
+ the applied restriction now retains the exact scoped allow-list instead of
83
+ accidentally reducing it to global-only tools. Removed the redundant native
84
+ presentation override so reloads cannot collide with the preset's mode.
85
+ - The browser re-reads the Host configuration when the connection is restored,
86
+ so an open page survives a Host restart with a changed deployment config.
87
+ - Added LAN serving support: a shipped `deploy/qa-lan.patch.yml` webserver
88
+ overlay, and a `qaSurface/describe` Host Remote the browser falls back to
89
+ when the loopback-pinned settings namespace is unavailable, so branding,
90
+ session pinning and lockdown UI switches keep working over the network.
91
+ - Localized the end-user chat interface into Russian, added rotating playful
92
+ thinking phrases, and moved the default quick-question chips next to the
93
+ composer on an empty chat.
package/README.md CHANGED
@@ -11,8 +11,13 @@ Session and Agent Loop.
11
11
  - stays invisible outside the configured route;
12
12
  - creates or restores one real DSH Session;
13
13
  - renders only user text, assistant-visible text and safe status messages;
14
- - supports streaming, Stop, New chat, safe Markdown and mobile layouts;
14
+ - supports streaming, Stop, optional New chat, safe Markdown, copy actions and
15
+ a responsive first-party-style conversation layout;
16
+ - optionally shows a minimal per-browser chat-history sidebar
17
+ (`ui.showSessionList`) whose switching re-runs policy attestation;
15
18
  - blocks unsupported approvals/questions instead of auto-approving them;
19
+ - pins locked sessions to `read-only` + `approval=never` before Send is enabled;
20
+ - applies a Host-side tool allow-list plus a monotonic execution guard;
16
21
  - uses the existing same-origin DSH connection and trust boundary.
17
22
 
18
23
  It does not add another HTTP server, provider proxy, permissive CORS rule, or
@@ -58,14 +63,17 @@ config:
58
63
  path: /qa
59
64
  matchChildren: true
60
65
  branding:
61
- title: Internal Assistant
62
- subtitle: Answers about the internal platform
63
- welcomeMessage: How can I help?
64
- placeholder: Ask a question...
66
+ title: Внутренний помощник
67
+ subtitle: Отвечает на вопросы о внутренней платформе
68
+ welcomeMessage: Чем могу помочь?
69
+ placeholder: Задайте вопрос…
65
70
  logoUrl: null
66
71
  session:
72
+ # Pin every chat to a directory (optional, one of):
73
+ # cwd: "D:/qa-docs" # direct absolute directory pin
74
+ # workspaceId: "<uuid>" # or a registered DSH workspace
67
75
  policy: browser-persistent
68
- storageKey: dsh-qa-surface
76
+ storageKey: dsh-qa-surface.session
69
77
  workspaceId: company-knowledge
70
78
  fixedSessionId: null
71
79
  agentPreset: qa-assistant
@@ -74,19 +82,38 @@ config:
74
82
  reasoningEffort: null
75
83
  ui:
76
84
  showHeader: true
77
- showReset: true
85
+ showReset: false
78
86
  showStop: true
79
87
  showTimestamps: false
80
88
  showToolActivity: false
81
89
  showReasoning: false
82
90
  renderMarkdown: true
83
91
  maxContentWidth: 900
92
+ showSessionList: false
84
93
  suggestedQuestions:
85
- - How do I request access?
86
- - Where is the runbook?
94
+ - Как запросить доступ?
95
+ - Где лежит инструкция?
87
96
  interaction:
88
97
  approvals: blocked
89
98
  questions: unsupported
99
+ lockdown:
100
+ enabled: true
101
+ enforceFixedAgentPreset: true
102
+ enforceFixedWorkspace: true
103
+ enforceFixedModel: true
104
+ sandboxMode: read-only
105
+ approvalPolicy: never
106
+ permissionPreset: qa-read-only
107
+ allowPermissionChanges: false
108
+ allowSlashCommands: false
109
+ allowSettingsMutation: false
110
+ allowSessionReset: false
111
+ allowSessionRename: false
112
+ allowSessionDelete: false
113
+ allowArbitrarySessionOpen: false
114
+ toolPolicy:
115
+ mode: allow-list
116
+ allow: []
90
117
  ```
91
118
 
92
119
  `workspaceId` is recommended for a deterministic assistant. Without it, DSH
@@ -95,8 +122,12 @@ skills, knowledge connections and permission policy in `agentPreset`, not in
95
122
  this UI plugin.
96
123
 
97
124
  Model override is opt-in: `provider` and `model` must be set together. Slash
98
- commands are rejected as plain QA input in this MVP. `showReasoning: true` is
99
- also rejected; raw reasoning is intentionally outside the safe MVP surface.
125
+ commands are rejected as plain QA input. Reasoning and tool details remain
126
+ hidden by default. Enabling `ui.showReasoning` and `ui.showToolActivity` adds a
127
+ turn-scoped work disclosure: it stays open while the assistant is working,
128
+ then collapses to `Worked for ...` before the final answer. Tool capability is
129
+ still controlled exclusively by `lockdown.toolPolicy.allow`; the display flags
130
+ do not grant tools.
100
131
 
101
132
  Session policies:
102
133
 
@@ -106,8 +137,85 @@ Session policies:
106
137
  - `fixed` requires `fixedSessionId`, never creates a replacement, and is meant
107
138
  only for controlled single-user deployments.
108
139
 
109
- New chat creates another DSH Session and leaves the old one intact for operator
110
- inspection.
140
+ New chat is disabled by default. To expose it, set `lockdown.allowSessionReset:
141
+ true` plus either `ui.showReset: true` (header button) or `ui.showSessionList:
142
+ true` (sidebar button). Pressing it opens a draft composer and creates nothing:
143
+ the DSH Session is materialized lazily by the first prompt, so the chat list
144
+ stays quiet until a message is actually sent, and the old session stays intact
145
+ for operator inspection. The sidebar orders chats by the host's last update,
146
+ so merely opening a chat never moves it.
147
+
148
+ Regeneration: the last committed answer offers a retry action. The session log
149
+ is append-only, so "regenerate" sends a hidden instruction as an ordinary
150
+ prompt and the answer arrives as a follow-up turn; the projection hides that
151
+ instruction and the consecutive turns read as variants of one question,
152
+ navigable with a `< 2/2 >` switcher (newest shown by default).
153
+
154
+ Sources: with `ui.showToolActivity: true` a header button opens a right-hand
155
+ drawer listing the pages fetched, searches run and files read in this chat,
156
+ projected from the same tool activity the work groups render - no extra
157
+ prompting or tooling is involved. Clicking a source opens its full tool
158
+ output with every http(s) link clickable, and web sources carry an
159
+ "Открыть" button straight to the page (a Jira issue, a wiki article).
160
+
161
+ Images: the composer accepts PNG/JPEG/WebP/GIF via drag & drop onto the
162
+ composer, paste, and the picker button, several at once (soft client caps:
163
+ 8 images, 15 MB each). Images ride the prompt as base64 uploads the Host
164
+ promotes to durable attachments, so they survive reloads; sent images
165
+ render as clickable thumbnails on the message. Whether the model can see
166
+ them depends on the deployment's model (vision).
167
+
168
+ Subagents: the deployment may opt the delegation family (`subagent`,
169
+ `subagent_fork`, `send_message`, `list_agents`, `interrupt_agent`) into the
170
+ lockdown allow-list; the preset must mount them. Launches then render as
171
+ first-class work items (description, background flag, durable child id),
172
+ settlement notices appear as status rows, an "Агенты" header drawer lists the
173
+ chat's subagents with live status, and any subagent opens as a read-only
174
+ live transcript (composer disabled, one click back to the chat) - viewing
175
+ never attests or writes.
176
+
177
+ `ui.showSessionList: true` renders a minimal chat-history sidebar beside the
178
+ conversation. It lists only the chats this browser has actually used: the
179
+ client keeps a per-browser id index under
180
+ `<storageKey>:v1:<route>:chats` in localStorage (capped at 50, most recently
181
+ used first) and intersects it with the Host session list, so users sharing the
182
+ deployment never see each other's chats. Switching re-runs the full policy
183
+ attestation; a chat the Host no longer lists is pruned from the index. Each
184
+ row carries a two-click delete control that removes the chat from this
185
+ browser's index; deleting the chat that is currently open continues in a
186
+ fresh attested session. Host-side sessions are not deleted — DSH 0.1.x
187
+ exposes no session-deletion seam. The sidebar hides below 600px viewports.
188
+
189
+ Locked mode requires a deployment permission preset named `qa-read-only`.
190
+ Extend the existing `@deepseek-ai/dsh-permission-presets` row without changing
191
+ its process-wide default:
192
+
193
+ ```yaml
194
+ - id: permission
195
+ name: "@deepseek-ai/dsh-permission-presets"
196
+ config:
197
+ presets:
198
+ read-only:
199
+ sandbox: read-only
200
+ approval: ask
201
+ workspace-write:
202
+ sandbox: workspace-write
203
+ approval: ask
204
+ danger-full-access:
205
+ sandbox: danger-full-access
206
+ approval: never
207
+ qa-read-only:
208
+ sandbox: read-only
209
+ approval: never
210
+ name: QA Read Only
211
+ description: No filesystem mutations and no permission escalation.
212
+ ```
213
+
214
+ The shipped tool allow-list is empty. Add only reviewed read-only tool names
215
+ from the actual deployment. A name that is not registered fails closed. The
216
+ Host restriction retains exact allow-listed tools from the agent preset's
217
+ ancestor scope, and the additional execution guard also denies session-scoped
218
+ tools and `run_code` unless their exact names are allowed.
111
219
 
112
220
  ## Security and deployment
113
221
 
@@ -115,12 +223,52 @@ inspection.
115
223
  with the same deployment authentication and DSH browser trust policy as the
116
224
  operator UI. The plugin never auto-approves a permission request or answers an
117
225
  interactive agent question. Configure a least-privilege, non-interactive agent
118
- preset for end-user deployments.
226
+ preset for end-user deployments. On a shared Host, a user who can open `/`,
227
+ call the normal DSH APIs, or use another authenticated client can bypass the
228
+ QA presentation. Use a dedicated DSH process/identity and network boundary
229
+ when `/qa` must be a strong security boundary.
119
230
 
120
231
  The `embedding.frameAncestors` field is reserved deployment metadata in 0.1.x;
121
232
  the plugin does not mutate CSP headers. Configure `frame-ancestors` at the
122
233
  trusted reverse proxy if iframe embedding is required.
123
234
 
235
+ ### Serving the QA rig over the LAN
236
+
237
+ The plugin itself needs no changes to be reachable from other machines; the
238
+ web server's bind address is a deployment composition decision. DSH refuses
239
+ `dsh --host 0.0.0.0` on the command line on purpose, so network exposure is
240
+ expressed with the shipped patch overlay instead:
241
+
242
+ ```bash
243
+ pnpm dsh --profile qa-surface --no-open --port 3082 \
244
+ --patch plugins/dsh-qa-surface/deploy/qa-lan.patch.yml
245
+ ```
246
+
247
+ The overlay sets the `webserver` row to `0.0.0.0` while keeping the `--port`
248
+ flag working. On an all-interfaces bind DSH derives trusted `/api` authorities
249
+ from the machine's LAN IPv4 addresses, so `http://<lan-ip>:3082/qa` works
250
+ without extra configuration; users reaching a DNS name instead of an IP
251
+ literal need `--trusted-host <name>` on the same command line.
252
+
253
+ LAN browsers cannot read the DSH settings namespace (settings RPCs are pinned
254
+ to loopback by the gateway). The plugin covers this itself: a browser that
255
+ sees the namespace as unavailable reads the effective configuration through
256
+ the `qaSurface/describe` Host Remote, so branding, session pinning and
257
+ lockdown UI switches keep working over the LAN. Host-side enforcement was
258
+ never dependent on that read path.
259
+
260
+ What a LAN deployment does not change:
261
+
262
+ - DSH has no authentication or TLS on this port; anyone who can reach it can
263
+ drive the harness, and the full operator UI at `/` stays reachable next to
264
+ `/qa`. Scope the port's reachability (subnet-limited firewall rule, VPN or
265
+ tailnet, authenticating reverse proxy) and use a dedicated process identity.
266
+ - Loopback-only settings, directory picking and credential RPCs stay refused
267
+ for LAN clients; the first-load welcome notice re-appears on reload because
268
+ remote browsers have no settings persistence.
269
+
270
+ See [Configuration](docs/CONFIGURATION.md) for the config-channel details.
271
+
124
272
  See [Architecture](docs/ARCHITECTURE.md),
125
273
  [Configuration](docs/CONFIGURATION.md), and
126
274
  [Compatibility](docs/COMPATIBILITY.md).