@yadsh/dsh-qa-surface 0.2.0 → 0.4.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 (325) hide show
  1. package/README.md +236 -20
  2. package/compatibility.json +11 -3
  3. package/lib/account-remotes.js +100 -0
  4. package/lib/account-remotes.js.map +1 -0
  5. package/lib/accounts/credentials.js +27 -0
  6. package/lib/accounts/credentials.js.map +1 -0
  7. package/lib/accounts/errors.js +9 -0
  8. package/lib/accounts/errors.js.map +1 -0
  9. package/lib/accounts/file.js +101 -0
  10. package/lib/accounts/file.js.map +1 -0
  11. package/lib/accounts/index.js +2 -0
  12. package/lib/accounts/index.js.map +1 -0
  13. package/lib/accounts/store.js +461 -0
  14. package/lib/accounts/store.js.map +1 -0
  15. package/lib/accounts/token.js +61 -0
  16. package/lib/accounts/token.js.map +1 -0
  17. package/lib/attachment-rules.js +111 -0
  18. package/lib/attachment-rules.js.map +1 -0
  19. package/lib/attestation.js.map +1 -1
  20. package/lib/cli.js +256 -0
  21. package/lib/cli.js.map +1 -0
  22. package/lib/client/QaAccountsController.js +359 -0
  23. package/lib/client/QaAccountsController.js.map +1 -0
  24. package/lib/client/QaSessionController.js +238 -180
  25. package/lib/client/QaSessionController.js.map +1 -1
  26. package/lib/client/QaSurface.js +281 -105
  27. package/lib/client/QaSurface.js.map +1 -1
  28. package/lib/client/QaTranscriptAdapter.js +95 -244
  29. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  30. package/lib/client/attachments.js +150 -0
  31. package/lib/client/attachments.js.map +1 -0
  32. package/lib/client/attestation.js +7 -1
  33. package/lib/client/attestation.js.map +1 -1
  34. package/lib/client/base64.js +16 -0
  35. package/lib/client/base64.js.map +1 -0
  36. package/lib/client/chat-files.js +48 -0
  37. package/lib/client/chat-files.js.map +1 -0
  38. package/lib/client/components/Markdown.js +54 -13
  39. package/lib/client/components/Markdown.js.map +1 -1
  40. package/lib/client/components/QaAuthGate.js +40 -0
  41. package/lib/client/components/QaAuthGate.js.map +1 -0
  42. package/lib/client/components/QaChangelog.js +131 -0
  43. package/lib/client/components/QaChangelog.js.map +1 -0
  44. package/lib/client/components/QaComposer.js +56 -74
  45. package/lib/client/components/QaComposer.js.map +1 -1
  46. package/lib/client/components/QaFileAttachment.js +19 -0
  47. package/lib/client/components/QaFileAttachment.js.map +1 -0
  48. package/lib/client/components/QaFilesPanel.js +45 -0
  49. package/lib/client/components/QaFilesPanel.js.map +1 -0
  50. package/lib/client/components/QaHeader.js +34 -0
  51. package/lib/client/components/QaHeader.js.map +1 -0
  52. package/lib/client/components/QaMessage.js +61 -18
  53. package/lib/client/components/QaMessage.js.map +1 -1
  54. package/lib/client/components/QaModal.js +31 -0
  55. package/lib/client/components/QaModal.js.map +1 -0
  56. package/lib/client/components/QaProfile.js +64 -0
  57. package/lib/client/components/QaProfile.js.map +1 -0
  58. package/lib/client/components/QaRightRail.js +15 -0
  59. package/lib/client/components/QaRightRail.js.map +1 -0
  60. package/lib/client/components/QaSidebar.js +68 -20
  61. package/lib/client/components/QaSidebar.js.map +1 -1
  62. package/lib/client/components/QaSourcesPanel.js +127 -0
  63. package/lib/client/components/QaSourcesPanel.js.map +1 -0
  64. package/lib/client/components/QaTurnRail.js +296 -0
  65. package/lib/client/components/QaTurnRail.js.map +1 -0
  66. package/lib/client/components/QaWelcomeNotice.js +125 -0
  67. package/lib/client/components/QaWelcomeNotice.js.map +1 -0
  68. package/lib/client/components/QaWidthHandle.js +147 -0
  69. package/lib/client/components/QaWidthHandle.js.map +1 -0
  70. package/lib/client/components/QaWorkGroup.js +19 -20
  71. package/lib/client/components/QaWorkGroup.js.map +1 -1
  72. package/lib/client/components/source-refs.js +78 -0
  73. package/lib/client/components/source-refs.js.map +1 -0
  74. package/lib/client/components/surface-utils.js +28 -0
  75. package/lib/client/components/surface-utils.js.map +1 -0
  76. package/lib/client/components/thinking-phrases.js +24 -0
  77. package/lib/client/components/thinking-phrases.js.map +1 -0
  78. package/lib/client/create-session.js +26 -0
  79. package/lib/client/create-session.js.map +1 -0
  80. package/lib/client/failure-copy.js +63 -0
  81. package/lib/client/failure-copy.js.map +1 -0
  82. package/lib/client/index.js +135 -16
  83. package/lib/client/index.js.map +1 -1
  84. package/lib/client/project-session-state.js +78 -0
  85. package/lib/client/project-session-state.js.map +1 -0
  86. package/lib/client/prompt-content.js +54 -0
  87. package/lib/client/prompt-content.js.map +1 -0
  88. package/lib/client/session-admission.js +38 -0
  89. package/lib/client/session-admission.js.map +1 -0
  90. package/lib/client/session-assets.js +52 -0
  91. package/lib/client/session-assets.js.map +1 -0
  92. package/lib/client/session-sources.js +61 -0
  93. package/lib/client/session-sources.js.map +1 -0
  94. package/lib/client/settings/card.js +143 -0
  95. package/lib/client/settings/card.js.map +1 -0
  96. package/lib/client/settings/fields.js +145 -0
  97. package/lib/client/settings/fields.js.map +1 -0
  98. package/lib/client/settings/format.js +255 -0
  99. package/lib/client/settings/format.js.map +1 -0
  100. package/lib/client/settings/sections/access.js +25 -0
  101. package/lib/client/settings/sections/access.js.map +1 -0
  102. package/lib/client/settings/sections/accounts.js +47 -0
  103. package/lib/client/settings/sections/accounts.js.map +1 -0
  104. package/lib/client/settings/sections/attachments.js +46 -0
  105. package/lib/client/settings/sections/attachments.js.map +1 -0
  106. package/lib/client/settings/sections/branding.js +28 -0
  107. package/lib/client/settings/sections/branding.js.map +1 -0
  108. package/lib/client/settings/sections/common.js +20 -0
  109. package/lib/client/settings/sections/common.js.map +1 -0
  110. package/lib/client/settings/sections/embedding.js +19 -0
  111. package/lib/client/settings/sections/embedding.js.map +1 -0
  112. package/lib/client/settings/sections/interface.js +60 -0
  113. package/lib/client/settings/sections/interface.js.map +1 -0
  114. package/lib/client/settings/sections/lockdown.js +82 -0
  115. package/lib/client/settings/sections/lockdown.js.map +1 -0
  116. package/lib/client/settings/sections/session.js +53 -0
  117. package/lib/client/settings/sections/session.js.map +1 -0
  118. package/lib/client/settings/sections/sources.js +72 -0
  119. package/lib/client/settings/sections/sources.js.map +1 -0
  120. package/lib/client/settings/sections/status.js +37 -0
  121. package/lib/client/settings/sections/status.js.map +1 -0
  122. package/lib/client/settings/sections.js +23 -0
  123. package/lib/client/settings/sections.js.map +1 -0
  124. package/lib/client/settings/styles.js +57 -0
  125. package/lib/client/settings/styles.js.map +1 -0
  126. package/lib/client/settlement.js +24 -0
  127. package/lib/client/settlement.js.map +1 -0
  128. package/lib/client/stream-publisher.js +47 -0
  129. package/lib/client/stream-publisher.js.map +1 -0
  130. package/lib/client/styles.js +225 -17
  131. package/lib/client/styles.js.map +1 -1
  132. package/lib/client/tool-presentation.js +102 -0
  133. package/lib/client/tool-presentation.js.map +1 -0
  134. package/lib/client/turn-sources.js +98 -0
  135. package/lib/client/turn-sources.js.map +1 -0
  136. package/lib/client/types.js +2 -0
  137. package/lib/client/types.js.map +1 -1
  138. package/lib/client/use-right-rail.js +96 -0
  139. package/lib/client/use-right-rail.js.map +1 -0
  140. package/lib/client/use-session-ui-state.js +75 -0
  141. package/lib/client/use-session-ui-state.js.map +1 -0
  142. package/lib/client/use-transcript-view.js +44 -0
  143. package/lib/client/use-transcript-view.js.map +1 -0
  144. package/lib/client.js +9052 -1345
  145. package/lib/client.js.map +1 -1
  146. package/lib/config-resolvers/accounts.js +60 -0
  147. package/lib/config-resolvers/accounts.js.map +1 -0
  148. package/lib/config-resolvers/attachments.js +28 -0
  149. package/lib/config-resolvers/attachments.js.map +1 -0
  150. package/lib/config-resolvers/basics.js +59 -0
  151. package/lib/config-resolvers/basics.js.map +1 -0
  152. package/lib/config-resolvers/defaults.js +132 -0
  153. package/lib/config-resolvers/defaults.js.map +1 -0
  154. package/lib/config-resolvers/lockdown.js +79 -0
  155. package/lib/config-resolvers/lockdown.js.map +1 -0
  156. package/lib/config-resolvers/session.js +42 -0
  157. package/lib/config-resolvers/session.js.map +1 -0
  158. package/lib/config-resolvers/shared.js +42 -0
  159. package/lib/config-resolvers/shared.js.map +1 -0
  160. package/lib/config-resolvers/sources.js +81 -0
  161. package/lib/config-resolvers/sources.js.map +1 -0
  162. package/lib/config-resolvers/ui.js +20 -0
  163. package/lib/config-resolvers/ui.js.map +1 -0
  164. package/lib/config.js +279 -102
  165. package/lib/config.js.map +1 -1
  166. package/lib/entry-redirect.js +45 -0
  167. package/lib/entry-redirect.js.map +1 -0
  168. package/lib/host-route.js +25 -1
  169. package/lib/host-route.js.map +1 -1
  170. package/lib/index.js +258 -23
  171. package/lib/index.js.map +1 -1
  172. package/lib/launch-token.js +42 -0
  173. package/lib/launch-token.js.map +1 -0
  174. package/lib/profile.js +214 -0
  175. package/lib/profile.js.map +1 -0
  176. package/lib/prompt-notes.js +231 -0
  177. package/lib/prompt-notes.js.map +1 -0
  178. package/lib/provenance/collector.js +40 -0
  179. package/lib/provenance/collector.js.map +1 -0
  180. package/lib/provenance/connectors.js +147 -0
  181. package/lib/provenance/connectors.js.map +1 -0
  182. package/lib/provenance/dedupe.js +89 -0
  183. package/lib/provenance/dedupe.js.map +1 -0
  184. package/lib/provenance/extractors.js +340 -0
  185. package/lib/provenance/extractors.js.map +1 -0
  186. package/lib/provenance/file-preview.js +39 -0
  187. package/lib/provenance/file-preview.js.map +1 -0
  188. package/lib/provenance/host-store.js +387 -0
  189. package/lib/provenance/host-store.js.map +1 -0
  190. package/lib/provenance/index.js +9 -0
  191. package/lib/provenance/index.js.map +1 -0
  192. package/lib/provenance/normalize.js +128 -0
  193. package/lib/provenance/normalize.js.map +1 -0
  194. package/lib/provenance/reported.js +53 -0
  195. package/lib/provenance/reported.js.map +1 -0
  196. package/lib/provenance/types.js +2 -0
  197. package/lib/provenance/types.js.map +1 -0
  198. package/lib/resolve-config.js +35 -249
  199. package/lib/resolve-config.js.map +1 -1
  200. package/lib/secure-session.js +122 -14
  201. package/lib/secure-session.js.map +1 -1
  202. package/lib/shared/session-key.js +18 -0
  203. package/lib/shared/session-key.js.map +1 -0
  204. package/lib/shared/settings.js +10 -0
  205. package/lib/shared/settings.js.map +1 -0
  206. package/lib/thinking-phrases.js +68 -0
  207. package/lib/thinking-phrases.js.map +1 -0
  208. package/lib/typert.host.js +749 -11
  209. package/lib/typert.remote-client.d.ts +23 -3
  210. package/lib/typert.remote-client.js +593 -6
  211. package/lib/types/account-remotes.d.ts +42 -0
  212. package/lib/types/accounts/credentials.d.ts +14 -0
  213. package/lib/types/accounts/errors.d.ts +11 -0
  214. package/lib/types/accounts/file.d.ts +82 -0
  215. package/lib/types/accounts/index.d.ts +2 -0
  216. package/lib/types/accounts/store.d.ts +144 -0
  217. package/lib/types/accounts/token.d.ts +31 -0
  218. package/lib/types/attachment-rules.d.ts +44 -0
  219. package/lib/types/attestation.d.ts +1 -1
  220. package/lib/types/cli.d.ts +9 -0
  221. package/lib/types/client/QaAccountsController.d.ts +102 -0
  222. package/lib/types/client/QaConfigController.d.ts +1 -1
  223. package/lib/types/client/QaSessionController.d.ts +61 -22
  224. package/lib/types/client/QaSurface.d.ts +14 -3
  225. package/lib/types/client/QaTranscriptAdapter.d.ts +5 -10
  226. package/lib/types/client/attachments.d.ts +54 -0
  227. package/lib/types/client/base64.d.ts +10 -0
  228. package/lib/types/client/chat-files.d.ts +30 -0
  229. package/lib/types/client/components/Markdown.d.ts +11 -1
  230. package/lib/types/client/components/QaAgentsDrawer.d.ts +1 -1
  231. package/lib/types/client/components/QaAuthGate.d.ts +15 -0
  232. package/lib/types/client/components/QaChangelog.d.ts +21 -0
  233. package/lib/types/client/components/QaComposer.d.ts +8 -4
  234. package/lib/types/client/components/QaFileAttachment.d.ts +14 -0
  235. package/lib/types/client/components/QaFilesPanel.d.ts +15 -0
  236. package/lib/types/client/components/QaHeader.d.ts +41 -0
  237. package/lib/types/client/components/QaMessage.d.ts +12 -3
  238. package/lib/types/client/components/QaModal.d.ts +26 -0
  239. package/lib/types/client/components/QaProfile.d.ts +25 -0
  240. package/lib/types/client/components/QaRightRail.d.ts +23 -0
  241. package/lib/types/client/components/QaSidebar.d.ts +44 -2
  242. package/lib/types/client/components/QaSourcesPanel.d.ts +23 -0
  243. package/lib/types/client/components/QaTurnRail.d.ts +65 -0
  244. package/lib/types/client/components/QaWelcomeNotice.d.ts +27 -0
  245. package/lib/types/client/components/QaWidthHandle.d.ts +43 -0
  246. package/lib/types/client/components/QaWorkGroup.d.ts +5 -3
  247. package/lib/types/client/components/source-refs.d.ts +16 -0
  248. package/lib/types/client/components/surface-utils.d.ts +11 -0
  249. package/lib/types/client/components/thinking-phrases.d.ts +9 -0
  250. package/lib/types/client/create-session.d.ts +13 -0
  251. package/lib/types/client/failure-copy.d.ts +19 -0
  252. package/lib/types/client/index.d.ts +1 -0
  253. package/lib/types/client/project-session-state.d.ts +30 -0
  254. package/lib/types/client/prompt-content.d.ts +30 -0
  255. package/lib/types/client/session-admission.d.ts +23 -0
  256. package/lib/types/client/session-assets.d.ts +20 -0
  257. package/lib/types/client/session-sources.d.ts +28 -0
  258. package/lib/types/client/settings/card.d.ts +23 -0
  259. package/lib/types/client/settings/fields.d.ts +139 -0
  260. package/lib/types/client/settings/format.d.ts +108 -0
  261. package/lib/types/client/settings/sections/access.d.ts +9 -0
  262. package/lib/types/client/settings/sections/accounts.d.ts +9 -0
  263. package/lib/types/client/settings/sections/attachments.d.ts +15 -0
  264. package/lib/types/client/settings/sections/branding.d.ts +8 -0
  265. package/lib/types/client/settings/sections/common.d.ts +28 -0
  266. package/lib/types/client/settings/sections/embedding.d.ts +8 -0
  267. package/lib/types/client/settings/sections/interface.d.ts +9 -0
  268. package/lib/types/client/settings/sections/lockdown.d.ts +9 -0
  269. package/lib/types/client/settings/sections/session.d.ts +9 -0
  270. package/lib/types/client/settings/sections/sources.d.ts +9 -0
  271. package/lib/types/client/settings/sections/status.d.ts +22 -0
  272. package/lib/types/client/settings/sections.d.ts +25 -0
  273. package/lib/types/client/settings/styles.d.ts +8 -0
  274. package/lib/types/client/settlement.d.ts +6 -0
  275. package/lib/types/client/stream-publisher.d.ts +28 -0
  276. package/lib/types/client/tool-presentation.d.ts +6 -0
  277. package/lib/types/client/turn-sources.d.ts +14 -0
  278. package/lib/types/client/types.d.ts +101 -9
  279. package/lib/types/client/use-right-rail.d.ts +56 -0
  280. package/lib/types/client/use-session-ui-state.d.ts +41 -0
  281. package/lib/types/client/use-transcript-view.d.ts +20 -0
  282. package/lib/types/config-resolvers/accounts.d.ts +15 -0
  283. package/lib/types/config-resolvers/attachments.d.ts +6 -0
  284. package/lib/types/config-resolvers/basics.d.ts +8 -0
  285. package/lib/types/config-resolvers/defaults.d.ts +4 -0
  286. package/lib/types/config-resolvers/lockdown.d.ts +10 -0
  287. package/lib/types/config-resolvers/session.d.ts +6 -0
  288. package/lib/types/config-resolvers/shared.d.ts +16 -0
  289. package/lib/types/config-resolvers/sources.d.ts +6 -0
  290. package/lib/types/config-resolvers/ui.d.ts +6 -0
  291. package/lib/types/entry-redirect.d.ts +20 -0
  292. package/lib/types/host-route.d.ts +14 -1
  293. package/lib/types/index.d.ts +54 -3
  294. package/lib/types/launch-token.d.ts +21 -0
  295. package/lib/types/profile.d.ts +76 -0
  296. package/lib/types/prompt-notes.d.ts +110 -0
  297. package/lib/types/provenance/collector.d.ts +26 -0
  298. package/lib/types/provenance/connectors.d.ts +11 -0
  299. package/lib/types/provenance/dedupe.d.ts +6 -0
  300. package/lib/types/provenance/extractors.d.ts +14 -0
  301. package/lib/types/provenance/file-preview.d.ts +10 -0
  302. package/lib/types/provenance/host-store.d.ts +54 -0
  303. package/lib/types/provenance/index.d.ts +10 -0
  304. package/lib/types/provenance/normalize.d.ts +13 -0
  305. package/lib/types/provenance/reported.d.ts +5 -0
  306. package/lib/types/provenance/types.d.ts +78 -0
  307. package/lib/types/resolve-config.d.ts +8 -3
  308. package/lib/types/secure-session.d.ts +36 -2
  309. package/lib/types/shared/session-key.d.ts +23 -0
  310. package/lib/types/shared/settings.d.ts +10 -0
  311. package/lib/types/thinking-phrases.d.ts +17 -0
  312. package/lib/types/types.d.ts +290 -16
  313. package/lib/types/user-workspace.d.ts +38 -0
  314. package/lib/user-workspace.js +231 -0
  315. package/lib/user-workspace.js.map +1 -0
  316. package/package.json +71 -47
  317. package/CHANGELOG.md +0 -93
  318. package/SPEC-dsh-qa-surface.md +0 -2425
  319. package/docs/ARCHITECTURE.md +0 -66
  320. package/docs/COMPATIBILITY.md +0 -14
  321. package/docs/CONFIGURATION.md +0 -112
  322. package/docs/EMBEDDING.md +0 -13
  323. package/lib/client/components/QaSourcesDrawer.js +0 -46
  324. package/lib/client/components/QaSourcesDrawer.js.map +0 -1
  325. package/lib/types/client/components/QaSourcesDrawer.d.ts +0 -8
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Phrases the running indicator cycles through. The list deliberately mixes
3
+ * Russian idiom with workshop imagery: a turn that keeps reshuffling the same
4
+ * thought in new words reads as alive, while a frozen one reads as stuck.
5
+ *
6
+ * Operators replace the list through `thinkingPhrases`; this module holds the
7
+ * canonical default so the config schema, the resolver and the browser all
8
+ * read one list.
9
+ */
10
+ export declare const DEFAULT_THINKING_PHRASES: readonly string[];
11
+ /**
12
+ * Phrase shown `elapsedMs` into a running turn. The list is operator supplied,
13
+ * so an empty one falls back to the default: the indicator always needs a
14
+ * label.
15
+ */
16
+ export declare function thinkingPhrase(elapsedMs: number, phrases?: readonly string[]): string;
17
+ //# sourceMappingURL=thinking-phrases.d.ts.map
@@ -1,7 +1,117 @@
1
+ import type { QaTurnSources } from "./provenance/types.js";
1
2
  export type QaSessionPolicy = "browser-persistent" | "new-on-load" | "fixed";
2
- export type QaSandboxMode = "read-only";
3
+ export type QaSandboxMode = "read-only" | "workspace-write";
3
4
  export type QaApprovalPolicy = "never";
4
5
  export type QaToolPolicyMode = "allow-list";
6
+ export type QaAccountRole = "user" | "admin";
7
+ /** The account fields projected to browsers; never includes credentials. */
8
+ export interface QaAccountUserPublic {
9
+ readonly id: string;
10
+ readonly email: string;
11
+ readonly displayName: string;
12
+ readonly role: QaAccountRole;
13
+ readonly createdAt: string;
14
+ readonly lastLoginAt: string | null;
15
+ readonly disabled: boolean;
16
+ /** Self-declared identity the QA prompt renders; empty until edited. */
17
+ readonly profile: QaAccountProfile;
18
+ }
19
+ /** One external system the deployment collects a handle for. */
20
+ export interface QaAccountIdentityField {
21
+ /** Stable lowercase key: the profile's storage key and the prompt's label. */
22
+ readonly key: string;
23
+ /** Operator copy shown in the profile form. */
24
+ readonly label: string;
25
+ }
26
+ /**
27
+ * What one account says about itself. Every value is the owner's own wording,
28
+ * which is why the prompt renders it as self-declared rather than as a
29
+ * verified directory attribute.
30
+ */
31
+ export interface QaAccountProfile {
32
+ /** Full name as the user writes it; empty when unset. */
33
+ readonly fullName: string;
34
+ /** External-system handles, keyed by the declared field key. */
35
+ readonly identities: Readonly<Record<string, string>>;
36
+ /** Free-form guidance about how the user wants answers; empty when unset. */
37
+ readonly instructions: string;
38
+ /** ISO timestamp of the last edit; null while the profile is untouched. */
39
+ readonly updatedAt: string | null;
40
+ }
41
+ /** Full-replace profile write: an absent value clears the stored one. */
42
+ export interface QaAccountProfileInput {
43
+ readonly fullName: string;
44
+ readonly identities: Readonly<Record<string, string>>;
45
+ readonly instructions: string;
46
+ }
47
+ /** One successful login/registration: the bearer token plus the user. */
48
+ export interface QaAccountSession {
49
+ readonly token: string;
50
+ readonly user: QaAccountUserPublic;
51
+ }
52
+ export type QaWhoamiResult = {
53
+ readonly authenticated: false;
54
+ } | {
55
+ readonly authenticated: true;
56
+ readonly user: QaAccountUserPublic;
57
+ };
58
+ /** Bulk ownership claim outcome for one browser's local chat index. */
59
+ export interface QaClaimResult {
60
+ readonly claimed: number;
61
+ /** Ids already owned by a different user; the browser drops them. */
62
+ readonly conflicts: readonly string[];
63
+ }
64
+ /**
65
+ * One chat-ownership entry as the admin views see it: the session, its owner
66
+ * and the owner's display name resolved at read time (a disabled account
67
+ * still names its chats).
68
+ */
69
+ export interface QaOwnershipEntry {
70
+ readonly sessionId: string;
71
+ readonly userId: string;
72
+ readonly displayName: string;
73
+ readonly claimedAt: string;
74
+ }
75
+ export interface QaSourcesConfig {
76
+ readonly enabled?: boolean;
77
+ readonly collect?: {
78
+ readonly parentAgent?: boolean;
79
+ readonly subagents?: boolean;
80
+ readonly persistTurnEvent?: boolean;
81
+ };
82
+ readonly display?: {
83
+ readonly sidebar?: boolean;
84
+ readonly footer?: boolean;
85
+ readonly groupByKind?: boolean;
86
+ readonly showDiscovered?: boolean;
87
+ readonly showOriginBadges?: boolean;
88
+ readonly maxInitiallyVisiblePerGroup?: number;
89
+ };
90
+ readonly webSearch?: {
91
+ readonly promoteSearchResultsWithoutFetch?: boolean;
92
+ readonly maxPromotedPerSearch?: number;
93
+ };
94
+ readonly dedupe?: {
95
+ readonly normalizeUrls?: boolean;
96
+ readonly stripTrackingParams?: boolean;
97
+ readonly mergeFileRanges?: boolean;
98
+ };
99
+ readonly filePreview?: {
100
+ readonly enabled?: boolean;
101
+ readonly markdownRenderedByDefault?: boolean;
102
+ readonly allowRawToggle?: boolean;
103
+ readonly maxBytes?: number;
104
+ readonly maxMarkdownRenderBytes?: number;
105
+ };
106
+ readonly subagents?: {
107
+ readonly inheritSources?: boolean;
108
+ readonly enableReportToolFallback?: boolean;
109
+ readonly markIncompleteOpaqueRuns?: boolean;
110
+ };
111
+ readonly legacy?: {
112
+ readonly parseAssistantSourcesBlock?: boolean;
113
+ };
114
+ }
5
115
  export interface QaSurfaceConfig {
6
116
  readonly enabled?: boolean;
7
117
  readonly route?: {
@@ -37,10 +147,16 @@ export interface QaSurfaceConfig {
37
147
  readonly showToolActivity?: boolean;
38
148
  readonly showReasoning?: boolean;
39
149
  readonly renderMarkdown?: boolean;
40
- readonly maxContentWidth?: number;
150
+ /** Floor for the visitor-resizable transcript/composer width; the page caps it. */
151
+ readonly minContentWidth?: number;
41
152
  readonly showSessionList?: boolean;
42
153
  };
43
154
  readonly suggestedQuestions?: readonly string[];
155
+ /**
156
+ * Phrases the running indicator cycles through. An empty list restores the
157
+ * built-in phrases: the indicator always carries a label.
158
+ */
159
+ readonly thinkingPhrases?: readonly string[];
44
160
  readonly interaction?: {
45
161
  readonly approvals?: "blocked";
46
162
  readonly questions?: "unsupported";
@@ -68,6 +184,59 @@ export interface QaSurfaceConfig {
68
184
  readonly embedding?: {
69
185
  readonly frameAncestors?: string | null;
70
186
  };
187
+ readonly accounts?: {
188
+ /** Opt-in; the QA gate stays off until the deployment turns this on. */
189
+ readonly enabled?: boolean;
190
+ readonly allowRegistration?: boolean;
191
+ readonly sessionTtlDays?: number;
192
+ /** Let admins see chats owned by other QA accounts. */
193
+ readonly showOtherUsersChats?: boolean;
194
+ /**
195
+ * Give every account a private cwd below the configured workspaceId.
196
+ * The child directory is not registered as a separate DSH workspace.
197
+ */
198
+ readonly perUserWorkspace?: boolean;
199
+ /** Self-declared profile: storage, the owner's form, and prompt injection. */
200
+ readonly profile?: {
201
+ readonly enabled?: boolean;
202
+ /** Render the profile into the QA agent's system prompt. */
203
+ readonly inject?: boolean;
204
+ /** External systems to collect a handle for; empty declares none. */
205
+ readonly identities?: readonly {
206
+ readonly key?: string;
207
+ readonly label?: string;
208
+ }[];
209
+ /** Character cap on the user's free-form agent guidance. */
210
+ readonly instructionsMaxLength?: number;
211
+ };
212
+ };
213
+ readonly entry?: {
214
+ /** Inject the root → /qa redirect for non-loopback hostnames. */
215
+ readonly redirectNonLoopback?: boolean;
216
+ /** Cookie-less /qa navigations go through the one-time ?token= exchange. */
217
+ readonly cookieBootstrap?: boolean;
218
+ };
219
+ readonly sources?: QaSourcesConfig;
220
+ readonly attachments?: QaAttachmentsConfig;
221
+ }
222
+ /** What a QA visitor may attach to one message. */
223
+ export interface QaAttachmentsConfig {
224
+ /** Offer text files (md, txt, log, …) next to images; off = images only. */
225
+ readonly textFiles?: boolean;
226
+ /**
227
+ * Line count above which pasted plain text becomes an attached file instead
228
+ * of landing in the input field. Zero never converts.
229
+ */
230
+ readonly pastedTextLines?: number;
231
+ /** Byte ceiling for one attached file; the composer refuses anything larger. */
232
+ readonly maxFileBytes?: number;
233
+ /**
234
+ * Pending attachments (images plus text files) allowed on one message. The
235
+ * Host keeps its own authority over assembled image batches.
236
+ */
237
+ readonly maxPending?: number;
238
+ /** Accepted text-file extensions, lowercase and without the dot. */
239
+ readonly extensions?: readonly string[];
71
240
  }
72
241
  export interface ResolvedQaSurfaceConfig {
73
242
  readonly enabled: boolean;
@@ -102,10 +271,11 @@ export interface ResolvedQaSurfaceConfig {
102
271
  readonly showToolActivity: boolean;
103
272
  readonly showReasoning: boolean;
104
273
  readonly renderMarkdown: boolean;
105
- readonly maxContentWidth: number;
274
+ readonly minContentWidth: number;
106
275
  readonly showSessionList: boolean;
107
276
  };
108
277
  readonly suggestedQuestions: readonly string[];
278
+ readonly thinkingPhrases: readonly string[];
109
279
  readonly interaction: {
110
280
  readonly approvals: "blocked";
111
281
  readonly questions: "unsupported";
@@ -133,6 +303,78 @@ export interface ResolvedQaSurfaceConfig {
133
303
  readonly embedding: {
134
304
  readonly frameAncestors: string | null;
135
305
  };
306
+ readonly accounts: {
307
+ readonly enabled: boolean;
308
+ readonly allowRegistration: boolean;
309
+ readonly sessionTtlDays: number;
310
+ readonly showOtherUsersChats: boolean;
311
+ readonly perUserWorkspace: boolean;
312
+ readonly profile: {
313
+ readonly enabled: boolean;
314
+ readonly inject: boolean;
315
+ readonly identities: readonly QaAccountIdentityField[];
316
+ readonly instructionsMaxLength: number;
317
+ };
318
+ };
319
+ readonly entry: {
320
+ readonly redirectNonLoopback: boolean;
321
+ readonly cookieBootstrap: boolean;
322
+ };
323
+ readonly sources: {
324
+ readonly enabled: boolean;
325
+ readonly collect: {
326
+ readonly parentAgent: boolean;
327
+ readonly subagents: boolean;
328
+ readonly persistTurnEvent: boolean;
329
+ };
330
+ readonly display: {
331
+ readonly sidebar: boolean;
332
+ readonly footer: boolean;
333
+ readonly groupByKind: boolean;
334
+ readonly showDiscovered: boolean;
335
+ readonly showOriginBadges: boolean;
336
+ readonly maxInitiallyVisiblePerGroup: number;
337
+ };
338
+ readonly webSearch: {
339
+ readonly promoteSearchResultsWithoutFetch: boolean;
340
+ readonly maxPromotedPerSearch: number;
341
+ };
342
+ readonly dedupe: {
343
+ readonly normalizeUrls: boolean;
344
+ readonly stripTrackingParams: boolean;
345
+ readonly mergeFileRanges: boolean;
346
+ };
347
+ readonly filePreview: {
348
+ readonly enabled: boolean;
349
+ readonly markdownRenderedByDefault: boolean;
350
+ readonly allowRawToggle: boolean;
351
+ readonly maxBytes: number;
352
+ readonly maxMarkdownRenderBytes: number;
353
+ };
354
+ readonly subagents: {
355
+ readonly inheritSources: boolean;
356
+ readonly enableReportToolFallback: boolean;
357
+ readonly markIncompleteOpaqueRuns: boolean;
358
+ };
359
+ readonly legacy: {
360
+ readonly parseAssistantSourcesBlock: boolean;
361
+ };
362
+ };
363
+ readonly attachments: {
364
+ readonly textFiles: boolean;
365
+ readonly pastedTextLines: number;
366
+ readonly maxFileBytes: number;
367
+ readonly maxPending: number;
368
+ readonly extensions: readonly string[];
369
+ };
370
+ }
371
+ export interface QaSourceFilePreview {
372
+ readonly path: string;
373
+ readonly content: string;
374
+ readonly size: number;
375
+ readonly truncated: boolean;
376
+ readonly markdown: boolean;
377
+ readonly renderableMarkdown: boolean;
136
378
  }
137
379
  /** Host-attested facts required before the QA composer may become writable. */
138
380
  export interface QaLockdownProof {
@@ -141,7 +383,7 @@ export interface QaLockdownProof {
141
383
  readonly agentPresetMatches: boolean;
142
384
  readonly workspaceMatches: boolean;
143
385
  readonly modelMatches: boolean;
144
- readonly sandboxIsReadOnly: boolean;
386
+ readonly sandboxModeMatches: boolean;
145
387
  readonly approvalIsNever: boolean;
146
388
  readonly permissionPreset: string;
147
389
  readonly toolPolicyLoaded: boolean;
@@ -154,6 +396,13 @@ export type QaMessage = {
154
396
  readonly status: "committed";
155
397
  readonly timestamp?: number;
156
398
  readonly images?: readonly QaImageView[];
399
+ /** Durable files the user attached to this message, in prompt order. */
400
+ readonly files?: readonly QaFileView[];
401
+ /**
402
+ * Chat owner's display name, set only for an admin reading a foreign
403
+ * chat; the owner themself sees their messages unlabeled.
404
+ */
405
+ readonly author?: string;
157
406
  } | {
158
407
  readonly id: string;
159
408
  readonly role: "assistant";
@@ -162,6 +411,10 @@ export type QaMessage = {
162
411
  readonly timestamp?: number;
163
412
  /** Host turn this answer belongs to; groups regenerations into variants. */
164
413
  readonly turn?: number;
414
+ /** Canonical evidence snapshot shared with this answer's source drawer. */
415
+ readonly sources?: readonly QaSource[];
416
+ readonly sourcesComplete?: boolean;
417
+ readonly incompleteSourceOrigins?: QaTurnSources["incompleteOrigins"];
165
418
  /** Host-recorded response timing, present on finalized messages only. */
166
419
  readonly stats?: {
167
420
  /** step start → final message. */
@@ -186,7 +439,8 @@ export type QaMessage = {
186
439
  readonly id: string;
187
440
  readonly role: "work";
188
441
  readonly turn: number;
189
- readonly status: "running" | "complete";
442
+ /** "error" marks a turn the host ended with a provider failure. */
443
+ readonly status: "running" | "complete" | "error";
190
444
  readonly startedAt?: number;
191
445
  readonly endedAt?: number;
192
446
  readonly items: readonly QaWorkItem[];
@@ -211,21 +465,14 @@ export type QaWorkItem = {
211
465
  readonly endedAt?: number;
212
466
  };
213
467
  export type QaSessionPhase = "idle" | "creating" | "ready" | "running" | "reconnecting" | "blocked" | "error";
214
- /** One tool-derived source shown in the sources drawer. */
215
- export interface QaSource {
216
- readonly id: string;
217
- readonly kind: "web" | "search" | "file";
218
- /** URL, filesystem path or search query as the model supplied it. */
219
- readonly target: string;
220
- readonly title: string;
221
- readonly snippet: string;
222
- /** The tool's full text output, capped for the detail pane. */
223
- readonly output: string;
224
- }
468
+ export type { QaSourceEvidence, QaSourceKind, QaSourceLocation, QaSourceOrigin, QaSourceReference, QaTurnSources, } from "./provenance/types.js";
469
+ /** Transitional UI name; the drawer now consumes the canonical source object. */
470
+ export type QaSource = import("./provenance/types.js").QaSourceReference;
225
471
  /** Raster formats the attachment path accepts (mirrors the host contract). */
226
472
  export type QaImageMediaType = "image/png" | "image/jpeg" | "image/webp" | "image/gif";
227
473
  /** One image pending in the composer, browser-owned until the prompt lands. */
228
474
  export interface QaImageDraft {
475
+ readonly kind: "image";
229
476
  readonly id: string;
230
477
  readonly mediaType: QaImageMediaType;
231
478
  readonly name: string;
@@ -234,11 +481,36 @@ export interface QaImageDraft {
234
481
  /** Local object URL for the preview thumbnail. */
235
482
  readonly previewUrl: string;
236
483
  }
484
+ /**
485
+ * One file pending in the composer. The bytes stay in the browser until the
486
+ * prompt is sent: an image can ride the prompt inline, but a file must be
487
+ * staged through the Host upload route first and then cited by its receipt.
488
+ */
489
+ export interface QaFileDraft {
490
+ readonly kind: "file";
491
+ readonly id: string;
492
+ readonly name: string;
493
+ readonly bytes: number;
494
+ /** Verbatim payload; a pasted blob is a File built from the pasted text. */
495
+ readonly blob: Blob;
496
+ }
497
+ /** Anything the composer may hold before the prompt is sent. */
498
+ export type QaAttachmentDraft = QaImageDraft | QaFileDraft;
237
499
  /** A durable image attached to a sent message. */
238
500
  export interface QaImageView {
239
501
  readonly attachmentId: string;
240
502
  readonly mediaType: QaImageMediaType;
241
503
  }
504
+ /**
505
+ * A durable file attached to a sent message. The browser never reads these
506
+ * bytes back (the attachment route serves images); the row shows the handle
507
+ * the model resolves, and the Host keeps the verbatim copy.
508
+ */
509
+ export interface QaFileView {
510
+ readonly attachmentId: string;
511
+ readonly name: string;
512
+ readonly bytes: number;
513
+ }
242
514
  /** A subagent transcript opened read-only from the agents panel. */
243
515
  export interface QaSubagentView {
244
516
  readonly id: string;
@@ -255,6 +527,8 @@ export interface QaSessionState {
255
527
  readonly chatsRevision: number;
256
528
  /** Tool-derived sources of the current chat (web targets and files read). */
257
529
  readonly sources: readonly QaSource[];
530
+ readonly sourcesComplete: boolean;
531
+ readonly incompleteSourceOrigins: QaTurnSources["incompleteOrigins"];
258
532
  /** Set while the bound session is a subagent watched from the panel. */
259
533
  readonly viewingSubagent: QaSubagentView | null;
260
534
  }
@@ -0,0 +1,38 @@
1
+ /** Stable, non-workspace child used to keep account directories out of sight. */
2
+ export declare const QA_USER_WORKSPACES_DIRECTORY = ".qa-users";
3
+ /** Conservative fixed budgets; there is deliberately no browser override. */
4
+ export declare const QA_USER_WORKSPACE_MAX_BYTES: number;
5
+ export declare const QA_USER_WORKSPACE_MAX_WRITE_BYTES: number;
6
+ export interface QaToolExecutionLike {
7
+ readonly name: string;
8
+ readonly arguments: unknown;
9
+ }
10
+ export declare function pathIsInside(target: string, root: string): boolean;
11
+ /**
12
+ * Resolve the deepest existing ancestor, preserving the missing suffix. This
13
+ * catches symlink escapes for reads and for files which a write is about to
14
+ * create.
15
+ */
16
+ export declare function canonicalCandidate(input: string): string;
17
+ /**
18
+ * Materialize `<registered workspace>/.qa-users/<account UUID>` without
19
+ * registering the child as a DSH Workspace. Both directory levels reject
20
+ * symlinks, and their real paths must stay below the configured workspace.
21
+ */
22
+ export declare function prepareQaUserWorkspace(workspaceRoot: string, userId: string): string;
23
+ /** Resolve an already provisioned account directory for admission checks. */
24
+ export declare function existingQaUserWorkspace(workspaceRoot: string, userId: string): string;
25
+ /**
26
+ * Monotonic guard used in addition to DSH's workspace-write sandbox. It
27
+ * fences model-controlled file paths for both reads and writes, rejects tools
28
+ * whose implementation can walk above cwd, and enforces bounded scratch use.
29
+ *
30
+ * `attachmentRoot` is the one deliberate read exemption: an uploaded file is
31
+ * an immutable content-addressed copy the deployment stores outside every
32
+ * workspace, and the model reaches it exactly as the harness intends (the
33
+ * prompt carries the stored path). Reads of a single such file are allowed;
34
+ * directory-wide tools stay confined because the store is shared by every
35
+ * account, and writes are never exempted.
36
+ */
37
+ export declare function qaUserWorkspaceDenial(execution: QaToolExecutionLike, root: string, attachmentRoot?: string): string | undefined;
38
+ //# sourceMappingURL=user-workspace.d.ts.map
@@ -0,0 +1,231 @@
1
+ import { chmodSync, lstatSync, mkdirSync, readdirSync, realpathSync, statSync, } from "node:fs";
2
+ import path from "node:path";
3
+ /** Stable, non-workspace child used to keep account directories out of sight. */
4
+ export const QA_USER_WORKSPACES_DIRECTORY = ".qa-users";
5
+ /** Conservative fixed budgets; there is deliberately no browser override. */
6
+ export const QA_USER_WORKSPACE_MAX_BYTES = 256 * 1024 * 1024;
7
+ export const QA_USER_WORKSPACE_MAX_WRITE_BYTES = 10 * 1024 * 1024;
8
+ const USER_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
9
+ const PATH_DENIAL = "QA workspace boundary: path is outside this user's directory.";
10
+ const QUOTA_DENIAL = "QA workspace boundary: storage quota exceeded.";
11
+ function samePath(left, right) {
12
+ return process.platform === "win32"
13
+ ? left.toLowerCase() === right.toLowerCase()
14
+ : left === right;
15
+ }
16
+ export function pathIsInside(target, root) {
17
+ const relative = path.relative(root, target);
18
+ return (relative === "" ||
19
+ (!relative.startsWith(`..${path.sep}`) &&
20
+ relative !== ".." &&
21
+ !path.isAbsolute(relative)));
22
+ }
23
+ /**
24
+ * Resolve the deepest existing ancestor, preserving the missing suffix. This
25
+ * catches symlink escapes for reads and for files which a write is about to
26
+ * create.
27
+ */
28
+ export function canonicalCandidate(input) {
29
+ const resolved = path.resolve(input);
30
+ const suffix = [];
31
+ let cursor = resolved;
32
+ for (;;) {
33
+ try {
34
+ return path.resolve(realpathSync.native(cursor), ...suffix);
35
+ }
36
+ catch (error) {
37
+ if (error.code !== "ENOENT")
38
+ throw error;
39
+ const parent = path.dirname(cursor);
40
+ if (parent === cursor)
41
+ throw error;
42
+ suffix.unshift(path.basename(cursor));
43
+ cursor = parent;
44
+ }
45
+ }
46
+ }
47
+ function assertPlainDirectory(input) {
48
+ const stat = lstatSync(input);
49
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
50
+ throw new Error(`QA user workspace must be a real directory: ${input}`);
51
+ }
52
+ }
53
+ /**
54
+ * Materialize `<registered workspace>/.qa-users/<account UUID>` without
55
+ * registering the child as a DSH Workspace. Both directory levels reject
56
+ * symlinks, and their real paths must stay below the configured workspace.
57
+ */
58
+ export function prepareQaUserWorkspace(workspaceRoot, userId) {
59
+ if (!USER_ID_PATTERN.test(userId)) {
60
+ throw new Error("QA account id is not safe for a workspace directory");
61
+ }
62
+ const canonicalWorkspace = realpathSync.native(workspaceRoot);
63
+ const usersRoot = path.join(canonicalWorkspace, QA_USER_WORKSPACES_DIRECTORY);
64
+ mkdirSync(usersRoot, { recursive: true, mode: 0o700 });
65
+ assertPlainDirectory(usersRoot);
66
+ if (process.platform !== "win32")
67
+ chmodSync(usersRoot, 0o700);
68
+ const canonicalUsersRoot = realpathSync.native(usersRoot);
69
+ if (!pathIsInside(canonicalUsersRoot, canonicalWorkspace)) {
70
+ throw new Error("QA user-workspace root escapes the configured workspace");
71
+ }
72
+ const userRoot = path.join(canonicalUsersRoot, userId);
73
+ mkdirSync(userRoot, { recursive: true, mode: 0o700 });
74
+ assertPlainDirectory(userRoot);
75
+ if (process.platform !== "win32")
76
+ chmodSync(userRoot, 0o700);
77
+ const canonicalUserRoot = realpathSync.native(userRoot);
78
+ if (!pathIsInside(canonicalUserRoot, canonicalUsersRoot) ||
79
+ !samePath(path.dirname(canonicalUserRoot), canonicalUsersRoot)) {
80
+ throw new Error("QA user workspace escapes its account directory");
81
+ }
82
+ return canonicalUserRoot;
83
+ }
84
+ /** Resolve an already provisioned account directory for admission checks. */
85
+ export function existingQaUserWorkspace(workspaceRoot, userId) {
86
+ if (!USER_ID_PATTERN.test(userId)) {
87
+ throw new Error("QA account id is not safe for a workspace directory");
88
+ }
89
+ const canonicalWorkspace = realpathSync.native(workspaceRoot);
90
+ const usersRoot = path.join(canonicalWorkspace, QA_USER_WORKSPACES_DIRECTORY);
91
+ const userRoot = path.join(usersRoot, userId);
92
+ assertPlainDirectory(usersRoot);
93
+ assertPlainDirectory(userRoot);
94
+ const canonicalUsersRoot = realpathSync.native(usersRoot);
95
+ const canonicalUserRoot = realpathSync.native(userRoot);
96
+ if (!pathIsInside(canonicalUsersRoot, canonicalWorkspace) ||
97
+ !pathIsInside(canonicalUserRoot, canonicalUsersRoot) ||
98
+ !samePath(path.dirname(canonicalUserRoot), canonicalUsersRoot)) {
99
+ throw new Error("QA user workspace escapes the configured workspace");
100
+ }
101
+ return canonicalUserRoot;
102
+ }
103
+ function record(value) {
104
+ return value !== null && typeof value === "object" && !Array.isArray(value)
105
+ ? value
106
+ : undefined;
107
+ }
108
+ function requestedPaths(execution) {
109
+ const args = record(execution.arguments);
110
+ if (args === undefined)
111
+ return [];
112
+ switch (execution.name) {
113
+ case "read":
114
+ case "read_image":
115
+ case "write":
116
+ case "edit": {
117
+ return typeof args.file_path === "string" ? [args.file_path] : [];
118
+ }
119
+ case "glob":
120
+ case "grep": {
121
+ return [typeof args.path === "string" ? args.path : "."];
122
+ }
123
+ case "str_replace_editor": {
124
+ return typeof args.path === "string" ? [args.path] : [];
125
+ }
126
+ default:
127
+ return [];
128
+ }
129
+ }
130
+ /**
131
+ * Whether the named path is only read. Directory-wide tools stay confined even
132
+ * for reads: the attachment store is shared by every account, and only the
133
+ * exact file an upload produced is meant to be reachable.
134
+ */
135
+ function isReadOnlyPathTool(execution) {
136
+ if (execution.name === "read" || execution.name === "read_image")
137
+ return true;
138
+ return (execution.name === "str_replace_editor" &&
139
+ record(execution.arguments)?.command === "view");
140
+ }
141
+ function directoryBytes(root, stopAfter) {
142
+ let total = 0;
143
+ const pending = [root];
144
+ while (pending.length > 0 && total <= stopAfter) {
145
+ const current = pending.pop();
146
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
147
+ const candidate = path.join(current, entry.name);
148
+ if (entry.isSymbolicLink())
149
+ continue;
150
+ if (entry.isDirectory())
151
+ pending.push(candidate);
152
+ else if (entry.isFile())
153
+ total += statSync(candidate).size;
154
+ if (total > stopAfter)
155
+ break;
156
+ }
157
+ }
158
+ return total;
159
+ }
160
+ function requestedWriteBytes(execution) {
161
+ const args = record(execution.arguments);
162
+ if (args === undefined)
163
+ return 0;
164
+ const values = [args.content, args.new_string, args.new_str];
165
+ return values.reduce((total, value) => total + (typeof value === "string" ? Buffer.byteLength(value) : 0), 0);
166
+ }
167
+ /**
168
+ * Monotonic guard used in addition to DSH's workspace-write sandbox. It
169
+ * fences model-controlled file paths for both reads and writes, rejects tools
170
+ * whose implementation can walk above cwd, and enforces bounded scratch use.
171
+ *
172
+ * `attachmentRoot` is the one deliberate read exemption: an uploaded file is
173
+ * an immutable content-addressed copy the deployment stores outside every
174
+ * workspace, and the model reaches it exactly as the harness intends (the
175
+ * prompt carries the stored path). Reads of a single such file are allowed;
176
+ * directory-wide tools stay confined because the store is shared by every
177
+ * account, and writes are never exempted.
178
+ */
179
+ export function qaUserWorkspaceDenial(execution, root, attachmentRoot) {
180
+ const args = record(execution.arguments);
181
+ if (args?.sandbox_permissions !== undefined ||
182
+ (execution.name === "edit" &&
183
+ (args?.replace_all === true || args?.replaceAll === true))) {
184
+ return PATH_DENIAL;
185
+ }
186
+ if (execution.name === "bash" ||
187
+ execution.name === "shell" ||
188
+ execution.name.startsWith("terminal_") ||
189
+ execution.name.startsWith("job_") ||
190
+ execution.name === "run_code" ||
191
+ execution.name === "lsp" ||
192
+ execution.name === "apply_patch" ||
193
+ execution.name.startsWith("dsh_git_")) {
194
+ return PATH_DENIAL;
195
+ }
196
+ const readsOnly = isReadOnlyPathTool(execution);
197
+ try {
198
+ for (const requested of requestedPaths(execution)) {
199
+ const absolute = path.isAbsolute(requested)
200
+ ? requested
201
+ : path.resolve(root, requested);
202
+ const canonical = canonicalCandidate(absolute);
203
+ if (pathIsInside(canonical, root))
204
+ continue;
205
+ // Resolved only for a path the fence would otherwise refuse, so the
206
+ // common case pays no extra filesystem walk.
207
+ if (readsOnly &&
208
+ attachmentRoot !== undefined &&
209
+ pathIsInside(canonical, canonicalCandidate(attachmentRoot))) {
210
+ continue;
211
+ }
212
+ return PATH_DENIAL;
213
+ }
214
+ if (execution.name === "write" ||
215
+ execution.name === "edit" ||
216
+ (execution.name === "str_replace_editor" &&
217
+ record(execution.arguments)?.command !== "view")) {
218
+ const requested = requestedWriteBytes(execution);
219
+ if (requested > QA_USER_WORKSPACE_MAX_WRITE_BYTES ||
220
+ directoryBytes(root, QA_USER_WORKSPACE_MAX_BYTES) + requested >
221
+ QA_USER_WORKSPACE_MAX_BYTES) {
222
+ return QUOTA_DENIAL;
223
+ }
224
+ }
225
+ return undefined;
226
+ }
227
+ catch {
228
+ return PATH_DENIAL;
229
+ }
230
+ }
231
+ //# sourceMappingURL=user-workspace.js.map