@yadsh/dsh-qa-surface 0.2.0 → 0.3.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 (161) hide show
  1. package/CHANGELOG.md +84 -1
  2. package/README.md +87 -12
  3. package/compatibility.json +11 -3
  4. package/docs/ARCHITECTURE.md +1 -1
  5. package/docs/COMPATIBILITY.md +1 -1
  6. package/docs/CONFIGURATION.md +88 -1
  7. package/docs/SOURCES-MIGRATION.md +28 -0
  8. package/docs/SOURCES-PROVENANCE-AUDIT.md +88 -0
  9. package/docs/SPEC-dsh-qa-surface-accounts.md +219 -0
  10. package/docs/SPEC-dsh-qa-surface-sources-provenance.md +1692 -0
  11. package/lib/accounts/index.js +2 -0
  12. package/lib/accounts/index.js.map +1 -0
  13. package/lib/accounts/store.js +478 -0
  14. package/lib/accounts/store.js.map +1 -0
  15. package/lib/attestation.js.map +1 -1
  16. package/lib/cli.js +143 -0
  17. package/lib/cli.js.map +1 -0
  18. package/lib/client/QaAccountsController.js +329 -0
  19. package/lib/client/QaAccountsController.js.map +1 -0
  20. package/lib/client/QaSessionController.js +203 -127
  21. package/lib/client/QaSessionController.js.map +1 -1
  22. package/lib/client/QaSurface.js +234 -72
  23. package/lib/client/QaSurface.js.map +1 -1
  24. package/lib/client/QaTranscriptAdapter.js +27 -241
  25. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  26. package/lib/client/attestation.js +6 -0
  27. package/lib/client/attestation.js.map +1 -1
  28. package/lib/client/components/Markdown.js +54 -13
  29. package/lib/client/components/Markdown.js.map +1 -1
  30. package/lib/client/components/QaAuthGate.js +40 -0
  31. package/lib/client/components/QaAuthGate.js.map +1 -0
  32. package/lib/client/components/QaChangelog.js +112 -0
  33. package/lib/client/components/QaChangelog.js.map +1 -0
  34. package/lib/client/components/QaMessage.js +47 -17
  35. package/lib/client/components/QaMessage.js.map +1 -1
  36. package/lib/client/components/QaSidebar.js +63 -20
  37. package/lib/client/components/QaSidebar.js.map +1 -1
  38. package/lib/client/components/QaSourcesDrawer.js +113 -32
  39. package/lib/client/components/QaSourcesDrawer.js.map +1 -1
  40. package/lib/client/components/QaTurnRail.js +281 -0
  41. package/lib/client/components/QaTurnRail.js.map +1 -0
  42. package/lib/client/components/QaWelcomeNotice.js +125 -0
  43. package/lib/client/components/QaWelcomeNotice.js.map +1 -0
  44. package/lib/client/components/QaWidthHandle.js +142 -0
  45. package/lib/client/components/QaWidthHandle.js.map +1 -0
  46. package/lib/client/components/source-refs.js +78 -0
  47. package/lib/client/components/source-refs.js.map +1 -0
  48. package/lib/client/create-session.js +51 -0
  49. package/lib/client/create-session.js.map +1 -0
  50. package/lib/client/index.js +100 -12
  51. package/lib/client/index.js.map +1 -1
  52. package/lib/client/project-session-state.js +78 -0
  53. package/lib/client/project-session-state.js.map +1 -0
  54. package/lib/client/session-admission.js +38 -0
  55. package/lib/client/session-admission.js.map +1 -0
  56. package/lib/client/session-assets.js +52 -0
  57. package/lib/client/session-assets.js.map +1 -0
  58. package/lib/client/session-sources.js +61 -0
  59. package/lib/client/session-sources.js.map +1 -0
  60. package/lib/client/settlement.js +24 -0
  61. package/lib/client/settlement.js.map +1 -0
  62. package/lib/client/styles.js +151 -9
  63. package/lib/client/styles.js.map +1 -1
  64. package/lib/client/tool-presentation.js +102 -0
  65. package/lib/client/tool-presentation.js.map +1 -0
  66. package/lib/client/turn-sources.js +98 -0
  67. package/lib/client/turn-sources.js.map +1 -0
  68. package/lib/client/types.js +2 -0
  69. package/lib/client/types.js.map +1 -1
  70. package/lib/client/use-session-ui-state.js +68 -0
  71. package/lib/client/use-session-ui-state.js.map +1 -0
  72. package/lib/client/use-transcript-view.js +44 -0
  73. package/lib/client/use-transcript-view.js.map +1 -0
  74. package/lib/client.js +4649 -861
  75. package/lib/client.js.map +1 -1
  76. package/lib/config.js +218 -102
  77. package/lib/config.js.map +1 -1
  78. package/lib/entry-redirect.js +44 -0
  79. package/lib/entry-redirect.js.map +1 -0
  80. package/lib/host-route.js +25 -1
  81. package/lib/host-route.js.map +1 -1
  82. package/lib/index.js +209 -23
  83. package/lib/index.js.map +1 -1
  84. package/lib/launch-token.js +42 -0
  85. package/lib/launch-token.js.map +1 -0
  86. package/lib/provenance/collector.js +40 -0
  87. package/lib/provenance/collector.js.map +1 -0
  88. package/lib/provenance/connectors.js +147 -0
  89. package/lib/provenance/connectors.js.map +1 -0
  90. package/lib/provenance/dedupe.js +89 -0
  91. package/lib/provenance/dedupe.js.map +1 -0
  92. package/lib/provenance/extractors.js +340 -0
  93. package/lib/provenance/extractors.js.map +1 -0
  94. package/lib/provenance/file-preview.js +39 -0
  95. package/lib/provenance/file-preview.js.map +1 -0
  96. package/lib/provenance/host-store.js +387 -0
  97. package/lib/provenance/host-store.js.map +1 -0
  98. package/lib/provenance/index.js +9 -0
  99. package/lib/provenance/index.js.map +1 -0
  100. package/lib/provenance/normalize.js +128 -0
  101. package/lib/provenance/normalize.js.map +1 -0
  102. package/lib/provenance/reported.js +53 -0
  103. package/lib/provenance/reported.js.map +1 -0
  104. package/lib/provenance/types.js +2 -0
  105. package/lib/provenance/types.js.map +1 -0
  106. package/lib/resolve-config.js +161 -0
  107. package/lib/resolve-config.js.map +1 -1
  108. package/lib/secure-session.js +50 -8
  109. package/lib/secure-session.js.map +1 -1
  110. package/lib/typert.host.js +599 -6
  111. package/lib/typert.remote-client.d.ts +19 -3
  112. package/lib/typert.remote-client.js +471 -3
  113. package/lib/types/accounts/index.d.ts +2 -0
  114. package/lib/types/accounts/store.d.ts +121 -0
  115. package/lib/types/attestation.d.ts +1 -1
  116. package/lib/types/cli.d.ts +9 -0
  117. package/lib/types/client/QaAccountsController.d.ts +95 -0
  118. package/lib/types/client/QaConfigController.d.ts +1 -1
  119. package/lib/types/client/QaSessionController.d.ts +46 -7
  120. package/lib/types/client/QaSurface.d.ts +8 -3
  121. package/lib/types/client/QaTranscriptAdapter.d.ts +5 -10
  122. package/lib/types/client/components/Markdown.d.ts +11 -1
  123. package/lib/types/client/components/QaAgentsDrawer.d.ts +1 -1
  124. package/lib/types/client/components/QaAuthGate.d.ts +15 -0
  125. package/lib/types/client/components/QaChangelog.d.ts +21 -0
  126. package/lib/types/client/components/QaMessage.d.ts +10 -3
  127. package/lib/types/client/components/QaSidebar.d.ts +30 -2
  128. package/lib/types/client/components/QaSourcesDrawer.d.ts +15 -3
  129. package/lib/types/client/components/QaTurnRail.d.ts +59 -0
  130. package/lib/types/client/components/QaWelcomeNotice.d.ts +27 -0
  131. package/lib/types/client/components/QaWidthHandle.d.ts +36 -0
  132. package/lib/types/client/components/source-refs.d.ts +16 -0
  133. package/lib/types/client/create-session.d.ts +16 -0
  134. package/lib/types/client/index.d.ts +1 -0
  135. package/lib/types/client/project-session-state.d.ts +30 -0
  136. package/lib/types/client/session-admission.d.ts +23 -0
  137. package/lib/types/client/session-assets.d.ts +20 -0
  138. package/lib/types/client/session-sources.d.ts +28 -0
  139. package/lib/types/client/settlement.d.ts +6 -0
  140. package/lib/types/client/tool-presentation.d.ts +6 -0
  141. package/lib/types/client/turn-sources.d.ts +14 -0
  142. package/lib/types/client/types.d.ts +73 -9
  143. package/lib/types/client/use-session-ui-state.d.ts +35 -0
  144. package/lib/types/client/use-transcript-view.d.ts +20 -0
  145. package/lib/types/entry-redirect.d.ts +20 -0
  146. package/lib/types/host-route.d.ts +14 -1
  147. package/lib/types/index.d.ts +47 -3
  148. package/lib/types/launch-token.d.ts +21 -0
  149. package/lib/types/provenance/collector.d.ts +26 -0
  150. package/lib/types/provenance/connectors.d.ts +11 -0
  151. package/lib/types/provenance/dedupe.d.ts +6 -0
  152. package/lib/types/provenance/extractors.d.ts +14 -0
  153. package/lib/types/provenance/file-preview.d.ts +10 -0
  154. package/lib/types/provenance/host-store.d.ts +54 -0
  155. package/lib/types/provenance/index.d.ts +10 -0
  156. package/lib/types/provenance/normalize.d.ts +13 -0
  157. package/lib/types/provenance/reported.d.ts +5 -0
  158. package/lib/types/provenance/types.d.ts +78 -0
  159. package/lib/types/secure-session.d.ts +11 -2
  160. package/lib/types/types.d.ts +167 -11
  161. package/package.json +58 -41
package/CHANGELOG.md CHANGED
@@ -1,5 +1,89 @@
1
+ ## 0.3.0 (2026-09-12)
2
+
3
+ ### 🚀 Features
4
+
5
+ - Optional QA accounts and entry routing. `accounts.enabled` mounts a ([dc2f582](https://github.com/xarleyn/dsh-plugins/commit/dc2f582))
6
+ full-frame login/registration gate (email + password, coarse audience-safe
7
+ refusals, per-store rate limiting, self-registration toggle) backed by
8
+ `$DSH_HOME/qa-accounts.json`: scrypt password hashes, a persisted HMAC secret
9
+ for stateful-expiry account tokens, and a session ownership map. Ownership is
10
+ first come, first served - attesting or bulk-claiming an unowned session binds
11
+ it to the caller (the migration path for existing per-browser chats on first
12
+ login); sessions owned by another user refuse attestation with
13
+ `session-owned-elsewhere` and stay hidden from the sidebar, admins are not
14
+ refused. The identity rides as an explicit token argument into the gated
15
+ `qaSurface` remotes (the typert carrier never exposes HTTP requests), the
16
+ policy admission checks it before any session fact is revealed
17
+ (`auth-required` reopens the gate on expiry), and the account chip with
18
+ logout lives in the sidebar footer. `entry.redirectNonLoopback` injects a
19
+ guarded head script through the `webserver/index-inject` event that continues
20
+ non-loopback hostnames into the QA route - the navigation-marker hand-off is
21
+ never redirected (no loops), `/?ui=admin` persists an operator bypass and
22
+ `/?ui=qa` clears it. Accounts are an identity layer for the QA surface, not a
23
+ harness boundary: QA users still hold the shared host launch-token cookie.
24
+
25
+ Follow-ups adopted from a review of the independent dsh-auth-gate plugin: a
26
+ proxy-side deny list for the privileged config-plane RPC methods behind the
27
+ deploy proxy's Host/Origin rewrite, a plugin-side launch-token bridge
28
+ (`entry.cookieBootstrap`) that performs the one-time host-cookie exchange on
29
+ the `/qa` route itself, a `qa-accounts` bin CLI (list/add/set-role/disable/
30
+ enable/revoke) so account administration never requires hand-editing the
31
+ JSON file, and per-account state — a `disabled` flag refusing logins with
32
+ `account-disabled` plus a `tokenVersion` burned into tokens that
33
+ `disable`/`revoke` bump, making logout and lockout server-side facts.
34
+
35
+ Admins get cross-user views over the same ownership map:
36
+ `qaSurface/accountsListOwnership` (admin-only, `admin-required` refusal
37
+ otherwise) returns every chat with its owner's resolved display name, the
38
+ admin sidebar switches to per-owner sections ordered by their freshest chat
39
+ (unclaimed chats trail under "Без владельца"), and user messages in foreign
40
+ chats carry an `author` byline naming the chat owner. Ordinary accounts and
41
+ deployments with accounts disabled keep the flat sidebar and unlabeled
42
+ messages.
43
+
44
+ - The chat-history sidebar gained a footer version button that opens an ([d9d5868](https://github.com/xarleyn/dsh-plugins/commit/d9d5868))
45
+ end-user changelog dialog: a curated per-version summary (new features and
46
+ fixes in Russian) rendered in a themed modal with Escape/backdrop close.
47
+ The bundled version and entries are pinned to package.json and the release
48
+ CHANGELOG by a unit test, so a release cannot ship a stale dialog.
49
+
50
+ - Rebuild the client on the 0.1.5 surfaces: the transcript projects from the ([458b9c2](https://github.com/xarleyn/dsh-plugins/commit/458b9c2))
51
+ ui-chat conversation view's legacy slice, chat/model pinning moves to the
52
+ wire remotes (`agentPresets.select` on the still-blank session, then
53
+ `session.selectModel`) with the attestation ordering preserved, and
54
+ history reads go through the session-v3 surface. The supported host range
55
+ moves to `>=0.1.5-rc.2 <0.2.0`, dropping 0.1.1-rc.2.
56
+
57
+ - Harden the gated QA experience and make cross-user history explicitly ([2ed2024](https://github.com/xarleyn/dsh-plugins/commit/2ed2024))
58
+ opt-in. A new `accounts.showOtherUsersChats` setting defaults to `false`, so
59
+ administrators only see their own chats unless the deployment enables the
60
+ shared ownership view. Account storage now follows external CLI updates and
61
+ uses process-scoped temporary writes, while session admission and client state
62
+ handling avoid stale async results and reset session-bound assets reliably.
63
+
64
+ The QA client now presents a dedicated test-interface disclosure, improves
65
+ chat search and owner matching, keeps row actions from disturbing result
66
+ layout, distinguishes administrator roles, and removes decorative middle-dot
67
+ separators from the sidebar, messages, and source details. Its curated 0.3.0
68
+ history entry is prepared in advance, while the current-version marker is
69
+ injected from package.json at build time so the release bump promotes it
70
+ without another source edit.
71
+
72
+ ### ❤️ Thank You
73
+
74
+ - xarleyn @xarleyn
75
+
1
76
  ## 0.2.0 (2026-09-10)
2
77
 
78
+ - Added DSH-style symmetric transcript/composer width handles with adaptive
79
+ defaults, viewport clamping, and per-route browser persistence.
80
+ - Added Host-owned structured source provenance for parent and delegated turns,
81
+ replayable `qa/sources` snapshots, dedupe/ranking, opaque-provider reporting,
82
+ grouped source UI, and safe rendered/raw file previews.
83
+ - Updated packed Host/browser smoke coverage for browser authentication,
84
+ revisioned client batches, scoped Remote injection, and current Typert RPC
85
+ envelopes.
86
+
3
87
  ### 🚀 Features
4
88
 
5
89
  - Make the QA surface a complete end-user assistant shell: image attachments ([b9af082](https://github.com/xarleyn/dsh-plugins/commit/b9af082))
@@ -19,7 +103,6 @@
19
103
  interaction palette as `--dsh-qa-*` tokens, and a split of the surface into
20
104
  focused drawer, switcher, and formatting modules.
21
105
 
22
-
23
106
  ### 🩹 Fixes
24
107
 
25
108
  - Pack the generated Typert host and remote-client entrypoints: the `files` ([0059cb4](https://github.com/xarleyn/dsh-plugins/commit/0059cb4))
package/README.md CHANGED
@@ -18,6 +18,14 @@ Session and Agent Loop.
18
18
  - blocks unsupported approvals/questions instead of auto-approving them;
19
19
  - pins locked sessions to `read-only` + `approval=never` before Send is enabled;
20
20
  - applies a Host-side tool allow-list plus a monotonic execution guard;
21
+ - optionally gates the surface behind email + password accounts
22
+ (`accounts.enabled`) with server-side session ownership, a first-login
23
+ migration of the browser's existing chats, a `qa-accounts` management CLI
24
+ (list/add/set-role/disable/revoke), and a coarse honest boundary:
25
+ accounts identify QA users, they do not fence the harness root;
26
+ - optionally redirects non-loopback hostnames from the harness root into the
27
+ QA route (`entry.redirectNonLoopback`), keeping the operator's localhost
28
+ harness UI untouched;
21
29
  - uses the existing same-origin DSH connection and trust boundary.
22
30
 
23
31
  It does not add another HTTP server, provider proxy, permissive CORS rule, or
@@ -43,8 +51,8 @@ Some DSH patch operations replace a row's complete `config` instead of deeply
43
51
  merging it. When editing an existing row, provide every value that deployment
44
52
  needs.
45
53
 
46
- DSH `0.1.1-rc.2` serves unknown frontend paths as 404 rather than falling back
47
- to `index.html`. The Host half therefore claims only the configured QA
54
+ Tested DSH releases serve unknown frontend paths as 404 rather than falling
55
+ back to `index.html`. The Host half therefore claims only the configured QA
48
56
  navigation path and redirects it through the canonical `/` document with a
49
57
  short-lived query marker. The browser restores the requested `/qa` URL with
50
58
  `history.replaceState` before mounting the overlay. No second server, duplicate
@@ -90,6 +98,11 @@ config:
90
98
  renderMarkdown: true
91
99
  maxContentWidth: 900
92
100
  showSessionList: false
101
+ accounts:
102
+ enabled: false
103
+ allowRegistration: true
104
+ sessionTtlDays: 30
105
+ showOtherUsersChats: false
93
106
  suggestedQuestions:
94
107
  - Как запросить доступ?
95
108
  - Где лежит инструкция?
@@ -114,6 +127,38 @@ config:
114
127
  toolPolicy:
115
128
  mode: allow-list
116
129
  allow: []
130
+ sources:
131
+ enabled: true
132
+ collect:
133
+ parentAgent: true
134
+ subagents: true
135
+ persistTurnEvent: true
136
+ display:
137
+ sidebar: true
138
+ footer: true
139
+ groupByKind: true
140
+ showDiscovered: false
141
+ showOriginBadges: false
142
+ maxInitiallyVisiblePerGroup: 8
143
+ webSearch:
144
+ promoteSearchResultsWithoutFetch: true
145
+ maxPromotedPerSearch: 5
146
+ dedupe:
147
+ normalizeUrls: true
148
+ stripTrackingParams: true
149
+ mergeFileRanges: true
150
+ filePreview:
151
+ enabled: true
152
+ markdownRenderedByDefault: true
153
+ allowRawToggle: true
154
+ maxBytes: 2000000
155
+ maxMarkdownRenderBytes: 1000000
156
+ subagents:
157
+ inheritSources: true
158
+ enableReportToolFallback: true
159
+ markIncompleteOpaqueRuns: true
160
+ legacy:
161
+ parseAssistantSourcesBlock: false
117
162
  ```
118
163
 
119
164
  `workspaceId` is recommended for a deterministic assistant. Without it, DSH
@@ -151,12 +196,25 @@ prompt and the answer arrives as a follow-up turn; the projection hides that
151
196
  instruction and the consecutive turns read as variants of one question,
152
197
  navigable with a `< 2/2 >` switcher (newest shown by default).
153
198
 
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).
199
+ Sources are structured Host-owned provenance, independent of
200
+ `ui.showToolActivity`. Successful reads/fetches, bounded web-search evidence,
201
+ Jira/Confluence/knowledge results, and inherited subagent sources are
202
+ normalized and deduplicated into one turn bundle. That exact bundle feeds the
203
+ answer footer and grouped drawer and is persisted as `qa/sources`, so reload
204
+ does not rerun tools. Search-only discovery stays hidden by default.
205
+
206
+ Local file cards open a source-scoped, read-only preview after Host-side real
207
+ path validation against the attested session root. Markdown opens rendered by
208
+ default with an HTML-free renderer and offers `Rendered / Raw`; raw mode jumps
209
+ to recorded line ranges. The endpoint cannot browse or write files and refuses
210
+ paths that are not evidence in the canonical bundle.
211
+
212
+ Observable local subagents are inherited recursively. The internal
213
+ `qa_report_sources` tool covers opaque delegated providers and is admitted as
214
+ a provenance-only capability even when it is not listed among ordinary QA
215
+ tools. A provider that neither exposes events nor reports sources marks the
216
+ turn provenance incomplete. The QA prompt tells models not to append a manual
217
+ `Sources`/`Источники` bibliography.
160
218
 
161
219
  Images: the composer accepts PNG/JPEG/WebP/GIF via drag & drop onto the
162
220
  composer, paste, and the picker button, several at once (soft client caps:
@@ -175,17 +233,32 @@ live transcript (composer disabled, one click back to the chat) - viewing
175
233
  never attests or writes.
176
234
 
177
235
  `ui.showSessionList: true` renders a minimal chat-history sidebar beside the
178
- conversation. It lists only the chats this browser has actually used: the
236
+ conversation. By default it lists only the current user's chats: the
179
237
  client keeps a per-browser id index under
180
238
  `<storageKey>:v1:<route>:chats` in localStorage (capped at 50, most recently
181
239
  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
240
+ deployment never see each other's chats. An admin can explicitly enable
241
+ `accounts.showOtherUsersChats: true` to add chats owned by other QA accounts,
242
+ grouped by owner. Switching re-runs the full policy
183
243
  attestation; a chat the Host no longer lists is pruned from the index. Each
184
244
  row carries a two-click delete control that removes the chat from this
185
245
  browser's index; deleting the chat that is currently open continues in a
186
246
  fresh attested session. Host-side sessions are not deleted — DSH 0.1.x
187
247
  exposes no session-deletion seam. The sidebar hides below 600px viewports.
188
248
 
249
+ The sidebar footer shows the deployed plugin version. Clicking it opens a
250
+ changelog dialog with a curated per-version summary (features and fixes);
251
+ Escape or a backdrop click closes it.
252
+
253
+ On `/qa`, the plugin shadows DSH's stock `welcome-notice` onboarding entry and
254
+ renders a route-owned Russian testing disclosure. Keeping the visible dialog
255
+ in the QA overlay prevents DSH's blank-session onboarding lifecycle from
256
+ dismissing it when a question is submitted. It explains the DeepSeek Harness
257
+ preview foundation, QA review of questions and answers, the work-related
258
+ scope, and local in-contour model processing. Explicit acknowledgement is
259
+ stored as a versioned browser-local flag; changing the disclosure version
260
+ shows it again. Other DSH routes retain the stock onboarding entry.
261
+
189
262
  Locked mode requires a deployment permission preset named `qa-read-only`.
190
263
  Extend the existing `@deepseek-ai/dsh-permission-presets` row without changing
191
264
  its process-wide default:
@@ -264,8 +337,10 @@ What a LAN deployment does not change:
264
337
  `/qa`. Scope the port's reachability (subnet-limited firewall rule, VPN or
265
338
  tailnet, authenticating reverse proxy) and use a dedicated process identity.
266
339
  - 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.
340
+ for LAN clients. On `/qa`, the plugin replaces DSH's non-persistent welcome
341
+ step with a QA-specific testing disclosure and remembers its exact copy
342
+ version in that browser's local storage. The stock DSH notice remains
343
+ unchanged on operator routes.
269
344
 
270
345
  See [Configuration](docs/CONFIGURATION.md) for the config-channel details.
271
346
 
@@ -1,19 +1,27 @@
1
1
  {
2
2
  "deepseekHarness": {
3
3
  "channel": "next",
4
- "range": ">=0.1.1-rc.2 <0.2.0",
5
- "testedReleases": ["0.1.1-rc.2"],
4
+ "range": ">=0.1.5-rc.2 <0.2.0",
5
+ "testedReleases": ["0.1.5-rc.2"],
6
6
  "requiredClientFeatures": [
7
7
  "shell.overlay",
8
- "connection.api.sessions",
8
+ "remote.session.selectModel",
9
+ "remote.agentPresets.select",
10
+ "uiConversation.binding",
9
11
  "sessions.binding",
10
12
  "settingsScope",
13
+ "settings.onboarding list-slot shadowing",
11
14
  "webServer.register",
12
15
  "typert.remote",
13
16
  "agentPresets.composedPreset",
14
17
  "permissionPresets.set",
15
18
  "tools.restrict",
16
19
  "tools.guard",
20
+ "tools.register",
21
+ "session.event-extension",
22
+ "agent.turn-stopping",
23
+ "agent.currentInitiator",
24
+ "systemPrompt.section",
17
25
  "workspaceRegistry.get"
18
26
  ]
19
27
  },
@@ -2,7 +2,7 @@
2
2
 
3
3
  The Host half validates the Loader configuration, registers the `qa-surface`
4
4
  settings namespace and owns one narrow GET/HEAD route for the configured QA
5
- path. Published DSH `0.1.1-rc.2` returns 404 for unknown frontend paths, so the
5
+ path. Published DSH releases return 404 for unknown frontend paths, so the
6
6
  route redirects navigation through the canonical `/` index with an encoded
7
7
  same-origin path marker. It also exposes one typed
8
8
  `secureSession(sessionId)` operation whose policy is read only from Host
@@ -2,7 +2,7 @@
2
2
 
3
3
  | Plugin version | Tested DSH release | Required public contracts |
4
4
  | -------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
5
- | 0.1.x | 0.1.1-rc.2 | `shell.overlay`, Typert remote, `agentPresets.composedPreset`, `permissionPresets`, agent-scoped `tools.restrict/guard`, `workspaceRegistry`, session/model APIs, `webServer.register` |
5
+ | 0.1.x | 0.1.5-rc.2 | `shell.overlay`, Typert remote, `agentPresets.composedPreset`, `permissionPresets`, agent-scoped `tools.restrict/guard`, `workspaceRegistry`, session/model APIs, `webServer.register` |
6
6
 
7
7
  DSH is developer-preview software. Compatibility-sensitive calls are isolated
8
8
  in `QaSessionController`, `QaTranscriptAdapter`, `QaConfigController` and
@@ -7,12 +7,17 @@ rules are:
7
7
  `/`, `/api` or `/plugins`;
8
8
  - `fixed` requires `fixedSessionId`;
9
9
  - `provider` and `model` are either both absent or both present;
10
- - `maxContentWidth` is an integer from 480 through 1600;
10
+ - `maxContentWidth` is an integer from 480 through 1600 and caps the
11
+ user-resizable transcript/composer width; each QA route persists its chosen
12
+ width in browser storage;
11
13
  - duplicate/blank suggested questions are removed;
12
14
  - approval and question policies are fixed to safe blocking behavior;
13
15
  - reasoning and tool details are opt-in through `ui.showReasoning` and
14
16
  `ui.showToolActivity`; enable them only where those contents are appropriate
15
17
  for the QA audience;
18
+ - accounts default to disabled; when enabled, `sessionTtlDays` is an integer
19
+ from 1 through 365, and other users' chats stay hidden unless an admin view
20
+ explicitly enables `accounts.showOtherUsersChats`;
16
21
  - lockdown defaults to enabled and requires a non-empty permission preset;
17
22
  - the preset must resolve on the Host to exactly `read-only` + `never`;
18
23
  - permission, slash-command, settings, rename, delete and arbitrary-open
@@ -77,6 +82,88 @@ part of the same change. The package's
77
82
  [default inventory](../capability-policy.json) is intentionally empty, matching
78
83
  the default `toolPolicy.allow`.
79
84
 
85
+ ## Structured sources
86
+
87
+ The `sources` block controls provenance independently from Activity rendering.
88
+ The defaults collect parent and subagent results, persist a `qa/sources` event,
89
+ hide discovery-only candidates, group visible evidence by kind, and promote at
90
+ most five substantive `web_search` results when no fetch occurs. URL tracking
91
+ parameters and overlapping file ranges are normalized during deduplication.
92
+
93
+ `sources.filePreview` is a narrow read capability, not a filesystem browser.
94
+ The Host serves only a path already present in the attested session's canonical
95
+ evidence bundle, resolves symlinks with `realpath`, rejects root escape, and
96
+ applies `maxBytes` plus `maxMarkdownRenderBytes`. Markdown uses the client's
97
+ HTML-free renderer; relative links and executable raw HTML are not activated.
98
+
99
+ Set `sources.display.showOriginBadges: true` while auditing inheritance. Local
100
+ subagent sources carry run/session origins automatically. An opaque provider
101
+ can call the internal `qa_report_sources` tool; if it does not, and
102
+ `markIncompleteOpaqueRuns` remains enabled, the bundle is explicitly marked
103
+ incomplete.
104
+
105
+ See [Structured sources migration](SOURCES-MIGRATION.md) before removing an
106
+ older prompt-authored bibliography convention.
107
+
108
+ ## Accounts and the QA gate
109
+
110
+ `accounts.enabled: true` mounts the login/registration gate in front of the
111
+ QA surface and turns on server-side session ownership:
112
+
113
+ - The store lives at `$DSH_HOME/qa-accounts.json` (created on first use). It
114
+ holds scrypt password hashes, a persisted HMAC secret for the account
115
+ tokens, and the session ownership map. The path is not configurable and no
116
+ secret ever reaches `describe()`.
117
+ - `accounts.allowRegistration` (default true) controls self-service signup
118
+ in the gate; the first account ever registered becomes `admin`.
119
+ - `accounts.sessionTtlDays` (default 30) is the account token lifetime. The
120
+ token is an HMAC-signed value the browser keeps in `localStorage`; logout
121
+ or expiry returns the browser to the gate, and the Host re-derives the
122
+ identity on every attestation.
123
+ - `accounts.showOtherUsersChats` (default false) lets admins load and display
124
+ chats owned by other QA accounts. It has no effect for ordinary users.
125
+ - Session ownership is first come, first served: attesting or bulk-claiming
126
+ an unowned session binds it to the caller's account (this is how existing
127
+ per-browser chats migrate on the first login). Sessions owned by another
128
+ user are refused with the coarse `session-owned-elsewhere` reason and
129
+ hidden from the sidebar; admins are not refused.
130
+ - Manage accounts with the bundled CLI (no hand-editing of the JSON):
131
+
132
+ ```sh
133
+ qa-accounts list
134
+ qa-accounts add user@example.com --password-stdin --role user
135
+ qa-accounts set-role user@example.com admin
136
+ qa-accounts disable user@example.com # blocks logins, revokes live tokens
137
+ qa-accounts revoke user@example.com # invalidates every issued token
138
+ ```
139
+
140
+ `disable` bumps the account's token version, so all outstanding tokens die
141
+ server-side; `enable` requires a fresh sign-in. After a secret rotation
142
+ suspicion, `revoke` is the single-step response.
143
+
144
+ - Honest boundary: accounts identify QA users and gate the QA surface and
145
+ its remotes. They do not fence the harness: every QA user also holds the
146
+ host launch-token cookie, with which the full root UI stays technically
147
+ reachable. Keep the network scoping advice from the deployment kit.
148
+
149
+ ## Entry redirect
150
+
151
+ `entry.redirectNonLoopback: true` (default) injects one script into the
152
+ served root `index.html`: browsers whose URL host is not loopback
153
+ (`localhost`, `127.0.0.1`, `::1`, `*.localhost`) continue into the QA route,
154
+ so LAN visitors never see the full harness root. The check is client-side on
155
+ `location.hostname` on purpose - behind a local reverse proxy every request
156
+ looks loopback to the server. The navigation hand-off `/?__dsh_qa_route=…`
157
+ is never redirected (that would loop), `/?ui=admin` bypasses the redirect
158
+ and is remembered for the browser, and `/?ui=qa` clears the bypass.
159
+
160
+ `entry.cookieBootstrap: true` (default) additionally lets the `/qa` route
161
+ itself bootstrap the host cookie: a browser without any `dsh-auth-` cookie is
162
+ sent to the one-time `/?token=…` exchange (relative redirect, token resolved
163
+ from the host connection service) before the marker hand-off. This makes the
164
+ transparent entry work without the deploy proxy; with the proxy in front,
165
+ either side may perform the exchange and the other becomes a no-op.
166
+
80
167
  ## Configuration channel over the LAN
81
168
 
82
169
  The browser normally reads the effective configuration from the Host-owned
@@ -0,0 +1,28 @@
1
+ # Structured sources migration
2
+
3
+ `dsh-qa-surface` now treats `QaTurnSources` as the only canonical provenance
4
+ model. Assistant prose is never parsed for a `Sources` or `Источники` section,
5
+ and the default `sources.legacy.parseAssistantSourcesBlock` remains `false`.
6
+
7
+ ## Deployment steps
8
+
9
+ 1. Keep the QA agent's existing read-only retrieval tools in
10
+ `lockdown.toolPolicy.allow`.
11
+ 2. Leave `sources.collect.persistTurnEvent: true` so completed turns append a
12
+ replayable `qa/sources` snapshot.
13
+ 3. Remove prompt instructions that ask the assistant to manually write a
14
+ bibliography. The plugin contributes its own scoped guidance after QA
15
+ attestation.
16
+ 4. For an opaque subagent provider, expose the inherited
17
+ `qa_report_sources` tool and ask the provider to call it before completion.
18
+ Local DSH subagents require no reporting prompt.
19
+ 5. Start with `sources.display.showDiscovered: false`; enable it only for
20
+ provenance debugging.
21
+
22
+ Historical sessions without `qa/sources` remain readable: the Host reconstructs
23
+ bundles from durable structured tool metadata when available. Historical
24
+ assistant-authored source prose remains ordinary answer text and is not trusted
25
+ as provenance.
26
+
27
+ The `qa/sources` event is a required plugin-owned session event. Keep
28
+ `dsh-qa-surface` loaded in any Host that restores sessions containing it.
@@ -0,0 +1,88 @@
1
+ # Sources / provenance implementation audit
2
+
3
+ Date: 2026-09-11
4
+ Pinned DSH development API: `0.1.5-rc.2`
5
+
6
+ This note records the Phase 0 findings for
7
+ `SPEC-dsh-qa-surface-sources-provenance.md`. It is intentionally tied to the
8
+ versions in this repository rather than to DSH `master`.
9
+
10
+ ## Existing QA data path
11
+
12
+ - `QaSessionController` binds the client `UiConversation` snapshot.
13
+ - `QaTranscriptAdapter` reads the `chat.legacy` projection.
14
+ - The former Sources drawer inferred one row from selected tool names and raw
15
+ arguments. It counted a search query as a source and did not retain turn,
16
+ line-range, evidence, or origin data.
17
+ - `ToolResultNode.meta` is available in the client projection. DSH documents it
18
+ as the tool-private, JSON-safe result presentation payload persisted on the
19
+ corresponding `tool/result` event.
20
+
21
+ ## Structured metadata available in the pinned version
22
+
23
+ `@deepseek-ai/dsh-tools` exports the provider-neutral result shapes needed by
24
+ the first implementation slice:
25
+
26
+ - `ReadResultView`: `path`, numbered `lines`, `offset`, `totalLines`, `lang`;
27
+ - `SearchResultView`: grouped grep matches or path discovery;
28
+ - `WebSearchResultView`: structured `sources[]` and truncation state;
29
+ - `WebFetchResultView`: final redirect URL, status, and truncation state;
30
+ - generic call `FileLocation` values.
31
+
32
+ The QA extractor layer therefore consumes `ToolResultNode.meta` first and uses
33
+ tool arguments only as a compatibility fallback for direct read/fetch calls.
34
+ It does not parse assistant prose.
35
+
36
+ ## Replay and persistence
37
+
38
+ The completed implementation rebuilds collectors from persisted
39
+ `tool/call`/`tool/result.meta` events and writes one materialized `qa/sources`
40
+ snapshot at `agent/turn-stopping`. The type is declaration-merged into
41
+ `SessionEventMap`.
42
+
43
+ The pinned DSH exports its live `KNOWN_SESSION_EVENT_TYPES` set. As already
44
+ demonstrated by the repository's `dsh-session-scope` plugin, registering the
45
+ plugin event there is the supported compatibility mechanism when
46
+ `Session.append` cannot mark an extension event ignorable. Registration is
47
+ effect-scoped and removed on plugin disposal. A Host that owns sessions with
48
+ these events must load `dsh-qa-surface`, just as it must load any plugin that
49
+ owns a required durable event vocabulary.
50
+
51
+ ## Subagent lifecycle
52
+
53
+ The pinned `@deepseek-ai/dsh-subagent` does expose process-local
54
+ `subagent/start` and `subagent/end` events. Both carry `runId`, `provider`, child
55
+ session `id`, and `local`; the terminal payload additionally carries
56
+ `stopReason` and optional `lastAssistantMessage`. Scoped dispatch is keyed by
57
+ the delegating parent.
58
+
59
+ The Host now maintains that lineage registry, uses the child header or causal
60
+ `AgentRegistry.currentInitiator()` attribution, bubbles nested local-child
61
+ sources to the original root turn, and retains run/session origin fields.
62
+ Opaque providers can call `qa_report_sources`; an unreported opaque run sets
63
+ `complete=false` with an `incompleteOrigins` entry.
64
+
65
+ ## File preview reuse
66
+
67
+ The new `readSourceFile` Remote is limited to evidence paths in canonical
68
+ bundles, re-attests the session, resolves both root and target with `realpath`,
69
+ rejects traversal/symlink escape, caps bytes, and exposes no write/list API.
70
+
71
+ ## Implemented scope
72
+
73
+ - canonical source and turn-bundle types;
74
+ - URL/path/range normalization;
75
+ - extractor registry with read, grep discovery, web search, and web fetch;
76
+ - evidence promotion, deduplication, ranking, and a default display threshold;
77
+ - bounded structured web-search promotion (five results by default);
78
+ - Host per-turn collectors and materialized/replayable `qa/sources` events;
79
+ - local and nested subagent inheritance plus opaque-provider reporting;
80
+ - structured Jira, Confluence, knowledge and reported-source adapters;
81
+ - one canonical snapshot used by grouped drawer and answer footer;
82
+ - source details, origin/partial badges and completeness state;
83
+ - safe local-file preview, rendered/raw Markdown, range chips and exact raw
84
+ line highlighting;
85
+ - configuration and tests for the feature surface.
86
+
87
+ Optional inline citation rendering (SPEC Phase 5) remains intentionally out of
88
+ the MVP, as specified.