@yadsh/dsh-qa-surface 0.17.0 → 0.18.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 (185) hide show
  1. package/README.md +15 -3
  2. package/compatibility.json +1 -1
  3. package/lib/account-remotes.js +4 -0
  4. package/lib/account-remotes.js.map +1 -1
  5. package/lib/accounts/database.js +86 -1
  6. package/lib/accounts/database.js.map +1 -1
  7. package/lib/accounts/file.js.map +1 -1
  8. package/lib/accounts/store.js +111 -5
  9. package/lib/accounts/store.js.map +1 -1
  10. package/lib/accounts/token.js +8 -2
  11. package/lib/accounts/token.js.map +1 -1
  12. package/lib/admin/metrics.js +15 -2
  13. package/lib/admin/metrics.js.map +1 -1
  14. package/lib/admin/read-budget.js +28 -0
  15. package/lib/admin/read-budget.js.map +1 -0
  16. package/lib/admin/service.js +135 -40
  17. package/lib/admin/service.js.map +1 -1
  18. package/lib/admin/session-log.js +63 -4
  19. package/lib/admin/session-log.js.map +1 -1
  20. package/lib/chat-sharing/native-fence.js +20 -0
  21. package/lib/chat-sharing/native-fence.js.map +1 -1
  22. package/lib/chat-sharing/transport.js +8 -2
  23. package/lib/chat-sharing/transport.js.map +1 -1
  24. package/lib/chat-sharing/uploads.js +79 -6
  25. package/lib/chat-sharing/uploads.js.map +1 -1
  26. package/lib/cli.js +13 -0
  27. package/lib/cli.js.map +1 -1
  28. package/lib/client/QaAccountsController.js +61 -0
  29. package/lib/client/QaAccountsController.js.map +1 -1
  30. package/lib/client/QaRouteController.js +3 -1
  31. package/lib/client/QaRouteController.js.map +1 -1
  32. package/lib/client/QaSessionController.js +90 -26
  33. package/lib/client/QaSessionController.js.map +1 -1
  34. package/lib/client/QaSurface.js +40 -11
  35. package/lib/client/QaSurface.js.map +1 -1
  36. package/lib/client/account-sessions.js +74 -8
  37. package/lib/client/account-sessions.js.map +1 -1
  38. package/lib/client/admin/CapabilityPicker.js +50 -0
  39. package/lib/client/admin/CapabilityPicker.js.map +1 -0
  40. package/lib/client/admin/CoverageNotice.js +9 -0
  41. package/lib/client/admin/CoverageNotice.js.map +1 -0
  42. package/lib/client/admin/QaAdmin.js +3 -150
  43. package/lib/client/admin/QaAdmin.js.map +1 -1
  44. package/lib/client/admin/RoleEditor.js +46 -0
  45. package/lib/client/admin/RoleEditor.js.map +1 -0
  46. package/lib/client/admin/capability-selection.js +63 -0
  47. package/lib/client/admin/capability-selection.js.map +1 -0
  48. package/lib/client/admin/pages/Conversations.js +35 -17
  49. package/lib/client/admin/pages/Conversations.js.map +1 -1
  50. package/lib/client/admin/pages/Overview.js +3 -2
  51. package/lib/client/admin/pages/Overview.js.map +1 -1
  52. package/lib/client/admin/pages/Quality.js +77 -20
  53. package/lib/client/admin/pages/Quality.js.map +1 -1
  54. package/lib/client/admin/pages/SkillFiles.js +14 -3
  55. package/lib/client/admin/pages/SkillFiles.js.map +1 -1
  56. package/lib/client/admin/shared.js +68 -3
  57. package/lib/client/admin/shared.js.map +1 -1
  58. package/lib/client/components/QaAuthGate.js +31 -28
  59. package/lib/client/components/QaAuthGate.js.map +1 -1
  60. package/lib/client/components/QaChangelog.js +44 -0
  61. package/lib/client/components/QaChangelog.js.map +1 -1
  62. package/lib/client/components/QaComposer.js +27 -4
  63. package/lib/client/components/QaComposer.js.map +1 -1
  64. package/lib/client/components/QaFormItem.js +63 -0
  65. package/lib/client/components/QaFormItem.js.map +1 -0
  66. package/lib/client/components/QaInputForm.js +281 -0
  67. package/lib/client/components/QaInputForm.js.map +1 -0
  68. package/lib/client/components/QaModal.js +17 -4
  69. package/lib/client/components/QaModal.js.map +1 -1
  70. package/lib/client/forms-styles.js +14 -0
  71. package/lib/client/forms-styles.js.map +1 -0
  72. package/lib/client/forms.js +40 -0
  73. package/lib/client/forms.js.map +1 -0
  74. package/lib/client/index.js +15 -1
  75. package/lib/client/index.js.map +1 -1
  76. package/lib/client/oidc-browser.js +39 -0
  77. package/lib/client/oidc-browser.js.map +1 -0
  78. package/lib/client/panels/PanelHost.js +23 -1
  79. package/lib/client/panels/PanelHost.js.map +1 -1
  80. package/lib/client/settings/sections/interface.js +2 -1
  81. package/lib/client/settings/sections/interface.js.map +1 -1
  82. package/lib/client/settings/sections/status.js +3 -1
  83. package/lib/client/settings/sections/status.js.map +1 -1
  84. package/lib/client/styles.js +3 -3
  85. package/lib/client/types.js.map +1 -1
  86. package/lib/client.js +465 -72918
  87. package/lib/client.js.map +1 -1
  88. package/lib/config-resolvers/accounts.js +5 -4
  89. package/lib/config-resolvers/accounts.js.map +1 -1
  90. package/lib/config-resolvers/defaults.js +16 -0
  91. package/lib/config-resolvers/defaults.js.map +1 -1
  92. package/lib/config-resolvers/oidc.js +53 -0
  93. package/lib/config-resolvers/oidc.js.map +1 -0
  94. package/lib/config.js +71 -0
  95. package/lib/config.js.map +1 -1
  96. package/lib/forms/attachments.js +214 -0
  97. package/lib/forms/attachments.js.map +1 -0
  98. package/lib/forms/errors.js +37 -0
  99. package/lib/forms/errors.js.map +1 -0
  100. package/lib/forms/fingerprint.js +21 -0
  101. package/lib/forms/fingerprint.js.map +1 -0
  102. package/lib/forms/gate.js +125 -0
  103. package/lib/forms/gate.js.map +1 -0
  104. package/lib/forms/schema.js +182 -0
  105. package/lib/forms/schema.js.map +1 -0
  106. package/lib/forms/service.js +220 -0
  107. package/lib/forms/service.js.map +1 -0
  108. package/lib/forms/store.js +194 -0
  109. package/lib/forms/store.js.map +1 -0
  110. package/lib/forms/types.js +2 -0
  111. package/lib/forms/types.js.map +1 -0
  112. package/lib/forms/validation.js +113 -0
  113. package/lib/forms/validation.js.map +1 -0
  114. package/lib/index.js +227 -3
  115. package/lib/index.js.map +1 -1
  116. package/lib/oidc/http.js +30 -0
  117. package/lib/oidc/http.js.map +1 -0
  118. package/lib/oidc/service.js +250 -0
  119. package/lib/oidc/service.js.map +1 -0
  120. package/lib/qa-tools/activation-manager.js +24 -2
  121. package/lib/qa-tools/activation-manager.js.map +1 -1
  122. package/lib/qa-tools/catalog.js +11 -1
  123. package/lib/qa-tools/catalog.js.map +1 -1
  124. package/lib/qa-tools/index.js +27 -0
  125. package/lib/qa-tools/index.js.map +1 -1
  126. package/lib/qa-tools/request-user-form.js +121 -0
  127. package/lib/qa-tools/request-user-form.js.map +1 -0
  128. package/lib/resolve-config.js +2 -0
  129. package/lib/resolve-config.js.map +1 -1
  130. package/lib/secure-session.js +14 -8
  131. package/lib/secure-session.js.map +1 -1
  132. package/lib/typert.host.js +4587 -1101
  133. package/lib/typert.remote-client.d.ts +133 -3
  134. package/lib/typert.remote-client.js +4463 -1103
  135. package/lib/types/accounts/database.d.ts +8 -0
  136. package/lib/types/accounts/file.d.ts +2 -0
  137. package/lib/types/accounts/store.d.ts +17 -0
  138. package/lib/types/accounts/token.d.ts +3 -1
  139. package/lib/types/admin/metrics.d.ts +5 -1
  140. package/lib/types/admin/read-budget.d.ts +8 -0
  141. package/lib/types/admin/service.d.ts +17 -0
  142. package/lib/types/admin/session-log.d.ts +30 -1
  143. package/lib/types/chat-sharing/uploads.d.ts +2 -1
  144. package/lib/types/client/QaAccountsController.d.ts +4 -0
  145. package/lib/types/client/QaRouteController.d.ts +1 -0
  146. package/lib/types/client/QaSessionController.d.ts +12 -3
  147. package/lib/types/client/QaSurface.d.ts +2 -0
  148. package/lib/types/client/admin/CapabilityPicker.d.ts +14 -0
  149. package/lib/types/client/admin/CoverageNotice.d.ts +6 -0
  150. package/lib/types/client/admin/RoleEditor.d.ts +15 -0
  151. package/lib/types/client/admin/capability-selection.d.ts +27 -0
  152. package/lib/types/client/admin/shared.d.ts +39 -1
  153. package/lib/types/client/components/QaAuthGate.d.ts +2 -0
  154. package/lib/types/client/components/QaFormItem.d.ts +20 -0
  155. package/lib/types/client/components/QaInputForm.d.ts +18 -0
  156. package/lib/types/client/forms-styles.d.ts +2 -0
  157. package/lib/types/client/forms.d.ts +23 -0
  158. package/lib/types/client/oidc-browser.d.ts +12 -0
  159. package/lib/types/client/types.d.ts +7 -1
  160. package/lib/types/config-resolvers/oidc.d.ts +3 -0
  161. package/lib/types/forms/attachments.d.ts +48 -0
  162. package/lib/types/forms/errors.d.ts +11 -0
  163. package/lib/types/forms/fingerprint.d.ts +9 -0
  164. package/lib/types/forms/gate.d.ts +33 -0
  165. package/lib/types/forms/schema.d.ts +72 -0
  166. package/lib/types/forms/service.d.ts +15 -0
  167. package/lib/types/forms/store.d.ts +25 -0
  168. package/lib/types/forms/types.d.ts +215 -0
  169. package/lib/types/forms/validation.d.ts +20 -0
  170. package/lib/types/index.d.ts +28 -0
  171. package/lib/types/oidc/http.d.ts +5 -0
  172. package/lib/types/oidc/service.d.ts +30 -0
  173. package/lib/types/oidc.js +2 -0
  174. package/lib/types/oidc.js.map +1 -0
  175. package/lib/types/qa-tools/activation-manager.d.ts +5 -0
  176. package/lib/types/qa-tools/catalog.d.ts +4 -1
  177. package/lib/types/qa-tools/index.d.ts +5 -1
  178. package/lib/types/qa-tools/request-user-form.d.ts +5 -0
  179. package/lib/types/secure-session.d.ts +1 -0
  180. package/lib/types/types/accounts.d.ts +2 -0
  181. package/lib/types/types/admin.d.ts +6 -0
  182. package/lib/types/types/config.d.ts +6 -0
  183. package/lib/types/types/oidc.d.ts +36 -0
  184. package/lib/types/types.d.ts +2 -0
  185. package/package.json +57 -55
package/lib/index.js CHANGED
@@ -58,12 +58,19 @@ import { QaApprovalGate } from "./approvals.js";
58
58
  import { QaAttestationError, qaAttestationFailureMessage, } from "./attestation.js";
59
59
  import { QaFileDeleteGate } from "./qa-tools/file-delete-gate.js";
60
60
  import { QaQuestionGate } from "./questions.js";
61
+ import { QaFormStore } from "./forms/store.js";
62
+ import { QaFormService } from "./forms/service.js";
63
+ import { QaFormGate } from "./forms/gate.js";
64
+ import { QaFormAttachmentAdapter, } from "./forms/attachments.js";
65
+ import { formFailure, QaFormError } from "./forms/errors.js";
61
66
  import { QaSessionOwnership } from "./session-ownership.js";
62
67
  import { QaLlmIdentity } from "./llm-identity.js";
63
68
  import { registerTurnFailureLog } from "./turn-failure-log.js";
64
69
  import { entryRedirectRow } from "./entry-redirect.js";
65
70
  import { qaKioskDeployment } from "./ui-mode.js";
66
71
  import { registerQaNavigationRoute } from "./host-route.js";
72
+ import { QaOidcService, oidcRefusal } from "./oidc/service.js";
73
+ import { registerQaOidcRoute } from "./oidc/http.js";
67
74
  import { makeLaunchTokenSource } from "./launch-token.js";
68
75
  import { QaIntegrationPrincipalBindings } from "./integration-principals.js";
69
76
  import { registerQaIntegrationRoutes } from "./integration/http.js";
@@ -139,6 +146,9 @@ let QaSurface = (() => {
139
146
  let _instanceExtraInitializers = [];
140
147
  let _accountsRegister_decorators;
141
148
  let _accountsLogin_decorators;
149
+ let _accountsOidcStart_decorators;
150
+ let _accountsOidcComplete_decorators;
151
+ let _accountsOidcLogout_decorators;
142
152
  let _accountsChangePassword_decorators;
143
153
  let _accountsRequestPasswordReset_decorators;
144
154
  let _accountsWhoami_decorators;
@@ -219,6 +229,12 @@ let QaSurface = (() => {
219
229
  let _pendingApprovals_decorators;
220
230
  let _answerApproval_decorators;
221
231
  let _pendingQuestions_decorators;
232
+ let _listForms_decorators;
233
+ let _getForm_decorators;
234
+ let _mutateForm_decorators;
235
+ let _validateFormStep_decorators;
236
+ let _formCapabilities_decorators;
237
+ let _readFormAttachment_decorators;
222
238
  let _answerQuestion_decorators;
223
239
  let _cancelQuestion_decorators;
224
240
  let _readSourceFile_decorators;
@@ -243,6 +259,9 @@ let QaSurface = (() => {
243
259
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
244
260
  _accountsRegister_decorators = [Remote("accountsRegister")];
245
261
  _accountsLogin_decorators = [Remote("accountsLogin")];
262
+ _accountsOidcStart_decorators = [Remote("accountsOidcStart")];
263
+ _accountsOidcComplete_decorators = [Remote("accountsOidcComplete")];
264
+ _accountsOidcLogout_decorators = [Remote("accountsOidcLogout")];
246
265
  _accountsChangePassword_decorators = [Remote("accountsChangePassword")];
247
266
  _accountsRequestPasswordReset_decorators = [Remote("accountsRequestPasswordReset")];
248
267
  _accountsWhoami_decorators = [Remote("accountsWhoami")];
@@ -323,6 +342,12 @@ let QaSurface = (() => {
323
342
  _pendingApprovals_decorators = [Remote("pendingApprovals")];
324
343
  _answerApproval_decorators = [Remote("answerApproval")];
325
344
  _pendingQuestions_decorators = [Remote("pendingQuestions")];
345
+ _listForms_decorators = [Remote("listForms")];
346
+ _getForm_decorators = [Remote("getForm")];
347
+ _mutateForm_decorators = [Remote("mutateForm")];
348
+ _validateFormStep_decorators = [Remote("validateFormStep")];
349
+ _formCapabilities_decorators = [Remote("formCapabilities")];
350
+ _readFormAttachment_decorators = [Remote("readFormAttachment")];
326
351
  _answerQuestion_decorators = [Remote("answerQuestion")];
327
352
  _cancelQuestion_decorators = [Remote("cancelQuestion")];
328
353
  _readSourceFile_decorators = [Remote("readSourceFile")];
@@ -344,6 +369,9 @@ let QaSurface = (() => {
344
369
  _slashExecute_decorators = [Remote("slashExecute")];
345
370
  __esDecorate(this, null, _accountsRegister_decorators, { kind: "method", name: "accountsRegister", static: false, private: false, access: { has: obj => "accountsRegister" in obj, get: obj => obj.accountsRegister }, metadata: _metadata }, null, _instanceExtraInitializers);
346
371
  __esDecorate(this, null, _accountsLogin_decorators, { kind: "method", name: "accountsLogin", static: false, private: false, access: { has: obj => "accountsLogin" in obj, get: obj => obj.accountsLogin }, metadata: _metadata }, null, _instanceExtraInitializers);
372
+ __esDecorate(this, null, _accountsOidcStart_decorators, { kind: "method", name: "accountsOidcStart", static: false, private: false, access: { has: obj => "accountsOidcStart" in obj, get: obj => obj.accountsOidcStart }, metadata: _metadata }, null, _instanceExtraInitializers);
373
+ __esDecorate(this, null, _accountsOidcComplete_decorators, { kind: "method", name: "accountsOidcComplete", static: false, private: false, access: { has: obj => "accountsOidcComplete" in obj, get: obj => obj.accountsOidcComplete }, metadata: _metadata }, null, _instanceExtraInitializers);
374
+ __esDecorate(this, null, _accountsOidcLogout_decorators, { kind: "method", name: "accountsOidcLogout", static: false, private: false, access: { has: obj => "accountsOidcLogout" in obj, get: obj => obj.accountsOidcLogout }, metadata: _metadata }, null, _instanceExtraInitializers);
347
375
  __esDecorate(this, null, _accountsChangePassword_decorators, { kind: "method", name: "accountsChangePassword", static: false, private: false, access: { has: obj => "accountsChangePassword" in obj, get: obj => obj.accountsChangePassword }, metadata: _metadata }, null, _instanceExtraInitializers);
348
376
  __esDecorate(this, null, _accountsRequestPasswordReset_decorators, { kind: "method", name: "accountsRequestPasswordReset", static: false, private: false, access: { has: obj => "accountsRequestPasswordReset" in obj, get: obj => obj.accountsRequestPasswordReset }, metadata: _metadata }, null, _instanceExtraInitializers);
349
377
  __esDecorate(this, null, _accountsWhoami_decorators, { kind: "method", name: "accountsWhoami", static: false, private: false, access: { has: obj => "accountsWhoami" in obj, get: obj => obj.accountsWhoami }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -424,6 +452,12 @@ let QaSurface = (() => {
424
452
  __esDecorate(this, null, _pendingApprovals_decorators, { kind: "method", name: "pendingApprovals", static: false, private: false, access: { has: obj => "pendingApprovals" in obj, get: obj => obj.pendingApprovals }, metadata: _metadata }, null, _instanceExtraInitializers);
425
453
  __esDecorate(this, null, _answerApproval_decorators, { kind: "method", name: "answerApproval", static: false, private: false, access: { has: obj => "answerApproval" in obj, get: obj => obj.answerApproval }, metadata: _metadata }, null, _instanceExtraInitializers);
426
454
  __esDecorate(this, null, _pendingQuestions_decorators, { kind: "method", name: "pendingQuestions", static: false, private: false, access: { has: obj => "pendingQuestions" in obj, get: obj => obj.pendingQuestions }, metadata: _metadata }, null, _instanceExtraInitializers);
455
+ __esDecorate(this, null, _listForms_decorators, { kind: "method", name: "listForms", static: false, private: false, access: { has: obj => "listForms" in obj, get: obj => obj.listForms }, metadata: _metadata }, null, _instanceExtraInitializers);
456
+ __esDecorate(this, null, _getForm_decorators, { kind: "method", name: "getForm", static: false, private: false, access: { has: obj => "getForm" in obj, get: obj => obj.getForm }, metadata: _metadata }, null, _instanceExtraInitializers);
457
+ __esDecorate(this, null, _mutateForm_decorators, { kind: "method", name: "mutateForm", static: false, private: false, access: { has: obj => "mutateForm" in obj, get: obj => obj.mutateForm }, metadata: _metadata }, null, _instanceExtraInitializers);
458
+ __esDecorate(this, null, _validateFormStep_decorators, { kind: "method", name: "validateFormStep", static: false, private: false, access: { has: obj => "validateFormStep" in obj, get: obj => obj.validateFormStep }, metadata: _metadata }, null, _instanceExtraInitializers);
459
+ __esDecorate(this, null, _formCapabilities_decorators, { kind: "method", name: "formCapabilities", static: false, private: false, access: { has: obj => "formCapabilities" in obj, get: obj => obj.formCapabilities }, metadata: _metadata }, null, _instanceExtraInitializers);
460
+ __esDecorate(this, null, _readFormAttachment_decorators, { kind: "method", name: "readFormAttachment", static: false, private: false, access: { has: obj => "readFormAttachment" in obj, get: obj => obj.readFormAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
427
461
  __esDecorate(this, null, _answerQuestion_decorators, { kind: "method", name: "answerQuestion", static: false, private: false, access: { has: obj => "answerQuestion" in obj, get: obj => obj.answerQuestion }, metadata: _metadata }, null, _instanceExtraInitializers);
428
462
  __esDecorate(this, null, _cancelQuestion_decorators, { kind: "method", name: "cancelQuestion", static: false, private: false, access: { has: obj => "cancelQuestion" in obj, get: obj => obj.cancelQuestion }, metadata: _metadata }, null, _instanceExtraInitializers);
429
463
  __esDecorate(this, null, _readSourceFile_decorators, { kind: "method", name: "readSourceFile", static: false, private: false, access: { has: obj => "readSourceFile" in obj, get: obj => obj.readSourceFile }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -453,6 +487,13 @@ let QaSurface = (() => {
453
487
  approvals;
454
488
  fileDeleteGate;
455
489
  userQuestions;
490
+ formService;
491
+ formGate;
492
+ formAttachments;
493
+ forms() {
494
+ return (this.formService ??= new QaFormService(new QaFormStore(path.join(path.dirname(defaultAccountsFilePath()), "qa-forms.db")), () => this.getConfig().forms, (session) => this.accountRemotes.resolve(this.getConfig())?.ownerIdOf(session) ??
495
+ session));
496
+ }
456
497
  provenance;
457
498
  notes;
458
499
  /** Assigned after the admission gate; that gate only reads it per execution. */
@@ -481,6 +522,12 @@ let QaSurface = (() => {
481
522
  integrationPrincipals = new QaIntegrationPrincipalBindings();
482
523
  webServer;
483
524
  disposeRoute;
525
+ oidc = new QaOidcService({
526
+ config: () => this.getConfig(),
527
+ accounts: () => this.accountRemotes.resolve(this.getConfig()),
528
+ });
529
+ disposeOidcRoute;
530
+ oidcRouteKey;
484
531
  routeKey;
485
532
  /**
486
533
  * The HTTP API an external application asks questions through. Constructed
@@ -518,6 +565,7 @@ let QaSurface = (() => {
518
565
  pluginId: "dsh-qa-surface",
519
566
  consoleSink: createHostLoggerSink(ctx.logger),
520
567
  });
568
+ this.formAttachments = new QaFormAttachmentAdapter(() => this.forms(), () => ctx.get("attachments"));
521
569
  this.accountRemotes = createQaAccountRemotes({
522
570
  getConfig: () => this.getConfig(),
523
571
  logger: this.logger,
@@ -632,7 +680,7 @@ let QaSurface = (() => {
632
680
  }, "dsh-qa-surface.chat-sharing");
633
681
  ctx.inject(["typertGateway", "connection"], () => {
634
682
  const dispose = installQaNativeFence(ctx, () => this.getConfig().accounts.enabled);
635
- const disposeUploads = installQaChatUploads(ctx, () => this.sharing(), (token, id) => this.secureSession(token, id));
683
+ const disposeUploads = installQaChatUploads(ctx, () => this.sharing(), (token, id) => this.secureSession(token, id), this.formAttachments);
636
684
  return async () => {
637
685
  dispose();
638
686
  await disposeUploads();
@@ -671,7 +719,20 @@ let QaSurface = (() => {
671
719
  this.approvals = new QaApprovalGate(ctx, () => this.getConfig().interaction.approvals === "interactive", ownership, this.logger);
672
720
  const integrationQuestionPolicy = new QaIntegrationQuestionPolicy(ctx, () => this.accountRemotes.resolve(this.getConfig()), (id) => ownership.rootOf(id));
673
721
  ctx.effect(() => () => integrationQuestionPolicy.dispose(), "qa-surface: integration question policy");
674
- this.userQuestions = new QaQuestionGate(ctx, () => this.getConfig().interaction.questions === "interactive", ownership, this.logger, (agent) => integrationQuestionPolicy.allowed(agent));
722
+ this.userQuestions = new QaQuestionGate(ctx, () => this.getConfig().interaction.questions === "interactive", ownership, this.logger, (agent) => integrationQuestionPolicy.allowed(agent) &&
723
+ !this.formGate?.hasSession(ownership.rootOf(String(agent.session.id)) ?? ""));
724
+ this.formGate = new QaFormGate(() => this.forms(), (id) => ownership.rootOf(id), (id) => this.userQuestions.list(id).length > 0 ||
725
+ this.approvals.list(id).length > 0, (exec) => this.getConfig().forms.enabled &&
726
+ exec.agent !== undefined &&
727
+ integrationQuestionPolicy.allowed(exec.agent) &&
728
+ this.admission.knowsSession(String(exec.agent.session.id)), () => this.getConfig().accounts.enabled &&
729
+ this.ctx.get("fileUploads") !== undefined &&
730
+ this.formAttachments.available());
731
+ this.formGate.install(ctx);
732
+ ctx.effect(() => () => {
733
+ this.formGate.dispose();
734
+ this.formService?.store.close();
735
+ }, "dsh-qa-surface.forms");
675
736
  // The destructive tool of the QA catalog composes the same approval flow
676
737
  // around itself: registered without `prepend`, it sits inside the approval
677
738
  // gate and answers `ask` for every file_delete call of an attested chat,
@@ -722,6 +783,9 @@ let QaSurface = (() => {
722
783
  // The QA tool catalog is attached per agent, never at boot: nothing here
723
784
  // reaches the model until a managed agent loads the activation skill.
724
785
  this.tools = new QaTools(ctx, {
786
+ forms: this.formGate,
787
+ formsEnabled: () => this.getConfig().forms.enabled,
788
+ formProjection: (submission) => this.formAttachments.readable(submission),
725
789
  logger: this.logger,
726
790
  dynamicActivation: this.getConfig().tools.dynamicActivation,
727
791
  activationSkill: this.getConfig().tools.activationSkill,
@@ -734,6 +798,12 @@ let QaSurface = (() => {
734
798
  docsDefaultVersion: () => docsDefaultVersionOf(this.getConfig().tools),
735
799
  });
736
800
  ctx.effect(() => () => this.tools.dispose(), "dsh-qa-surface.qa-tools");
801
+ const listenVolatile = ctx.on.bind(ctx);
802
+ ctx.effect(() => listenVolatile("loader/volatile-update", () => {
803
+ this.tools.refreshForms();
804
+ if (!this.getConfig().forms.enabled)
805
+ this.formGate.cancelAll();
806
+ }), "dsh-qa-surface.form-policy");
737
807
  // The root index gains one head script: non-loopback hostnames continue
738
808
  // into /qa, the loopback operator keeps the full harness UI. Not in the
739
809
  // kiosk composition: there the index itself is served under /qa, so the
@@ -760,6 +830,7 @@ let QaSurface = (() => {
760
830
  const config = this.getConfig();
761
831
  this.refreshRoute();
762
832
  this.refreshIntegrationRoutes();
833
+ this.refreshOidcRoute();
763
834
  this.warnDocumentsMoved();
764
835
  this.warnLegacySlashDefaults();
765
836
  this.logger.info("config.updated", {
@@ -772,11 +843,16 @@ let QaSurface = (() => {
772
843
  this.webServer = webContext.webServer;
773
844
  this.refreshRoute();
774
845
  this.refreshIntegrationRoutes();
846
+ this.refreshOidcRoute();
775
847
  webContext.effect(() => () => {
776
848
  this.disposeRoute?.();
777
849
  this.disposeRoute = undefined;
778
850
  this.routeKey = undefined;
779
851
  this.disposeIntegrationRoutes?.();
852
+ this.disposeOidcRoute?.();
853
+ this.disposeOidcRoute = undefined;
854
+ this.oidcRouteKey = undefined;
855
+ this.oidc.clear();
780
856
  this.disposeIntegrationRoutes = undefined;
781
857
  this.integrationRouteKey = undefined;
782
858
  this.webServer = undefined;
@@ -827,6 +903,10 @@ let QaSurface = (() => {
827
903
  * cannot be opened must not fail the page that triggered it.
828
904
  */
829
905
  dropVanishedChats(sessionIds) {
906
+ for (const id of sessionIds) {
907
+ this.formGate?.cancel(id);
908
+ this.formService?.store.cancelSession(id);
909
+ }
830
910
  try {
831
911
  const rows = this.quality().dropConversations(sessionIds);
832
912
  this.logger.info("quality.vanished-conversations-dropped", {
@@ -927,6 +1007,17 @@ let QaSurface = (() => {
927
1007
  accountsLogin(email, password) {
928
1008
  return this.accountRemotes.login(email, password);
929
1009
  }
1010
+ accountsOidcStart() {
1011
+ if (!this.disposeOidcRoute)
1012
+ return Promise.reject(oidcRefusal());
1013
+ return this.oidc.start();
1014
+ }
1015
+ accountsOidcComplete(handoff, completionKey) {
1016
+ return this.oidc.complete(handoff, completionKey);
1017
+ }
1018
+ accountsOidcLogout(token) {
1019
+ return this.oidc.logout(token);
1020
+ }
930
1021
  /** Identity probe; safe to call with an empty or expired token. */
931
1022
  accountsChangePassword(token, currentPassword, nextPassword) {
932
1023
  return this.accountRemotes.changePassword(token, currentPassword, nextPassword);
@@ -1406,7 +1497,9 @@ let QaSurface = (() => {
1406
1497
  }
1407
1498
  async chatAudit(token, sessionId, full) {
1408
1499
  this.requireChatOwner(token, sessionId);
1409
- await this.secureSession(token, sessionId);
1500
+ // Sidebar summaries read stored audit metadata; they do not need an agent.
1501
+ if (full)
1502
+ await this.secureSession(token, sessionId);
1410
1503
  this.requireChatOwner(token, sessionId);
1411
1504
  const audit = this.ctx.get("sessionAudit");
1412
1505
  if (audit === undefined)
@@ -1523,6 +1616,115 @@ let QaSurface = (() => {
1523
1616
  return [];
1524
1617
  return this.userQuestions.list(sessionId);
1525
1618
  }
1619
+ /** Forms stay owner-scoped; UUIDs and a browser-supplied session never grant access. */
1620
+ async admitForm(token, sessionId) {
1621
+ try {
1622
+ this.requireChatOwner(token, sessionId);
1623
+ await this.secureSession(token, sessionId);
1624
+ this.requireChatOwner(token, sessionId);
1625
+ }
1626
+ catch {
1627
+ throw new QaFormError("ACCESS_DENIED", "Form access is unavailable.");
1628
+ }
1629
+ }
1630
+ async listForms(token, sessionId) {
1631
+ try {
1632
+ await this.admitForm(token, sessionId);
1633
+ if (!this.getConfig().forms.enabled)
1634
+ return { ok: true, value: [] };
1635
+ this.formGate.touch(sessionId);
1636
+ const service = this.forms();
1637
+ return {
1638
+ ok: true,
1639
+ value: service.store
1640
+ .list(sessionId)
1641
+ .map((r) => service.view(r, this.formGate.waiting(r.id))),
1642
+ };
1643
+ }
1644
+ catch (error) {
1645
+ return formFailure(error);
1646
+ }
1647
+ }
1648
+ async getForm(token, sessionId, formId) {
1649
+ try {
1650
+ await this.admitForm(token, sessionId);
1651
+ const s = this.forms();
1652
+ return {
1653
+ ok: true,
1654
+ value: s.view(s.get(sessionId, formId), this.formGate.waiting(formId)),
1655
+ };
1656
+ }
1657
+ catch (error) {
1658
+ return formFailure(error);
1659
+ }
1660
+ }
1661
+ async mutateForm(token, sessionId, command) {
1662
+ try {
1663
+ await this.admitForm(token, sessionId);
1664
+ const s = this.forms(), r = s.command(sessionId, command);
1665
+ this.logger.info("form.mutated", {
1666
+ sessionId,
1667
+ formId: r.id,
1668
+ action: command.action,
1669
+ revision: r.revision,
1670
+ status: r.status,
1671
+ });
1672
+ this.formGate.notify(r, command.action === "pause");
1673
+ return { ok: true, value: s.view(r, this.formGate.waiting(r.id)) };
1674
+ }
1675
+ catch (error) {
1676
+ return formFailure(error);
1677
+ }
1678
+ }
1679
+ async validateFormStep(token, sessionId, formId, stepId, revision) {
1680
+ try {
1681
+ await this.admitForm(token, sessionId);
1682
+ this.forms().step(sessionId, formId, stepId, revision);
1683
+ return { ok: true, value: null };
1684
+ }
1685
+ catch (error) {
1686
+ return formFailure(error);
1687
+ }
1688
+ }
1689
+ async formCapabilities() {
1690
+ return {
1691
+ enabled: this.getConfig().forms.enabled,
1692
+ schemaVersion: 1,
1693
+ upload: this.getConfig().accounts.enabled &&
1694
+ this.ctx.get("fileUploads") !== undefined &&
1695
+ this.formAttachments.available(),
1696
+ };
1697
+ }
1698
+ async readFormAttachment(token, sessionId, formId, resourceId) {
1699
+ try {
1700
+ await this.admitForm(token, sessionId);
1701
+ const s = this.forms(), r = s.get(sessionId, formId);
1702
+ const file = Object.values(r.attachments)
1703
+ .flat()
1704
+ .find((f) => f.resource_id === resourceId);
1705
+ if (!file)
1706
+ return {
1707
+ ok: false,
1708
+ error: {
1709
+ code: "ACCESS_DENIED",
1710
+ message: "File access is unavailable.",
1711
+ },
1712
+ };
1713
+ const data = s.store.resource(formId, resourceId);
1714
+ this.requireChatOwner(token, sessionId);
1715
+ return {
1716
+ ok: true,
1717
+ value: {
1718
+ name: file.name,
1719
+ media_type: file.media_type,
1720
+ data: Buffer.from(data).toString("base64"),
1721
+ },
1722
+ };
1723
+ }
1724
+ catch (error) {
1725
+ return formFailure(error);
1726
+ }
1727
+ }
1526
1728
  /**
1527
1729
  * Apply the operator's answers to one parked question request. Every question
1528
1730
  * of the request must be present in `answers`; one the browser omits reads as
@@ -1901,6 +2103,28 @@ let QaSurface = (() => {
1901
2103
  });
1902
2104
  }
1903
2105
  }
2106
+ refreshOidcRoute() {
2107
+ const config = this.getConfig();
2108
+ const key = config.enabled && config.accounts.enabled && config.accounts.oidc.enabled
2109
+ ? JSON.stringify([config.accounts.oidc, config.route.path])
2110
+ : undefined;
2111
+ if (key === this.oidcRouteKey)
2112
+ return;
2113
+ this.disposeOidcRoute?.();
2114
+ this.disposeOidcRoute = undefined;
2115
+ this.oidcRouteKey = undefined;
2116
+ this.oidc.clear();
2117
+ if (!key || !this.webServer)
2118
+ return;
2119
+ try {
2120
+ this.disposeOidcRoute = registerQaOidcRoute(this.webServer, config, this.oidc);
2121
+ this.oidcRouteKey = key;
2122
+ this.logger.info("oidc.route-registered", {});
2123
+ }
2124
+ catch {
2125
+ this.logger.error("oidc.route-unavailable", {});
2126
+ }
2127
+ }
1904
2128
  refreshRoute() {
1905
2129
  const config = this.getConfig();
1906
2130
  // Kiosk: the harness route policy owns the QA paths and serves the