@yadsh/dsh-qa-surface 0.15.1 → 0.15.3

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 (88) hide show
  1. package/lib/access/model.js +15 -0
  2. package/lib/access/model.js.map +1 -1
  3. package/lib/access/service.js +56 -16
  4. package/lib/access/service.js.map +1 -1
  5. package/lib/attestation.js.map +1 -1
  6. package/lib/client/QaAccountsController.js +3 -1
  7. package/lib/client/QaAccountsController.js.map +1 -1
  8. package/lib/client/QaSessionController.js +66 -18
  9. package/lib/client/QaSessionController.js.map +1 -1
  10. package/lib/client/QaSurface.js +64 -2
  11. package/lib/client/QaSurface.js.map +1 -1
  12. package/lib/client/QaTranscriptAdapter.js +46 -8
  13. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  14. package/lib/client/admin/pages/Users.js +2 -2
  15. package/lib/client/admin/pages/Users.js.map +1 -1
  16. package/lib/client/attestation.js +13 -5
  17. package/lib/client/attestation.js.map +1 -1
  18. package/lib/client/chat-artifacts.js +94 -0
  19. package/lib/client/chat-artifacts.js.map +1 -0
  20. package/lib/client/chat-files.js +24 -14
  21. package/lib/client/chat-files.js.map +1 -1
  22. package/lib/client/clipboard.js +86 -0
  23. package/lib/client/clipboard.js.map +1 -0
  24. package/lib/client/components/QaAuthGate.js +41 -5
  25. package/lib/client/components/QaAuthGate.js.map +1 -1
  26. package/lib/client/components/QaChangelog.js +46 -0
  27. package/lib/client/components/QaChangelog.js.map +1 -1
  28. package/lib/client/components/QaFileAttachment.js +7 -5
  29. package/lib/client/components/QaFileAttachment.js.map +1 -1
  30. package/lib/client/components/QaFilesPanel.js +7 -3
  31. package/lib/client/components/QaFilesPanel.js.map +1 -1
  32. package/lib/client/components/QaMessage.js +27 -24
  33. package/lib/client/components/QaMessage.js.map +1 -1
  34. package/lib/client/components/QaWorkspaceBrowser.js +18 -25
  35. package/lib/client/components/QaWorkspaceBrowser.js.map +1 -1
  36. package/lib/client/components/copy-hint.js +10 -0
  37. package/lib/client/components/copy-hint.js.map +1 -0
  38. package/lib/client/markdown/CodeBlock.js +6 -13
  39. package/lib/client/markdown/CodeBlock.js.map +1 -1
  40. package/lib/client/project-session-state.js +1 -0
  41. package/lib/client/project-session-state.js.map +1 -1
  42. package/lib/client/settings/styles.js +1 -1
  43. package/lib/client/styles.js +11 -4
  44. package/lib/client/styles.js.map +1 -1
  45. package/lib/client/user-settings/IntegrationTokensPage.js +4 -12
  46. package/lib/client/user-settings/IntegrationTokensPage.js.map +1 -1
  47. package/lib/client/user-settings/PasswordSettingsPage.js +6 -1
  48. package/lib/client/user-settings/PasswordSettingsPage.js.map +1 -1
  49. package/lib/client/workspace-download.js +32 -0
  50. package/lib/client/workspace-download.js.map +1 -0
  51. package/lib/client/workspace-paths.js +55 -0
  52. package/lib/client/workspace-paths.js.map +1 -0
  53. package/lib/client.js +727 -253
  54. package/lib/client.js.map +1 -1
  55. package/lib/enforcement/skill-policy.js +2 -1
  56. package/lib/enforcement/skill-policy.js.map +1 -1
  57. package/lib/index.js +26 -10
  58. package/lib/index.js.map +1 -1
  59. package/lib/secure-session.js +36 -11
  60. package/lib/secure-session.js.map +1 -1
  61. package/lib/slash/catalog.js.map +1 -1
  62. package/lib/typert.host.js +75 -75
  63. package/lib/typert.remote-client.js +75 -75
  64. package/lib/types/access/model.d.ts +13 -0
  65. package/lib/types/access/service.d.ts +26 -0
  66. package/lib/types/attestation.d.ts +1 -1
  67. package/lib/types/client/QaAccountsController.d.ts +2 -0
  68. package/lib/types/client/QaSessionController.d.ts +13 -0
  69. package/lib/types/client/QaTranscriptAdapter.d.ts +6 -0
  70. package/lib/types/client/attestation.d.ts +7 -3
  71. package/lib/types/client/chat-artifacts.d.ts +10 -0
  72. package/lib/types/client/chat-files.d.ts +13 -8
  73. package/lib/types/client/clipboard.d.ts +37 -0
  74. package/lib/types/client/components/QaAuthGate.d.ts +4 -2
  75. package/lib/types/client/components/QaFileAttachment.d.ts +13 -4
  76. package/lib/types/client/components/QaFilesPanel.d.ts +15 -2
  77. package/lib/types/client/components/QaMessage.d.ts +10 -2
  78. package/lib/types/client/components/QaWorkspaceBrowser.d.ts +7 -1
  79. package/lib/types/client/components/copy-hint.d.ts +8 -0
  80. package/lib/types/client/project-session-state.d.ts +5 -0
  81. package/lib/types/client/workspace-download.d.ts +16 -0
  82. package/lib/types/client/workspace-paths.d.ts +25 -0
  83. package/lib/types/index.d.ts +6 -4
  84. package/lib/types/secure-session.d.ts +4 -0
  85. package/lib/types/slash/catalog.d.ts +5 -0
  86. package/lib/types/types.d.ts +26 -0
  87. package/locale/en.json +6 -0
  88. package/package.json +5 -3
package/lib/client.js CHANGED
@@ -8721,7 +8721,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8721
8721
  },
8722
8722
  sourceLocation: {
8723
8723
  "file": "packages/dsh-qa-surface/src/index.ts",
8724
- "line": 1014,
8724
+ "line": 1034,
8725
8725
  "column": 9
8726
8726
  }
8727
8727
  },
@@ -8757,7 +8757,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8757
8757
  },
8758
8758
  sourceLocation: {
8759
8759
  "file": "packages/dsh-qa-surface/src/index.ts",
8760
- "line": 1025,
8760
+ "line": 1045,
8761
8761
  "column": 3
8762
8762
  }
8763
8763
  },
@@ -8784,7 +8784,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8784
8784
  },
8785
8785
  sourceLocation: {
8786
8786
  "file": "packages/dsh-qa-surface/src/index.ts",
8787
- "line": 999,
8787
+ "line": 1019,
8788
8788
  "column": 3
8789
8789
  }
8790
8790
  },
@@ -8833,7 +8833,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8833
8833
  },
8834
8834
  sourceLocation: {
8835
8835
  "file": "packages/dsh-qa-surface/src/index.ts",
8836
- "line": 1039,
8836
+ "line": 1059,
8837
8837
  "column": 3
8838
8838
  }
8839
8839
  },
@@ -8869,7 +8869,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8869
8869
  },
8870
8870
  sourceLocation: {
8871
8871
  "file": "packages/dsh-qa-surface/src/index.ts",
8872
- "line": 1005,
8872
+ "line": 1025,
8873
8873
  "column": 3
8874
8874
  }
8875
8875
  },
@@ -8905,7 +8905,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8905
8905
  },
8906
8906
  sourceLocation: {
8907
8907
  "file": "packages/dsh-qa-surface/src/index.ts",
8908
- "line": 1084,
8908
+ "line": 1104,
8909
8909
  "column": 3
8910
8910
  }
8911
8911
  },
@@ -8954,7 +8954,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8954
8954
  },
8955
8955
  sourceLocation: {
8956
8956
  "file": "packages/dsh-qa-surface/src/index.ts",
8957
- "line": 1061,
8957
+ "line": 1081,
8958
8958
  "column": 3
8959
8959
  }
8960
8960
  },
@@ -8990,7 +8990,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8990
8990
  },
8991
8991
  sourceLocation: {
8992
8992
  "file": "packages/dsh-qa-surface/src/index.ts",
8993
- "line": 1051,
8993
+ "line": 1071,
8994
8994
  "column": 3
8995
8995
  }
8996
8996
  },
@@ -9026,7 +9026,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9026
9026
  },
9027
9027
  sourceLocation: {
9028
9028
  "file": "packages/dsh-qa-surface/src/index.ts",
9029
- "line": 1073,
9029
+ "line": 1093,
9030
9030
  "column": 3
9031
9031
  }
9032
9032
  },
@@ -9075,7 +9075,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9075
9075
  },
9076
9076
  sourceLocation: {
9077
9077
  "file": "packages/dsh-qa-surface/src/index.ts",
9078
- "line": 1032,
9078
+ "line": 1052,
9079
9079
  "column": 3
9080
9080
  }
9081
9081
  },
@@ -9124,7 +9124,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9124
9124
  },
9125
9125
  sourceLocation: {
9126
9126
  "file": "packages/dsh-qa-surface/src/index.ts",
9127
- "line": 720,
9127
+ "line": 740,
9128
9128
  "column": 3
9129
9129
  }
9130
9130
  },
@@ -9160,7 +9160,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9160
9160
  },
9161
9161
  sourceLocation: {
9162
9162
  "file": "packages/dsh-qa-surface/src/index.ts",
9163
- "line": 754,
9163
+ "line": 774,
9164
9164
  "column": 3
9165
9165
  }
9166
9166
  },
@@ -9196,7 +9196,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9196
9196
  },
9197
9197
  sourceLocation: {
9198
9198
  "file": "packages/dsh-qa-surface/src/index.ts",
9199
- "line": 841,
9199
+ "line": 861,
9200
9200
  "column": 3
9201
9201
  }
9202
9202
  },
@@ -9223,7 +9223,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9223
9223
  },
9224
9224
  sourceLocation: {
9225
9225
  "file": "packages/dsh-qa-surface/src/index.ts",
9226
- "line": 775,
9226
+ "line": 795,
9227
9227
  "column": 3
9228
9228
  }
9229
9229
  },
@@ -9250,7 +9250,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9250
9250
  },
9251
9251
  sourceLocation: {
9252
9252
  "file": "packages/dsh-qa-surface/src/index.ts",
9253
- "line": 828,
9253
+ "line": 848,
9254
9254
  "column": 3
9255
9255
  }
9256
9256
  },
@@ -9286,7 +9286,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9286
9286
  },
9287
9287
  sourceLocation: {
9288
9288
  "file": "packages/dsh-qa-surface/src/index.ts",
9289
- "line": 714,
9289
+ "line": 734,
9290
9290
  "column": 3
9291
9291
  }
9292
9292
  },
@@ -9313,7 +9313,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9313
9313
  },
9314
9314
  sourceLocation: {
9315
9315
  "file": "packages/dsh-qa-surface/src/index.ts",
9316
- "line": 765,
9316
+ "line": 785,
9317
9317
  "column": 3
9318
9318
  }
9319
9319
  },
@@ -9349,7 +9349,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9349
9349
  },
9350
9350
  sourceLocation: {
9351
9351
  "file": "packages/dsh-qa-surface/src/index.ts",
9352
- "line": 709,
9352
+ "line": 729,
9353
9353
  "column": 3
9354
9354
  }
9355
9355
  },
@@ -9376,7 +9376,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9376
9376
  },
9377
9377
  sourceLocation: {
9378
9378
  "file": "packages/dsh-qa-surface/src/index.ts",
9379
- "line": 738,
9379
+ "line": 758,
9380
9380
  "column": 3
9381
9381
  }
9382
9382
  },
@@ -9412,7 +9412,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9412
9412
  },
9413
9413
  sourceLocation: {
9414
9414
  "file": "packages/dsh-qa-surface/src/index.ts",
9415
- "line": 854,
9415
+ "line": 874,
9416
9416
  "column": 3
9417
9417
  }
9418
9418
  },
@@ -9448,7 +9448,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9448
9448
  },
9449
9449
  sourceLocation: {
9450
9450
  "file": "packages/dsh-qa-surface/src/index.ts",
9451
- "line": 815,
9451
+ "line": 835,
9452
9452
  "column": 3
9453
9453
  }
9454
9454
  },
@@ -9484,7 +9484,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9484
9484
  },
9485
9485
  sourceLocation: {
9486
9486
  "file": "packages/dsh-qa-surface/src/index.ts",
9487
- "line": 788,
9487
+ "line": 808,
9488
9488
  "column": 3
9489
9489
  }
9490
9490
  },
@@ -9520,7 +9520,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9520
9520
  },
9521
9521
  sourceLocation: {
9522
9522
  "file": "packages/dsh-qa-surface/src/index.ts",
9523
- "line": 801,
9523
+ "line": 821,
9524
9524
  "column": 3
9525
9525
  }
9526
9526
  },
@@ -9547,7 +9547,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9547
9547
  },
9548
9548
  sourceLocation: {
9549
9549
  "file": "packages/dsh-qa-surface/src/index.ts",
9550
- "line": 744,
9550
+ "line": 764,
9551
9551
  "column": 3
9552
9552
  }
9553
9553
  },
@@ -9606,7 +9606,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9606
9606
  },
9607
9607
  sourceLocation: {
9608
9608
  "file": "packages/dsh-qa-surface/src/index.ts",
9609
- "line": 1456,
9609
+ "line": 1476,
9610
9610
  "column": 3
9611
9611
  }
9612
9612
  },
@@ -9642,7 +9642,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9642
9642
  },
9643
9643
  sourceLocation: {
9644
9644
  "file": "packages/dsh-qa-surface/src/index.ts",
9645
- "line": 1336,
9645
+ "line": 1356,
9646
9646
  "column": 3
9647
9647
  }
9648
9648
  },
@@ -9702,7 +9702,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9702
9702
  },
9703
9703
  sourceLocation: {
9704
9704
  "file": "packages/dsh-qa-surface/src/index.ts",
9705
- "line": 1316,
9705
+ "line": 1336,
9706
9706
  "column": 3
9707
9707
  }
9708
9708
  },
@@ -9738,7 +9738,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9738
9738
  },
9739
9739
  sourceLocation: {
9740
9740
  "file": "packages/dsh-qa-surface/src/index.ts",
9741
- "line": 1351,
9741
+ "line": 1371,
9742
9742
  "column": 3
9743
9743
  }
9744
9744
  },
@@ -9797,7 +9797,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9797
9797
  },
9798
9798
  sourceLocation: {
9799
9799
  "file": "packages/dsh-qa-surface/src/index.ts",
9800
- "line": 1361,
9800
+ "line": 1381,
9801
9801
  "column": 3
9802
9802
  }
9803
9803
  },
@@ -9833,7 +9833,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9833
9833
  },
9834
9834
  sourceLocation: {
9835
9835
  "file": "packages/dsh-qa-surface/src/index.ts",
9836
- "line": 1398,
9836
+ "line": 1418,
9837
9837
  "column": 3
9838
9838
  }
9839
9839
  },
@@ -9892,7 +9892,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9892
9892
  },
9893
9893
  sourceLocation: {
9894
9894
  "file": "packages/dsh-qa-surface/src/index.ts",
9895
- "line": 1210,
9895
+ "line": 1230,
9896
9896
  "column": 3
9897
9897
  }
9898
9898
  },
@@ -9941,7 +9941,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9941
9941
  },
9942
9942
  sourceLocation: {
9943
9943
  "file": "packages/dsh-qa-surface/src/index.ts",
9944
- "line": 1223,
9944
+ "line": 1243,
9945
9945
  "column": 3
9946
9946
  }
9947
9947
  },
@@ -10010,7 +10010,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10010
10010
  },
10011
10011
  sourceLocation: {
10012
10012
  "file": "packages/dsh-qa-surface/src/index.ts",
10013
- "line": 1196,
10013
+ "line": 1216,
10014
10014
  "column": 3
10015
10015
  }
10016
10016
  },
@@ -10037,7 +10037,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10037
10037
  },
10038
10038
  sourceLocation: {
10039
10039
  "file": "packages/dsh-qa-surface/src/index.ts",
10040
- "line": 1190,
10040
+ "line": 1210,
10041
10041
  "column": 3
10042
10042
  }
10043
10043
  },
@@ -10086,7 +10086,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10086
10086
  },
10087
10087
  sourceLocation: {
10088
10088
  "file": "packages/dsh-qa-surface/src/index.ts",
10089
- "line": 1241,
10089
+ "line": 1261,
10090
10090
  "column": 3
10091
10091
  }
10092
10092
  },
@@ -10114,7 +10114,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10114
10114
  },
10115
10115
  sourceLocation: {
10116
10116
  "file": "packages/dsh-qa-surface/src/index.ts",
10117
- "line": 1449,
10117
+ "line": 1469,
10118
10118
  "column": 3
10119
10119
  }
10120
10120
  },
@@ -10142,7 +10142,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10142
10142
  },
10143
10143
  sourceLocation: {
10144
10144
  "file": "packages/dsh-qa-surface/src/index.ts",
10145
- "line": 1260,
10145
+ "line": 1280,
10146
10146
  "column": 3
10147
10147
  }
10148
10148
  },
@@ -10169,7 +10169,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10169
10169
  },
10170
10170
  sourceLocation: {
10171
10171
  "file": "packages/dsh-qa-surface/src/index.ts",
10172
- "line": 1296,
10172
+ "line": 1316,
10173
10173
  "column": 3
10174
10174
  }
10175
10175
  },
@@ -10218,7 +10218,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10218
10218
  },
10219
10219
  sourceLocation: {
10220
10220
  "file": "packages/dsh-qa-surface/src/index.ts",
10221
- "line": 1426,
10221
+ "line": 1446,
10222
10222
  "column": 3
10223
10223
  }
10224
10224
  },
@@ -10277,7 +10277,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10277
10277
  },
10278
10278
  sourceLocation: {
10279
10279
  "file": "packages/dsh-qa-surface/src/index.ts",
10280
- "line": 1382,
10280
+ "line": 1402,
10281
10281
  "column": 3
10282
10282
  }
10283
10283
  },
@@ -10326,7 +10326,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10326
10326
  },
10327
10327
  sourceLocation: {
10328
10328
  "file": "packages/dsh-qa-surface/src/index.ts",
10329
- "line": 1305,
10329
+ "line": 1325,
10330
10330
  "column": 3
10331
10331
  }
10332
10332
  },
@@ -10376,7 +10376,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10376
10376
  },
10377
10377
  sourceLocation: {
10378
10378
  "file": "packages/dsh-qa-surface/src/index.ts",
10379
- "line": 1408,
10379
+ "line": 1428,
10380
10380
  "column": 3
10381
10381
  }
10382
10382
  },
@@ -10412,7 +10412,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10412
10412
  },
10413
10413
  sourceLocation: {
10414
10414
  "file": "packages/dsh-qa-surface/src/index.ts",
10415
- "line": 1441,
10415
+ "line": 1461,
10416
10416
  "column": 3
10417
10417
  }
10418
10418
  },
@@ -10461,7 +10461,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10461
10461
  },
10462
10462
  sourceLocation: {
10463
10463
  "file": "packages/dsh-qa-surface/src/index.ts",
10464
- "line": 1114,
10464
+ "line": 1134,
10465
10465
  "column": 3
10466
10466
  }
10467
10467
  },
@@ -10520,7 +10520,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10520
10520
  },
10521
10521
  sourceLocation: {
10522
10522
  "file": "packages/dsh-qa-surface/src/index.ts",
10523
- "line": 1142,
10523
+ "line": 1162,
10524
10524
  "column": 3
10525
10525
  }
10526
10526
  },
@@ -10556,7 +10556,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10556
10556
  },
10557
10557
  sourceLocation: {
10558
10558
  "file": "packages/dsh-qa-surface/src/index.ts",
10559
- "line": 1105,
10559
+ "line": 1125,
10560
10560
  "column": 3
10561
10561
  }
10562
10562
  },
@@ -10615,7 +10615,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10615
10615
  },
10616
10616
  sourceLocation: {
10617
10617
  "file": "packages/dsh-qa-surface/src/index.ts",
10618
- "line": 1129,
10618
+ "line": 1149,
10619
10619
  "column": 3
10620
10620
  }
10621
10621
  },
@@ -10651,7 +10651,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10651
10651
  },
10652
10652
  sourceLocation: {
10653
10653
  "file": "packages/dsh-qa-surface/src/index.ts",
10654
- "line": 1168,
10654
+ "line": 1188,
10655
10655
  "column": 9
10656
10656
  }
10657
10657
  },
@@ -10710,7 +10710,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10710
10710
  },
10711
10711
  sourceLocation: {
10712
10712
  "file": "packages/dsh-qa-surface/src/index.ts",
10713
- "line": 1155,
10713
+ "line": 1175,
10714
10714
  "column": 3
10715
10715
  }
10716
10716
  },
@@ -10759,7 +10759,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10759
10759
  },
10760
10760
  sourceLocation: {
10761
10761
  "file": "packages/dsh-qa-surface/src/index.ts",
10762
- "line": 1285,
10762
+ "line": 1305,
10763
10763
  "column": 3
10764
10764
  }
10765
10765
  },
@@ -10795,7 +10795,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10795
10795
  },
10796
10796
  sourceLocation: {
10797
10797
  "file": "packages/dsh-qa-surface/src/index.ts",
10798
- "line": 1279,
10798
+ "line": 1299,
10799
10799
  "column": 3
10800
10800
  }
10801
10801
  },
@@ -10854,7 +10854,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10854
10854
  },
10855
10855
  sourceLocation: {
10856
10856
  "file": "packages/dsh-qa-surface/src/index.ts",
10857
- "line": 1267,
10857
+ "line": 1287,
10858
10858
  "column": 3
10859
10859
  }
10860
10860
  },
@@ -10913,7 +10913,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10913
10913
  },
10914
10914
  sourceLocation: {
10915
10915
  "file": "packages/dsh-qa-surface/src/index.ts",
10916
- "line": 1532,
10916
+ "line": 1555,
10917
10917
  "column": 9
10918
10918
  }
10919
10919
  },
@@ -10972,7 +10972,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10972
10972
  },
10973
10973
  sourceLocation: {
10974
10974
  "file": "packages/dsh-qa-surface/src/index.ts",
10975
- "line": 1561,
10975
+ "line": 1584,
10976
10976
  "column": 9
10977
10977
  }
10978
10978
  },
@@ -11021,7 +11021,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11021
11021
  },
11022
11022
  sourceLocation: {
11023
11023
  "file": "packages/dsh-qa-surface/src/index.ts",
11024
- "line": 1573,
11024
+ "line": 1596,
11025
11025
  "column": 9
11026
11026
  }
11027
11027
  },
@@ -11070,7 +11070,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11070
11070
  },
11071
11071
  sourceLocation: {
11072
11072
  "file": "packages/dsh-qa-surface/src/index.ts",
11073
- "line": 900,
11073
+ "line": 920,
11074
11074
  "column": 9
11075
11075
  }
11076
11076
  },
@@ -11088,7 +11088,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11088
11088
  },
11089
11089
  sourceLocation: {
11090
11090
  "file": "packages/dsh-qa-surface/src/index.ts",
11091
- "line": 869,
11091
+ "line": 889,
11092
11092
  "column": 3
11093
11093
  }
11094
11094
  },
@@ -11137,7 +11137,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11137
11137
  },
11138
11138
  sourceLocation: {
11139
11139
  "file": "packages/dsh-qa-surface/src/index.ts",
11140
- "line": 1641,
11140
+ "line": 1664,
11141
11141
  "column": 9
11142
11142
  }
11143
11143
  },
@@ -11173,7 +11173,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11173
11173
  },
11174
11174
  sourceLocation: {
11175
11175
  "file": "packages/dsh-qa-surface/src/index.ts",
11176
- "line": 1518,
11176
+ "line": 1541,
11177
11177
  "column": 9
11178
11178
  }
11179
11179
  },
@@ -11209,7 +11209,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11209
11209
  },
11210
11210
  sourceLocation: {
11211
11211
  "file": "packages/dsh-qa-surface/src/index.ts",
11212
- "line": 1547,
11212
+ "line": 1570,
11213
11213
  "column": 9
11214
11214
  }
11215
11215
  },
@@ -11258,7 +11258,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11258
11258
  },
11259
11259
  sourceLocation: {
11260
11260
  "file": "packages/dsh-qa-surface/src/index.ts",
11261
- "line": 1702,
11261
+ "line": 1725,
11262
11262
  "column": 9
11263
11263
  }
11264
11264
  },
@@ -11276,7 +11276,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11276
11276
  },
11277
11277
  sourceLocation: {
11278
11278
  "file": "packages/dsh-qa-surface/src/index.ts",
11279
- "line": 887,
11279
+ "line": 907,
11280
11280
  "column": 3
11281
11281
  }
11282
11282
  },
@@ -11325,7 +11325,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11325
11325
  },
11326
11326
  sourceLocation: {
11327
11327
  "file": "packages/dsh-qa-surface/src/index.ts",
11328
- "line": 1592,
11328
+ "line": 1615,
11329
11329
  "column": 9
11330
11330
  }
11331
11331
  },
@@ -11374,7 +11374,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11374
11374
  },
11375
11375
  sourceLocation: {
11376
11376
  "file": "packages/dsh-qa-surface/src/index.ts",
11377
- "line": 1668,
11377
+ "line": 1691,
11378
11378
  "column": 9
11379
11379
  }
11380
11380
  },
@@ -11410,7 +11410,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11410
11410
  },
11411
11411
  sourceLocation: {
11412
11412
  "file": "packages/dsh-qa-surface/src/index.ts",
11413
- "line": 1469,
11413
+ "line": 1489,
11414
11414
  "column": 9
11415
11415
  }
11416
11416
  },
@@ -11446,7 +11446,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11446
11446
  },
11447
11447
  sourceLocation: {
11448
11448
  "file": "packages/dsh-qa-surface/src/index.ts",
11449
- "line": 1772,
11449
+ "line": 1795,
11450
11450
  "column": 3
11451
11451
  }
11452
11452
  },
@@ -11482,7 +11482,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11482
11482
  },
11483
11483
  sourceLocation: {
11484
11484
  "file": "packages/dsh-qa-surface/src/index.ts",
11485
- "line": 1766,
11485
+ "line": 1789,
11486
11486
  "column": 3
11487
11487
  }
11488
11488
  },
@@ -11509,7 +11509,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11509
11509
  },
11510
11510
  sourceLocation: {
11511
11511
  "file": "packages/dsh-qa-surface/src/index.ts",
11512
- "line": 1760,
11512
+ "line": 1783,
11513
11513
  "column": 3
11514
11514
  }
11515
11515
  },
@@ -11558,7 +11558,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11558
11558
  },
11559
11559
  sourceLocation: {
11560
11560
  "file": "packages/dsh-qa-surface/src/index.ts",
11561
- "line": 1791,
11561
+ "line": 1814,
11562
11562
  "column": 3
11563
11563
  }
11564
11564
  },
@@ -11585,7 +11585,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11585
11585
  },
11586
11586
  sourceLocation: {
11587
11587
  "file": "packages/dsh-qa-surface/src/index.ts",
11588
- "line": 1817,
11588
+ "line": 1840,
11589
11589
  "column": 3
11590
11590
  }
11591
11591
  },
@@ -11634,7 +11634,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11634
11634
  },
11635
11635
  sourceLocation: {
11636
11636
  "file": "packages/dsh-qa-surface/src/index.ts",
11637
- "line": 1781,
11637
+ "line": 1804,
11638
11638
  "column": 3
11639
11639
  }
11640
11640
  },
@@ -11683,7 +11683,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11683
11683
  },
11684
11684
  sourceLocation: {
11685
11685
  "file": "packages/dsh-qa-surface/src/index.ts",
11686
- "line": 1804,
11686
+ "line": 1827,
11687
11687
  "column": 3
11688
11688
  }
11689
11689
  },
@@ -11719,7 +11719,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11719
11719
  },
11720
11720
  sourceLocation: {
11721
11721
  "file": "packages/dsh-qa-surface/src/index.ts",
11722
- "line": 1834,
11722
+ "line": 1857,
11723
11723
  "column": 3
11724
11724
  }
11725
11725
  },
@@ -11778,7 +11778,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11778
11778
  },
11779
11779
  sourceLocation: {
11780
11780
  "file": "packages/dsh-qa-surface/src/index.ts",
11781
- "line": 1849,
11781
+ "line": 1872,
11782
11782
  "column": 3
11783
11783
  }
11784
11784
  },
@@ -11814,7 +11814,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11814
11814
  },
11815
11815
  sourceLocation: {
11816
11816
  "file": "packages/dsh-qa-surface/src/index.ts",
11817
- "line": 1505,
11817
+ "line": 1528,
11818
11818
  "column": 9
11819
11819
  }
11820
11820
  }
@@ -13428,7 +13428,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
13428
13428
  case "account-disabled": return "Аккаунт отключён администратором.";
13429
13429
  case "email-taken": return "Этот email уже зарегистрирован.";
13430
13430
  case "invalid-email": return "Введите корректный email.";
13431
- case "weak-password": return "Пароль должен быть не короче 8 символов.";
13431
+ case "weak-password": return `Пароль должен быть не короче 8 символов.`;
13432
13432
  case "invalid-display-name": return "Слишком длинное имя.";
13433
13433
  case "invalid-profile": return "Проверьте поля профиля: значение слишком длинное или недопустимое.";
13434
13434
  case "profile-disabled": return "Профиль отключён на этом сервере.";
@@ -14517,12 +14517,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14517
14517
  }
14518
14518
  };
14519
14519
  /**
14520
- * Refusals that classify an existing chat as belonging to an older deployment
14521
- * composition. Keeping only its transcript is safe: policyReady stays false,
14522
- * so no prompt, cancel, approval or question reaches the Host.
14520
+ * Refusals that classify what the browser has on screen rather than faulting
14521
+ * the deployment: an existing chat of an older composition, one adopted from
14522
+ * outside the QA policy, or an identity that is another conversation's
14523
+ * delegated child.
14524
+ *
14525
+ * Keeping only its transcript is safe: policyReady stays false, so no prompt,
14526
+ * cancel, approval or question reaches the Host.
14523
14527
  */
14524
14528
  function canOpenAsCompatibilityReadOnly(reason) {
14525
- return reason === "composition-mismatch" || reason === "agent-unavailable" || reason === "adoption-refused";
14529
+ return reason === "composition-mismatch" || reason === "agent-unavailable" || reason === "adoption-refused" || reason === "subagent-session";
14526
14530
  }
14527
14531
  /**
14528
14532
  * Extract the coarse attestation reason code from a Host wire failure, if the
@@ -14533,7 +14537,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14533
14537
  return reason === void 0 ? null : reason;
14534
14538
  }
14535
14539
  function attestationHint(reason) {
14536
- if (reason === "agent-unavailable") return "The Host could not put a live agent behind this chat: resuming the composition its session recorded failed (a preset that no longer mounts does this). The refusal detail is in the Host logs.";
14540
+ if (reason === "agent-unavailable") return "This chat's transcript is on the Host, but no agent stands behind it: resuming the composition its session recorded failed — a preset that no longer mounts, or a log the Host refuses to read. That is not what a restarted stand looks like; after a restart the next open simply resumes the chat. The failing session is named in the Host log under `session.agent-resolve-rejected` — re-mount the preset that session recorded and re-open the chat, while New chat gives the visitor a working one meanwhile.";
14541
+ if (reason === "subagent-session") return "The id this page asked about is a delegated subagent run, not a chat: subagent routing owns that identity, so the Host puts no sendable agent behind it and nothing can be attested here. Read the run from its parent chat's work group — its sources already reach that chat. If a chat row of this browser leads here, it points at a child: start a new chat and leave that id behind.";
14537
14542
  if (reason === "unknown-tools") return "A lockdown.toolPolicy name is not mounted in this session's tool catalog — check the deployment agent preset and the tool's server availability.";
14538
14543
  if (reason === "workspace-unavailable") return "The configured session.workspaceId does not match a registered workspace - create the workspace or fix the id (session.cwd is the no-registry alternative).";
14539
14544
  if (reason === "composition-mismatch") return "The session's agent preset, workspace or model no longer matches the deployment QA config.";
@@ -15157,6 +15162,97 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15157
15162
  });
15158
15163
  }
15159
15164
  //#endregion
15165
+ //#region src/client/chat-artifacts.ts
15166
+ /**
15167
+ * The files one turn produced, read out of the tool results that produced them.
15168
+ *
15169
+ * A document the agent made is the result of the conversation, but nothing
15170
+ * carries it into the transcript: it is a file in the chat's own workspace, and
15171
+ * the only trace it leaves is the line the producing tool rendered. Reading
15172
+ * those lines is what turns a paragraph that mentions a file into a card a
15173
+ * person can open and download, so the reader does not have to know the files
15174
+ * tab exists to receive what they asked for.
15175
+ *
15176
+ * The line shape belongs to the tools of `@yadsh/dsh-documents`, which report
15177
+ * every produced file as `<format>: <path> (<size>, sha256 …)`. The path is the
15178
+ * workspace-relative name the tool was asked to report: an absolute path would
15179
+ * both refuse at the workspace fence and print the account's own directory into
15180
+ * the answer, so a line carrying one is ignored rather than rendered.
15181
+ */
15182
+ /** The tools that hand a file to the user, and the formats each reports. */
15183
+ const PRODUCED_FORMATS = Object.freeze({
15184
+ document_create: ["docx", "pdf"],
15185
+ document_convert: [
15186
+ "docx",
15187
+ "pdf",
15188
+ "md"
15189
+ ],
15190
+ document_to_markdown: ["markdown"],
15191
+ document_from_url: ["markdown"]
15192
+ });
15193
+ /** One reported file: `<format>: <path>` with an optional `(<size>, …)`. */
15194
+ const FILE_LINE = /^([a-z][a-z0-9]*):\s+(\S+)(?:\s+\(([^)]*)\))?$/u;
15195
+ /** A path spelled from the filesystem root, which the fence would refuse. */
15196
+ function isAbsolutePath(value) {
15197
+ return value.startsWith("/") || value.startsWith("\\") || /^[A-Za-z]:[\\/]/u.test(value);
15198
+ }
15199
+ /** `12.3 KiB` back to bytes; an unparseable size reads as unknown. */
15200
+ const UNIT_SCALES = Object.freeze({
15201
+ b: 1,
15202
+ kib: 1024,
15203
+ mib: 1048576,
15204
+ gib: 1073741824
15205
+ });
15206
+ function reportedBytes(details) {
15207
+ const size = /^([\d.]+)\s*([A-Za-z]+)$/u.exec(details?.split(",")[0]?.trim() ?? "");
15208
+ if (size === null) return 0;
15209
+ const value = Number.parseFloat(size[1] ?? "");
15210
+ const scale = UNIT_SCALES[(size[2] ?? "").toLowerCase()];
15211
+ if (!Number.isFinite(value) || scale === void 0) return 0;
15212
+ return Math.round(value * scale);
15213
+ }
15214
+ /** Last segment of a reported path, spelled with forward slashes. */
15215
+ function baseName(path) {
15216
+ const segments = path.split(/[/\\]/u).filter((segment) => segment !== "");
15217
+ return segments[segments.length - 1] ?? path;
15218
+ }
15219
+ /**
15220
+ * Whether a reported name is a file. A produced document always carries its
15221
+ * extension, while a prose line that happens to start with a format word — a
15222
+ * summary the tool wrote, a translation of the status — does not. Reading the
15223
+ * one as the other would card a sentence.
15224
+ */
15225
+ function isFileName(value) {
15226
+ return /^[^./\\][^/\\]*\.[A-Za-z][A-Za-z0-9]{0,9}$/u.test(baseName(value));
15227
+ }
15228
+ /**
15229
+ * The files one tool result reports, in the order it reported them.
15230
+ *
15231
+ * `output` is the rendered text of the result, the same string the work group
15232
+ * shows. A tool that produced nothing — a read, an inspection, a refusal — is
15233
+ * not in the table and contributes no artifact.
15234
+ */
15235
+ function artifactsOfToolResult(toolName, output) {
15236
+ const formats = PRODUCED_FORMATS[toolName];
15237
+ if (formats === void 0 || output === null) return [];
15238
+ const artifacts = [];
15239
+ for (const line of output.split(/\r?\n/u)) {
15240
+ const reported = FILE_LINE.exec(line.trim());
15241
+ if (reported === null) continue;
15242
+ const format = reported[1] ?? "";
15243
+ const path = reported[2] ?? "";
15244
+ if (!formats.includes(format)) continue;
15245
+ if (path === "failed" || !isFileName(path) || isAbsolutePath(path)) continue;
15246
+ artifacts.push({
15247
+ path,
15248
+ name: baseName(path),
15249
+ format,
15250
+ bytes: reportedBytes(reported[3])
15251
+ });
15252
+ }
15253
+ return artifacts;
15254
+ }
15255
+ //#endregion
15160
15256
  //#region src/provenance/normalize.ts
15161
15257
  const TRACKING_PARAMETERS = /* @__PURE__ */ new Set([
15162
15258
  "fbclid",
@@ -15864,6 +15960,55 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15864
15960
  return configured === void 0 || configured === "" ? void 0 : configured;
15865
15961
  }
15866
15962
  //#endregion
15963
+ //#region src/client/workspace-paths.ts
15964
+ /**
15965
+ * The answer a person reads names a file; it does not say where the deployment
15966
+ * keeps the account that owns it.
15967
+ *
15968
+ * A model quoting a path it was handed prints it verbatim, and a QA deployment
15969
+ * gives every account its own directory under one parent, so a path printed
15970
+ * with an account identifier is a sentence about the server's layout — and
15971
+ * stand dialogs below the signature line can be read by other accounts of the
15972
+ * same server. The document tools report workspace-relative names, which is
15973
+ * the fix at the source; this is the repair for what still arrives, because a
15974
+ * model can rebuild a path from the working directory it is told, and because
15975
+ * an answer written before this change is durable history that replays.
15976
+ */
15977
+ /** Characters a literal path may carry into a regular expression. */
15978
+ function escapeForRegex(value) {
15979
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`);
15980
+ }
15981
+ /** One or two separators, either convention, as a regular-expression source. */
15982
+ const SEPARATOR = String.raw`[\\\/]+`;
15983
+ const TRAILING_SEPARATOR = String.raw`[\\\/]?`;
15984
+ /**
15985
+ * The workspace root spelled so either separator convention matches it: a
15986
+ * container records forward slashes, a browser on Windows may print backslashes
15987
+ * at the same position, and a mask that only reads one of them leaks on the
15988
+ * other. A leading separator is kept, so what replaces the root reads as a name
15989
+ * inside the workspace rather than as a path leading out of nowhere.
15990
+ */
15991
+ function rootPattern(workspaceRoot) {
15992
+ const segments = workspaceRoot.split(/[/\\]/u).filter((segment) => segment !== "").map((segment) => escapeForRegex(segment));
15993
+ const leading = /^[/\\]/u.test(workspaceRoot) ? SEPARATOR : "";
15994
+ return new RegExp(`${leading}${segments.join(SEPARATOR)}${TRAILING_SEPARATOR}`, "gu");
15995
+ }
15996
+ /**
15997
+ * Rewrite an answer so a file is named, not located.
15998
+ *
15999
+ * The chat's own workspace directory is removed, leaving the spelling the
16000
+ * producing tool reports and the workspace reads take. The per-account
16001
+ * partition is removed the same way even when the root is unknown or the answer
16002
+ * quotes a different chat's file, because the identifier inside it is the part
16003
+ * that must not be printed. A path that is neither of those is left alone: a
16004
+ * reader told about `/etc/hosts` is not being shown this deployment's layout.
16005
+ */
16006
+ function toWorkspaceRelativeText(text, workspaceRoot) {
16007
+ if (!text.includes("/") && !text.includes("\\")) return text;
16008
+ const root = workspaceRoot?.trim();
16009
+ return (root === void 0 || root === "" ? text : text.replace(rootPattern(root), "")).replace(/[/\\](?:[^/\\\s]+[/\\])*?\.qa-users[/\\][^/\\\s]+[/\\]/gu, "");
16010
+ }
16011
+ //#endregion
15867
16012
  //#region src/client/subagent-codenames.ts
15868
16013
  /**
15869
16014
  * Deterministic display codenames for subagent sessions.
@@ -16246,8 +16391,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16246
16391
  return Reflect.get(block, "type") === "text" && typeof Reflect.get(block, "text") === "string" ? [Reflect.get(block, "text")] : [];
16247
16392
  }).join("");
16248
16393
  }
16249
- function visibleAssistantText(blocks) {
16250
- return blocks.filter((block) => block.kind === "text" && typeof block.text === "string").map((block) => block.text).join("");
16394
+ function visibleAssistantText(blocks, workspaceRoot) {
16395
+ return toWorkspaceRelativeText(blocks.filter((block) => block.kind === "text" && typeof block.text === "string").map((block) => block.text).join(""), workspaceRoot);
16251
16396
  }
16252
16397
  /**
16253
16398
  * Response timing for the message row, straight from the host-recorded step
@@ -16277,15 +16422,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16277
16422
  buffer = {
16278
16423
  turn,
16279
16424
  text: [],
16280
- work: []
16425
+ work: [],
16426
+ artifacts: []
16281
16427
  };
16282
16428
  turns.set(turn, buffer);
16283
16429
  }
16284
16430
  return buffer;
16285
16431
  }
16286
- function collectAssistant(node, turns, toolHeads, showReasoning) {
16432
+ function collectAssistant(node, turns, toolHeads, showReasoning, workspaceRoot) {
16287
16433
  const turn = getTurn(turns, node.turn);
16288
- const text = visibleAssistantText(node.blocks);
16434
+ const text = visibleAssistantText(node.blocks, workspaceRoot);
16289
16435
  if (text !== "") turn.text.push({
16290
16436
  id: `assistant:${node.messageId ?? node.seq}`,
16291
16437
  seq: node.seq,
@@ -16335,6 +16481,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16335
16481
  }
16336
16482
  return settled;
16337
16483
  }
16484
+ /**
16485
+ * The files each turn's tools handed over, keyed onto the turn that made them.
16486
+ *
16487
+ * This reads the settled tool results whatever the work-view switch says: a
16488
+ * produced document is part of the answer, not tool noise, so a stand that
16489
+ * hides the tool rows must still hand the file to the reader. The turn is
16490
+ * resolved exactly as the work rows resolve it, because the card belongs to the
16491
+ * answer the same turn wrote.
16492
+ */
16493
+ function collectTurnArtifacts(nodes, turns, toolHeads) {
16494
+ let nearestTurn;
16495
+ for (const node of nodes) {
16496
+ if (node.kind === "assistant") nearestTurn = node.turn;
16497
+ if (node.kind !== "tool-result") continue;
16498
+ const head = toolHeads.get(node.callId);
16499
+ const name = head?.name ?? node.call?.name;
16500
+ const turnNumber = head?.turn ?? nearestTurn;
16501
+ if (name === void 0 || turnNumber === void 0) continue;
16502
+ const buffer = getTurn(turns, turnNumber);
16503
+ for (const artifact of artifactsOfToolResult(name, flattenToolOutput(node))) {
16504
+ if (buffer.artifacts.some((seen) => seen.path === artifact.path)) continue;
16505
+ buffer.artifacts.push(artifact);
16506
+ }
16507
+ }
16508
+ }
16338
16509
  function collectRunningTool(call, turns, toolHeads) {
16339
16510
  const head = toolHeads.get(call.callId);
16340
16511
  getTurn(turns, call.turn).work.push({
@@ -16420,7 +16591,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16420
16591
  turn: turn.turn,
16421
16592
  ...finalText.seq === void 0 ? {} : { seq: finalText.seq },
16422
16593
  ...finalText.timestamp === void 0 ? {} : { timestamp: finalText.timestamp },
16423
- ...finalText.stats === void 0 ? {} : { stats: finalText.stats }
16594
+ ...finalText.stats === void 0 ? {} : { stats: finalText.stats },
16595
+ ...turn.artifacts.length === 0 ? {} : { artifacts: turn.artifacts }
16424
16596
  }
16425
16597
  });
16426
16598
  }
@@ -16428,6 +16600,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16428
16600
  function projectTranscript(snapshot, options = {}) {
16429
16601
  const legacy = chatLegacyOf(snapshot);
16430
16602
  const running = options.running === true;
16603
+ const workspaceRoot = options.workspaceRoot;
16431
16604
  const output = [];
16432
16605
  const turns = /* @__PURE__ */ new Map();
16433
16606
  const toolHeads = /* @__PURE__ */ new Map();
@@ -16450,7 +16623,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16450
16623
  ...files.length === 0 ? {} : { files }
16451
16624
  }
16452
16625
  });
16453
- } else if (node.kind === "assistant") collectAssistant(node, turns, toolHeads, options.showReasoning === true);
16626
+ } else if (node.kind === "assistant") collectAssistant(node, turns, toolHeads, options.showReasoning === true, workspaceRoot);
16454
16627
  else if (node.kind === "context") {
16455
16628
  if (!(node.producer.label ?? "").toLowerCase().startsWith("subagent")) continue;
16456
16629
  const text = visibleContentText(node.content);
@@ -16517,13 +16690,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16517
16690
  timestamp: node.time
16518
16691
  }
16519
16692
  });
16693
+ collectTurnArtifacts(legacy.nodes, turns, toolHeads);
16520
16694
  if (options.showToolActivity === true) {
16521
16695
  const settled = collectSettledTools(legacy.nodes, turns, toolHeads);
16522
16696
  for (const call of legacy.runningCalls) if (!settled.has(call.callId)) collectRunningTool(call, turns, toolHeads);
16523
16697
  }
16524
16698
  if (legacy.partial !== null) {
16525
16699
  const partialTurn = getTurn(turns, legacy.partial.turn);
16526
- const text = visibleAssistantText(legacy.partial.blocks);
16700
+ const text = visibleAssistantText(legacy.partial.blocks, workspaceRoot);
16527
16701
  if (text !== "") partialTurn.text.push({
16528
16702
  id: `assistant:partial:${legacy.partial.turn}:${legacy.partial.step}`,
16529
16703
  order: Number.MAX_SAFE_INTEGER - 1,
@@ -16687,7 +16861,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16687
16861
  showToolActivity: config.ui.showToolActivity,
16688
16862
  showReasoning: config.ui.showReasoning,
16689
16863
  subagentNames: input.subagentNames,
16690
- subagentCodenames: config.ui.subagentCodenames
16864
+ subagentCodenames: config.ui.subagentCodenames,
16865
+ workspaceRoot: input.workspaceRoot
16691
16866
  }).map((message) => {
16692
16867
  if (message.role === "user" && input.author !== void 0) return {
16693
16868
  ...message,
@@ -17425,10 +17600,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17425
17600
  }
17426
17601
  if (this.disposed || this.generation !== owned) return;
17427
17602
  }
17603
+ this.enterDraft();
17604
+ }
17605
+ /**
17606
+ * Put a draft on the screen: no session, so no row in the chat history and no
17607
+ * record on the Host until the first prompt is sent into it
17608
+ * ({@link materializeDraft}). Taking the screen moves the generation the way
17609
+ * every other path that retires a binding does, so an adoption still looking
17610
+ * for its session measures itself against the new one rather than taking the
17611
+ * draft back.
17612
+ *
17613
+ * A draft that is already on screen — including one whose first send fell
17614
+ * short of a claimed session — keeps its chat identity: a new one rebuilds the
17615
+ * composer over the question the visitor is still looking at.
17616
+ */
17617
+ enterDraft() {
17428
17618
  this.generation += 1;
17429
17619
  this.materializing = void 0;
17430
17620
  this.drafting = true;
17431
- this.openChat();
17621
+ if (this.namedSession !== null) this.openChat();
17622
+ else {
17623
+ this.pendingSubmission = void 0;
17624
+ this.admissionPending = false;
17625
+ }
17432
17626
  this.unbind();
17433
17627
  this.operationError = null;
17434
17628
  this.policyReady = false;
@@ -17638,6 +17832,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17638
17832
  this.listeners.clear();
17639
17833
  }
17640
17834
  async ensureSessionNow() {
17835
+ const resumable = this.config.session.policy === "browser-persistent" && this.chats.activeId() !== null;
17836
+ if (this.config.session.policy !== "fixed" && !resumable) {
17837
+ this.enterDraft();
17838
+ return;
17839
+ }
17641
17840
  const operation = ++this.generation;
17642
17841
  this.drafting = false;
17643
17842
  this.state = {
@@ -17666,12 +17865,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17666
17865
  existing = true;
17667
17866
  } else if (stored !== null) this.chats.clearActive();
17668
17867
  }
17669
- if (id === null) id = await createQaSession({
17670
- createSession: this.createSessionRemote,
17671
- token: this.accounts?.token() ?? "",
17672
- subroleId: this.selectedSubrole,
17673
- adminPreview: this.adminPreview
17674
- });
17868
+ if (id === null) {
17869
+ this.enterDraft();
17870
+ return;
17871
+ }
17675
17872
  if (this.disposed || operation !== this.generation) return;
17676
17873
  try {
17677
17874
  if (!await this.bind(id, {
@@ -17942,7 +18139,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17942
18139
  publish() {
17943
18140
  if (this.disposed) return;
17944
18141
  const connected = this.connection.getSnapshot() !== void 0;
17945
- if (this.drafting && this.session === void 0) {
18142
+ const refusedDraft = this.drafting && this.namedSession === null && this.materializing === void 0;
18143
+ if (this.drafting && (this.session === void 0 || refusedDraft)) {
17946
18144
  const materializing = this.materializing !== void 0 || this.pendingSubmission !== void 0;
17947
18145
  this.state = {
17948
18146
  ...QA_SESSION_IDLE_STATE,
@@ -17972,7 +18170,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17972
18170
  const conversationSnapshot = this.conversationBinding?.snapshot.getSnapshot();
17973
18171
  const projectedSourceBundles = projectTurnSources(conversationSnapshot, sessionId, this.sourceAnchor(sessionId));
17974
18172
  const sourceBundles = this.hostSources.merge(projectedSourceBundles);
17975
- if (!snapshot.running) this.hostSources.refresh(sessionId, () => this.publish());
18173
+ if (!snapshot.running && this.viewingSubagent === null) this.hostSources.refresh(sessionId, () => this.publish());
17976
18174
  this.probePending(sessionId, connected);
17977
18175
  this.syncPendingPolling(snapshot.running === true && !this.compatibilityReadOnly);
17978
18176
  const queuedMessages = this.queuedMessages();
@@ -17983,6 +18181,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17983
18181
  sessionSnapshot: snapshot,
17984
18182
  conversationSnapshot,
17985
18183
  sourceBundles,
18184
+ workspaceRoot: this.sourceAnchor(sessionId),
17986
18185
  approvals: this.hostApprovals.list(),
17987
18186
  questions: this.hostQuestions.list(),
17988
18187
  author: this.accounts?.messageAuthorOf(sessionId),
@@ -18617,6 +18816,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18617
18816
  }
18618
18817
  //#endregion
18619
18818
  //#region src/client/components/QaAuthGate.tsx
18819
+ /** The Host's address shape, mirrored so the card refuses before the round trip. */
18820
+ const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
18821
+ /** Own copy for input the browser would otherwise answer with its own English bubble. */
18822
+ function refusalOf(email, password) {
18823
+ if (!EMAIL_PATTERN.test(email.trim().toLowerCase())) return accountsErrorMessage("invalid-email");
18824
+ if (password !== null && password.length < 8) return accountsErrorMessage("weak-password");
18825
+ return null;
18826
+ }
18620
18827
  function Logo({ logoUrl }) {
18621
18828
  if (logoUrl !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
18622
18829
  className: "dsh-qa-auth__logo",
@@ -18635,7 +18842,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18635
18842
  /**
18636
18843
  * The full-frame login/registration card shown instead of the chat while the
18637
18844
  * deployment has accounts enabled and the browser holds no valid identity.
18638
- * Errors are the Host's coarse codes rendered as audience-safe copy.
18845
+ * Both halves of the error line are the surface's own copy: the card refuses
18846
+ * mistyped input itself, and renders the Host's coarse codes in audience-safe
18847
+ * words.
18639
18848
  */
18640
18849
  function QaAuthGate(props) {
18641
18850
  const { snapshot, accounts } = props;
@@ -18645,11 +18854,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18645
18854
  const notice = gate?.notice ?? null;
18646
18855
  const [email, setEmail] = (0, react.useState)("");
18647
18856
  const [password, setPassword] = (0, react.useState)("");
18857
+ const [refusal, setRefusal] = (0, react.useState)(null);
18648
18858
  const showRegister = props.allowRegistration;
18649
18859
  const reset = mode === "reset";
18860
+ const error = refusal ?? gate?.error ?? null;
18650
18861
  const submit = (event) => {
18651
18862
  event.preventDefault();
18652
18863
  if (busy) return;
18864
+ const next = refusalOf(email, reset ? null : password);
18865
+ setRefusal(next);
18866
+ if (next !== null) return;
18653
18867
  reset ? accounts.requestPasswordReset(email) : mode === "register" ? accounts.register(email, password) : accounts.login(email, password);
18654
18868
  };
18655
18869
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("main", {
@@ -18659,6 +18873,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18659
18873
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
18660
18874
  className: "dsh-qa-auth__card",
18661
18875
  "data-testid": "qa-surface-auth-card",
18876
+ noValidate: true,
18662
18877
  onSubmit: submit,
18663
18878
  children: [
18664
18879
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -18685,7 +18900,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18685
18900
  "data-testid": "qa-surface-auth-tab-login",
18686
18901
  "aria-selected": mode === "login",
18687
18902
  className: mode === "login" ? "dsh-qa-auth__tab dsh-qa-auth__tab--active" : "dsh-qa-auth__tab",
18688
- onClick: () => accounts.setMode("login"),
18903
+ onClick: () => {
18904
+ setRefusal(null);
18905
+ accounts.setMode("login");
18906
+ },
18689
18907
  children: "Вход"
18690
18908
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
18691
18909
  type: "button",
@@ -18693,7 +18911,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18693
18911
  "data-testid": "qa-surface-auth-tab-register",
18694
18912
  "aria-selected": mode === "register",
18695
18913
  className: mode === "register" ? "dsh-qa-auth__tab dsh-qa-auth__tab--active" : "dsh-qa-auth__tab",
18696
- onClick: () => accounts.setMode("register"),
18914
+ onClick: () => {
18915
+ setRefusal(null);
18916
+ accounts.setMode("register");
18917
+ },
18697
18918
  children: "Регистрация"
18698
18919
  })]
18699
18920
  }) : null,
@@ -18708,7 +18929,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18708
18929
  required: true,
18709
18930
  disabled: busy,
18710
18931
  value: email,
18711
- onChange: (event) => setEmail(event.currentTarget.value)
18932
+ onChange: (event) => {
18933
+ setRefusal(null);
18934
+ setEmail(event.currentTarget.value);
18935
+ }
18712
18936
  })]
18713
18937
  }),
18714
18938
  reset ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -18723,15 +18947,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18723
18947
  minLength: 8,
18724
18948
  disabled: busy,
18725
18949
  value: password,
18726
- onChange: (event) => setPassword(event.currentTarget.value)
18950
+ onChange: (event) => {
18951
+ setRefusal(null);
18952
+ setPassword(event.currentTarget.value);
18953
+ }
18727
18954
  })]
18728
18955
  }),
18729
- gate !== void 0 && gate.error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
18956
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
18730
18957
  className: "dsh-qa-auth__error",
18731
18958
  "data-testid": "qa-surface-auth-error",
18732
18959
  role: "alert",
18733
- children: gate.error
18734
- }) : null,
18960
+ children: error
18961
+ }),
18735
18962
  notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
18736
18963
  className: "dsh-qa-auth__notice",
18737
18964
  "data-testid": "qa-surface-auth-notice",
@@ -18750,7 +18977,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18750
18977
  className: "dsh-qa-auth__link",
18751
18978
  "data-testid": "qa-surface-auth-forgot",
18752
18979
  disabled: busy,
18753
- onClick: () => accounts.setMode(reset ? "login" : "reset"),
18980
+ onClick: () => {
18981
+ setRefusal(null);
18982
+ accounts.setMode(reset ? "login" : "reset");
18983
+ },
18754
18984
  children: reset ? "Вернуться ко входу" : "Забыли пароль?"
18755
18985
  })
18756
18986
  ]
@@ -19560,11 +19790,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19560
19790
  return extension.length > 0 && extension.length <= 4 ? extension.toUpperCase() : "FILE";
19561
19791
  }
19562
19792
  /**
19563
- * One attached file: the extension badge, the name and the size. The browser
19564
- * never re-reads these bytes (the Host attachment route serves images), so
19565
- * both the composer card and the sent row show the same handle.
19793
+ * One attached file: the extension badge, the name and the size. A sent
19794
+ * attachment shows the handle the model resolved and the browser never
19795
+ * re-reads its bytes; a file the turn produced is the same chip with the two
19796
+ * controls that make it usable — open it, or take it away.
19566
19797
  */
19567
- function QaFileAttachment({ name, bytes, tone, onRemove }) {
19798
+ function QaFileAttachment({ name, bytes, tone, onRemove, onOpen, onDownload }) {
19799
+ const text = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
19800
+ className: "dsh-qa-file__text",
19801
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19802
+ className: "dsh-qa-file__name",
19803
+ "data-testid": "qa-file-name",
19804
+ children: name
19805
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19806
+ className: "dsh-qa-file__meta",
19807
+ "data-testid": "qa-file-size",
19808
+ children: formatFileSize(bytes)
19809
+ })]
19810
+ });
19568
19811
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
19569
19812
  className: "dsh-qa-file",
19570
19813
  "data-testid": "qa-file",
@@ -19577,17 +19820,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19577
19820
  "aria-hidden": "true",
19578
19821
  children: badgeText(name)
19579
19822
  }),
19580
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
19581
- className: "dsh-qa-file__text",
19582
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19583
- className: "dsh-qa-file__name",
19584
- "data-testid": "qa-file-name",
19585
- children: name
19586
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19587
- className: "dsh-qa-file__meta",
19588
- "data-testid": "qa-file-size",
19589
- children: formatFileSize(bytes)
19590
- })]
19823
+ onOpen === void 0 ? text : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19824
+ type: "button",
19825
+ className: "dsh-qa-file__open",
19826
+ "data-testid": "qa-file-open",
19827
+ "aria-label": `Открыть ${name}`,
19828
+ title: `Открыть ${name}`,
19829
+ onClick: onOpen,
19830
+ children: text
19831
+ }),
19832
+ onDownload === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19833
+ type: "button",
19834
+ className: "dsh-qa-file__download",
19835
+ "data-testid": "qa-file-download",
19836
+ "aria-label": `Скачать ${name}`,
19837
+ title: "Скачать",
19838
+ onClick: onDownload,
19839
+ children: "Скачать"
19591
19840
  }),
19592
19841
  onRemove === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19593
19842
  type: "button",
@@ -21574,6 +21823,124 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21574
21823
  };
21575
21824
  }
21576
21825
  //#endregion
21826
+ //#region src/client/markdown/use-transient-flag.ts
21827
+ /**
21828
+ * A boolean that turns on for a fixed window and drops back off — the
21829
+ * "Скопировано" flash and its siblings. The pending timer lives in a ref and
21830
+ * is cleared by the unmount cleanup, so a pulse fired right before the block
21831
+ * leaves the DOM never schedules a state update on it, and re-pulsing restarts
21832
+ * the window instead of stacking timers.
21833
+ */
21834
+ function useTransientFlag(windowMs) {
21835
+ const [on, setOn] = (0, react.useState)(false);
21836
+ const timer = (0, react.useRef)(void 0);
21837
+ (0, react.useEffect)(() => () => {
21838
+ if (timer.current !== void 0) clearTimeout(timer.current);
21839
+ }, []);
21840
+ return {
21841
+ on,
21842
+ pulse: (0, react.useCallback)(() => {
21843
+ setOn(true);
21844
+ if (timer.current !== void 0) clearTimeout(timer.current);
21845
+ timer.current = setTimeout(() => {
21846
+ timer.current = void 0;
21847
+ setOn(false);
21848
+ }, windowMs);
21849
+ }, [windowMs])
21850
+ };
21851
+ }
21852
+ //#endregion
21853
+ //#region src/client/clipboard.ts
21854
+ /** How long the confirmation that a copy landed stays on screen. */
21855
+ const COPIED_WINDOW_MS = 1200;
21856
+ /**
21857
+ * The sentence a copy affordance falls back to. Three surfaces show it, so it
21858
+ * is written once.
21859
+ */
21860
+ const COPY_MANUAL_HINT = "Скопируйте вручную";
21861
+ /** The reason behind that sentence, for the tooltip that carries it. */
21862
+ const COPY_MANUAL_REASON = "Браузер не отдаёт буфер обмена — выделите текст и скопируйте его сами";
21863
+ function hasClipboardApi() {
21864
+ return navigator.clipboard?.writeText !== void 0;
21865
+ }
21866
+ /**
21867
+ * Whether this browser can be asked to move text at all.
21868
+ *
21869
+ * The stand is served over plain HTTP on a LAN address, and there
21870
+ * `window.isSecureContext` is false, so the browser never exposes
21871
+ * `navigator.clipboard`. The older copy command still works in an insecure
21872
+ * context, which is why the absence of the API alone does not mean copying is
21873
+ * impossible — and why asking for it anyway only ends in silence.
21874
+ */
21875
+ function canCopyToClipboard() {
21876
+ return hasClipboardApi() || typeof document.execCommand === "function";
21877
+ }
21878
+ /**
21879
+ * Write `text` to the clipboard, resolving false when nothing took it — the
21880
+ * caller then says so instead of leaving the click unanswered.
21881
+ */
21882
+ async function copyTextToClipboard(text) {
21883
+ const clipboard = navigator.clipboard;
21884
+ if (clipboard?.writeText !== void 0) try {
21885
+ await clipboard.writeText(text);
21886
+ return true;
21887
+ } catch {}
21888
+ return copyThroughField(text);
21889
+ }
21890
+ /** The pre-API path: select a throwaway field and ask the browser to copy it. */
21891
+ function copyThroughField(text) {
21892
+ const field = document.createElement("textarea");
21893
+ field.value = text;
21894
+ field.readOnly = true;
21895
+ field.setAttribute("aria-hidden", "true");
21896
+ field.style.position = "fixed";
21897
+ field.style.top = "0";
21898
+ field.style.left = "-100vw";
21899
+ document.body.appendChild(field);
21900
+ field.select();
21901
+ try {
21902
+ return document.execCommand("copy");
21903
+ } catch {
21904
+ return false;
21905
+ } finally {
21906
+ field.remove();
21907
+ }
21908
+ }
21909
+ /**
21910
+ * The copy state one affordance needs: the confirmation window, the refusal
21911
+ * that replaces it with the manual hint, and whether either was ever possible.
21912
+ */
21913
+ function useCopyAction(text) {
21914
+ const [impossible] = (0, react.useState)(() => !canCopyToClipboard());
21915
+ const { on: copied, pulse } = useTransientFlag(COPIED_WINDOW_MS);
21916
+ const [refused, setRefused] = (0, react.useState)(false);
21917
+ return {
21918
+ copied,
21919
+ impossible,
21920
+ refused,
21921
+ copy: (0, react.useCallback)(() => {
21922
+ copyTextToClipboard(text).then((ok) => {
21923
+ setRefused(!ok);
21924
+ if (ok) pulse();
21925
+ });
21926
+ }, [text, pulse])
21927
+ };
21928
+ }
21929
+ //#endregion
21930
+ //#region src/client/components/copy-hint.tsx
21931
+ /**
21932
+ * What a copy affordance shows where copying cannot happen: a sentence naming
21933
+ * the dead end, in place of a button that would only look like it worked.
21934
+ */
21935
+ function CopyHint({ testId }) {
21936
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
21937
+ className: "dsh-qa-copy-hint",
21938
+ "data-testid": testId,
21939
+ title: COPY_MANUAL_REASON,
21940
+ children: COPY_MANUAL_HINT
21941
+ });
21942
+ }
21943
+ //#endregion
21577
21944
  //#region src/client/markdown/highlight.ts
21578
21945
  /** Double and single quotes that end at the line they start on. */
21579
21946
  const QUOTES = [{
@@ -22698,36 +23065,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
22698
23065
  return cursor;
22699
23066
  }
22700
23067
  //#endregion
22701
- //#region src/client/markdown/use-transient-flag.ts
22702
- /**
22703
- * A boolean that turns on for a fixed window and drops back off — the
22704
- * "Скопировано" flash and its siblings. The pending timer lives in a ref and
22705
- * is cleared by the unmount cleanup, so a pulse fired right before the block
22706
- * leaves the DOM never schedules a state update on it, and re-pulsing restarts
22707
- * the window instead of stacking timers.
22708
- */
22709
- function useTransientFlag(windowMs) {
22710
- const [on, setOn] = (0, react.useState)(false);
22711
- const timer = (0, react.useRef)(void 0);
22712
- (0, react.useEffect)(() => () => {
22713
- if (timer.current !== void 0) clearTimeout(timer.current);
22714
- }, []);
22715
- return {
22716
- on,
22717
- pulse: (0, react.useCallback)(() => {
22718
- setOn(true);
22719
- if (timer.current !== void 0) clearTimeout(timer.current);
22720
- timer.current = setTimeout(() => {
22721
- timer.current = void 0;
22722
- setOn(false);
22723
- }, windowMs);
22724
- }, [windowMs])
22725
- };
22726
- }
22727
- //#endregion
22728
23068
  //#region src/client/markdown/CodeBlock.tsx
22729
- /** Copied-label window: long enough to read, short enough to not stick. */
22730
- const COPIED_MS = 1200;
22731
23069
  /**
22732
23070
  * One fenced code block: the language banner, a copy button, and the code
22733
23071
  * itself. Highlighting is our own scanner (see `highlight.ts`) — the fence is
@@ -22736,12 +23074,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
22736
23074
  */
22737
23075
  const CodeBlock = (0, react.memo)(function CodeBlock({ code, lang }) {
22738
23076
  const lines = (0, react.useMemo)(() => highlightLines(code, lang), [code, lang]);
22739
- const { on: copied, pulse: pulseCopied } = useTransientFlag(COPIED_MS);
22740
- const onCopy = (0, react.useCallback)(() => {
22741
- const clipboard = navigator.clipboard;
22742
- if (clipboard === void 0) return;
22743
- clipboard.writeText(code).then(pulseCopied, () => void 0);
22744
- }, [code, pulseCopied]);
23077
+ const copy = useCopyAction(code);
22745
23078
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22746
23079
  className: "dsh-qa-md-code",
22747
23080
  "data-testid": "qa-md-code",
@@ -22752,14 +23085,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
22752
23085
  className: "dsh-qa-md-code__lang",
22753
23086
  "data-testid": "qa-md-code-lang",
22754
23087
  children: lang ?? ""
22755
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
23088
+ }), copy.impossible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyHint, { testId: "qa-md-code-copy-hint" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
22756
23089
  type: "button",
22757
23090
  className: "dsh-qa-md-code__copy",
22758
23091
  "data-testid": "qa-md-code-copy",
22759
- onClick: onCopy,
22760
- "aria-label": copied ? "Скопировано" : "Копировать код",
22761
- children: copied ? "Скопировано" : "Копировать"
22762
- })]
23092
+ onClick: copy.copy,
23093
+ "aria-label": copy.copied ? "Скопировано" : "Копировать код",
23094
+ children: copy.copied ? "Скопировано" : "Копировать"
23095
+ }), copy.refused ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyHint, { testId: "qa-md-code-copy-hint" }) : null] })]
22763
23096
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
22764
23097
  tabIndex: 0,
22765
23098
  "data-testid": "qa-md-code-pre",
@@ -50301,7 +50634,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50301
50634
  if (a.role === "work" && b.role === "work") return a.turn === b.turn && a.startedAt === b.startedAt && a.endedAt === b.endedAt && sameWorkItems(a.items, b.items);
50302
50635
  if (a.role === "work" || b.role === "work") return false;
50303
50636
  if (a.text !== b.text || a.timestamp !== b.timestamp) return false;
50304
- if (a.role === "assistant" && b.role === "assistant") return sameSources(a.sources, b.sources) && a.stats === void 0 === (b.stats === void 0) && (a.stats === void 0 || b.stats === void 0 || a.stats.durationMs === b.stats.durationMs && a.stats.ttftMs === b.stats.ttftMs && a.stats.tokensPerSecond === b.stats.tokensPerSecond);
50637
+ if (a.role === "assistant" && b.role === "assistant") return sameSources(a.sources, b.sources) && sameArtifacts(a.artifacts, b.artifacts) && a.stats === void 0 === (b.stats === void 0) && (a.stats === void 0 || b.stats === void 0 || a.stats.durationMs === b.stats.durationMs && a.stats.ttftMs === b.stats.ttftMs && a.stats.tokensPerSecond === b.stats.tokensPerSecond);
50305
50638
  if (a.role === "user" && b.role === "user") return a.author === b.author && sameImages(a.images, b.images) && sameFiles(a.files, b.files);
50306
50639
  if (a.role === "system" && b.role === "system") return a.notice === void 0 === (b.notice === void 0) && (a.notice === void 0 || b.notice === void 0 || a.notice.title === b.notice.title && a.notice.body === b.notice.body && a.notice.meta === b.notice.meta);
50307
50640
  return false;
@@ -50321,6 +50654,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50321
50654
  if (a === void 0 || b === void 0 || a.length !== b.length) return false;
50322
50655
  return a.every((file, index) => file.attachmentId === b[index].attachmentId && file.name === b[index].name && file.bytes === b[index].bytes);
50323
50656
  }
50657
+ function sameArtifacts(a, b) {
50658
+ if (a === b) return true;
50659
+ if (a === void 0 || b === void 0 || a.length !== b.length) return false;
50660
+ return a.every((artifact, index) => artifact.path === b[index].path && artifact.name === b[index].name && artifact.format === b[index].format && artifact.bytes === b[index].bytes);
50661
+ }
50324
50662
  const REASONS = [
50325
50663
  "incorrect",
50326
50664
  "instruction_not_followed",
@@ -50411,9 +50749,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50411
50749
  }
50412
50750
  return parts;
50413
50751
  }
50414
- const QaMessage = (0, react.memo)(function QaMessage({ message, renderMarkdown, showTimestamp, stateKey, onRegenerate, resolveImage, onOpenSources, onSourceDetail, thinkingPhrases, onRateFeedback }) {
50415
- const [copied, setCopied] = (0, react.useState)(false);
50416
- const copiedTimer = (0, react.useRef)(void 0);
50752
+ const QaMessage = (0, react.memo)(function QaMessage({ message, renderMarkdown, showTimestamp, stateKey, onRegenerate, resolveImage, onOpenSources, onSourceDetail, onArtifactOpen, onArtifactDownload, thinkingPhrases, onRateFeedback }) {
50753
+ const { copied, impossible: copyImpossible, refused: copyRefused, copy } = useCopyAction(message.role === "work" ? "" : message.text);
50417
50754
  const [rating, setRating] = (0, react.useState)(null);
50418
50755
  const [askingWhy, setAskingWhy] = (0, react.useState)(false);
50419
50756
  const refsCache = (0, react.useRef)({
@@ -50431,9 +50768,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50431
50768
  (0, react.useEffect)(() => {
50432
50769
  setRating(readRatings(stateKey)[message.id] ?? null);
50433
50770
  }, [stateKey, message.id]);
50434
- (0, react.useEffect)(() => () => {
50435
- if (copiedTimer.current !== void 0) clearTimeout(copiedTimer.current);
50436
- }, []);
50437
50771
  if (message.role === "system" && message.command !== void 0) {
50438
50772
  const activity = message.command;
50439
50773
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
@@ -50515,15 +50849,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50515
50849
  })
50516
50850
  });
50517
50851
  const label = message.role === "assistant" ? "Помощник" : message.role === "user" ? message.author ?? "Вы" : "Статус";
50518
- const copy = async () => {
50519
- if (copied || navigator.clipboard?.writeText === void 0) return;
50520
- try {
50521
- await navigator.clipboard.writeText(message.text);
50522
- setCopied(true);
50523
- if (copiedTimer.current !== void 0) clearTimeout(copiedTimer.current);
50524
- copiedTimer.current = setTimeout(() => setCopied(false), 1200);
50525
- } catch {}
50526
- };
50527
50852
  const persist = (next, detail) => {
50528
50853
  if (next === null || onRateFeedback === void 0) return;
50529
50854
  if (message.role !== "assistant" || message.seq === void 0) {
@@ -50600,6 +50925,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50600
50925
  sourceRefs,
50601
50926
  onSourceOpen: onSourceDetail
50602
50927
  }) : message.text,
50928
+ message.role === "assistant" && message.artifacts !== void 0 && message.artifacts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
50929
+ className: "dsh-qa-message__artifacts",
50930
+ "data-testid": "qa-message-artifacts",
50931
+ "aria-label": "Созданные файлы",
50932
+ children: message.artifacts.map((artifact) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaFileAttachment, {
50933
+ name: artifact.name,
50934
+ bytes: artifact.bytes,
50935
+ tone: "sent",
50936
+ onOpen: onArtifactOpen === void 0 ? void 0 : () => onArtifactOpen(artifact),
50937
+ onDownload: onArtifactDownload === void 0 ? void 0 : () => onArtifactDownload(artifact)
50938
+ }, artifact.path))
50939
+ }) : null,
50603
50940
  message.status === "streaming" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
50604
50941
  className: "dsh-qa-message__cursor",
50605
50942
  "aria-hidden": "true"
@@ -50632,12 +50969,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50632
50969
  "data-persistent": persistentMeta || void 0,
50633
50970
  children: [
50634
50971
  message.role === "user" ? meta : null,
50635
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50972
+ copyImpossible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyHint, { testId: "qa-message-copy-hint" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50636
50973
  type: "button",
50637
50974
  "data-testid": "qa-message-copy",
50638
50975
  "aria-label": copied ? "Скопировано" : "Скопировать сообщение",
50639
50976
  title: copied ? "Скопировано" : "Копировать",
50640
- onClick: () => void copy(),
50977
+ onClick: copy,
50641
50978
  children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
50642
50979
  viewBox: "0 0 18 18",
50643
50980
  "aria-hidden": "true",
@@ -50653,7 +50990,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50653
50990
  rx: "2"
50654
50991
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M11.75 11.75v.5a2.5 2.5 0 0 1-2.5 2.5h-3.5a2.5 2.5 0 0 1-2.5-2.5v-3.5a2.5 2.5 0 0 1 2.5-2.5h.5" })]
50655
50992
  })
50656
- }),
50993
+ }), copyRefused ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyHint, { testId: "qa-message-copy-hint" }) : null] }),
50657
50994
  message.role === "user" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
50658
50995
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50659
50996
  type: "button",
@@ -50714,7 +51051,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
50714
51051
  }) : null
50715
51052
  ]
50716
51053
  });
50717
- }, (prev, next) => prev.renderMarkdown === next.renderMarkdown && prev.showTimestamp === next.showTimestamp && prev.stateKey === next.stateKey && prev.onRegenerate === next.onRegenerate && prev.resolveImage === next.resolveImage && prev.onOpenSources === next.onOpenSources && prev.onSourceDetail === next.onSourceDetail && prev.thinkingPhrases === next.thinkingPhrases && sameMessage(prev.message, next.message));
51054
+ }, (prev, next) => prev.renderMarkdown === next.renderMarkdown && prev.showTimestamp === next.showTimestamp && prev.stateKey === next.stateKey && prev.onRegenerate === next.onRegenerate && prev.resolveImage === next.resolveImage && prev.onOpenSources === next.onOpenSources && prev.onSourceDetail === next.onSourceDetail && prev.onArtifactOpen === next.onArtifactOpen && prev.onArtifactDownload === next.onArtifactDownload && prev.thinkingPhrases === next.thinkingPhrases && sameMessage(prev.message, next.message));
50718
51055
  //#endregion
50719
51056
  //#region ../../packages/audit-core/lib/types.js
50720
51057
  /**
@@ -52624,8 +52961,42 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
52624
52961
  }
52625
52962
  //#endregion
52626
52963
  //#region src/client/components/QaChangelog.tsx
52627
- const QA_VERSION = "0.15.1";
52964
+ const QA_VERSION = "0.15.3";
52628
52965
  const QA_CHANGELOG = [
52966
+ {
52967
+ version: "0.15.3",
52968
+ date: "2026-10-08",
52969
+ sections: [{
52970
+ title: "Исправления",
52971
+ items: ["Палитра слэш-команд в чате, который ещё не начался, называет те же навыки, что и в чате, где уже шёл ответ. Пока за разговором не стоял живой агент, список строился по одному общему allow-list'у развёртывания и не сужался ролью: в него попадал навык, который в этом чате вызвать нельзя — имя подсвечивалось, Enter подставлял его в строку, а в ответ приходил отказ, — и наоборот, доступный навык мог не показаться. Теперь роль учитывается при обоих чтениях, а список, который палитра показывает, и список, которым проверяется введённая строка, выводятся из одного правила, поэтому предложение в палитре и возможность его вызвать больше не могут разойтись."]
52972
+ }]
52973
+ },
52974
+ {
52975
+ version: "0.15.2",
52976
+ date: "2026-10-07",
52977
+ sections: [
52978
+ {
52979
+ title: "Исправления",
52980
+ items: ["Кнопка «Копировать» работает на стенде, открытом по http по адресу в локальной сети. Браузер доверяет буфер обмена только защищённым страницам, поэтому на таком стенде современного интерфейса буфера нет вовсе — и нажатие молчало: ни ошибки, ни отметки «Скопировано», ни текста в буфере, а проверка вставлялась пустой. Теперь поверхность сначала пробует буфер, а когда его нет, копирует через временное поле — этот путь доступен и на незащищённой странице. Так копируются код-блок в ответе, ответ целиком и значение интеграционного токена. Если браузер не отдал буфер ни одним путём, на месте кнопки появляется строка «Скопируйте вручную», а там, где кнопка осталась рабочей, та же строка встаёт рядом с ней после отказа: молчание больше не выглядит удачным копированием.", "Палитра слэш-команд в чате, который ещё не начался, называет те же навыки, что и в чате, где уже шёл ответ. Пока за разговором не стоял живой агент, список строился по одному общему allow-list'у развёртывания и не сужался ролью: в него попадал навык, который в этом чате вызвать нельзя — имя подсвечивалось, Enter подставлял его в строку, а в ответ приходил отказ, — и наоборот, доступный навык мог не показаться. Теперь роль учитывается при обоих чтениях, а список, который палитра показывает, и список, которым проверяется введённая строка, выводятся из одного правила, поэтому предложение в палитре и возможность его вызвать больше не могут разойтись."]
52981
+ },
52982
+ {
52983
+ title: "Изменения",
52984
+ items: [
52985
+ "Ряд плагина на странице Plugins называется словами, а не спецификатором пакета. Хост берёт имя и описание ряда из `locale/en.json` — этот файл читается без активации плагина, — и без него подписывает ряд полным именем пакета: вместо «QA Surface» оператор читал `@yadsh/dsh-qa-surface`. То же предложение, что описывает ряд, карточка отвечает и как однострочник на случай, когда описания у ряда нет, — проверка сверяет пару с самим файлом, а не с копией строки в тесте, поэтому ряд не сможет описать одну страницу, открыв другую. Так названы ряды всех плагинов набора.",
52986
+ "Страница настроек Prompt Firewall переехала на панель Plugins: её ряд открывает секцию конфигурации, и карточка больше не рисует свою рамку, заголовок и стрелку — их рисует страница. Раздел, который недоступен этому браузеру, отвечает предложением, а не пустой секцией. Страница Domain Experts села на ряд самого пакета и сняла собственный заголовок: панель уже подписывает пакет его именем и описанием.",
52987
+ "Блоки отказа и опасные действия снова окрашены. Стили ссылались на имена токенов, которых у темы хоста нет (`--dsw-alias-bg-error`, `--dsw-alias-label-error`), а неизвестный `var(--dsw-…)` отбрасывает всю декларацию молча: фон становился прозрачным, цвет — унаследованным, и отказ на вкладке выглядел обычным мелким текстом. Теперь цвет берётся из `--dsw-alias-state-error-primary` с фолбэком, а каждая подстановка `--dsw-*` сверяется с самой темой (`pnpm verify:tokens`), поэтому список несуществующих имён не вырастет."
52988
+ ]
52989
+ },
52990
+ {
52991
+ title: "Исправления",
52992
+ items: ["Отказ открыть чат называет причину. Дочерняя сессия субагента — кусок работы, делегированный из другого разговора, — раньше отвергалась теми же тремя словами, что и чат, за которым стенд не смог собрать агента: в консоли читалась подсказка про пресет, который никто не менял, а в журнале стенда на каждое обращение к чату падали две строки уровня ERROR, из-за чего день одного стенда выглядел как двенадцать одинаковых аварий. У этих двух случаев теперь разные лица: дочерняя сессия называется дочерней, и её строка идёт предупреждением — стенд ответил правильно про чужой разговор, и чинить нечего; чат же без агента остаётся ошибкой и в подсказке называет, куда смотреть и что делать (перемонтировать записанный пресет и открыть чат заново), а читателю тем временем остаётся переписка только для чтения и «Новый чат». Панель субагента перестала спрашивать у стенда источники дочерней сессии, которые тот по правилам не выдаёт: доказательства делегированного хода и так приходят в родительский чат, а экран читает их из самого транскрипта."]
52993
+ },
52994
+ {
52995
+ title: "Исправления",
52996
+ items: ["История чатов больше не наполняется пустыми. Вход на поверхность создавал разговор раньше, чем в него что-нибудь написали, и тот оставался в сайдбаре строкой «Новый чат»: шесть заходов одним и тем же аккаунтом — шесть одинаковых строк, а поиск по истории выдавал их вместо разговоров. Теперь разговор появляется вместе с первым отправленным сообщением: зашли, посмотрели, ушли — в истории ничего, и на стенде не осталось сессии, в которую никто не написал. Кнопка «Новый чат» пустой разговор не создавала и раньше; теперь так же ведёт себя и загрузка страницы.", "Созданный по просьбе документ приходит файлом, а не строкой пути. Ответ на «сделай docx» был текстом: карточки под ответом не было, вкладка «Файлы» стояла с «нет вложений», а в тексте печатался путь контейнера вместе с идентификатором аккаунта — раскладка сервера в сообщении, которое на этом стенде может быть видно другим аккаунтом. Теперь под ответом лежит карточка созданного файла с предпросмотром и скачиванием, она же появляется в списке вложений чата, и карточка видна независимо от того, скрыта ли в настройках активность инструментов: файл, который попросили, — это не шум инструментов. Плагины документов сообщают путь файла внутри рабочего каталога чата (тем же написанием, которым этот файл потом снова открывается), а если абсолютный путь всё же попадает в текст ответа — из старого, уже сохранённого хода или собранный моделью из известного ей рабочего каталога, — интерфейс вычитает из него каталог аккаунта, и остаётся имя файла."]
52997
+ }
52998
+ ]
52999
+ },
52629
53000
  {
52630
53001
  version: "0.15.1",
52631
53002
  date: "2026-10-05",
@@ -53150,7 +53521,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
53150
53521
  className: "dsh-qa-changelog__date",
53151
53522
  children: entry.date
53152
53523
  }),
53153
- entry.version === "0.15.1" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53524
+ entry.version === "0.15.3" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53154
53525
  className: "dsh-qa-changelog__current",
53155
53526
  children: "текущая"
53156
53527
  }) : null
@@ -53985,14 +54356,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
53985
54356
  /** Total attachment count across groups; the header badge's value. */
53986
54357
  function countChatAttachments(groups) {
53987
54358
  let total = 0;
53988
- for (const group of groups) total += group.files.length + group.images.length;
54359
+ for (const group of groups) total += group.files.length + group.images.length + (group.artifacts?.length ?? 0);
53989
54360
  return total;
53990
54361
  }
53991
54362
  /**
53992
- * Group the attachments of a chat by their sending message. Only user
53993
- * messages can carry attachments; messages without any are dropped. The
53994
- * groups come out newest message first, matching a roster that grows at the
53995
- * bottom of the transcript.
54363
+ * Group the attachments of a chat by their message. A user message carries what
54364
+ * was sent; an assistant message carries what its turn produced, so a document
54365
+ * the stand made is listed beside the answer that made it instead of only on
54366
+ * disk. Messages with neither are dropped. The groups come out newest first,
54367
+ * matching a roster that grows at the bottom of the transcript.
53996
54368
  * @param messages - the chat's projected transcript.
53997
54369
  * @returns attachment groups, newest message first.
53998
54370
  */
@@ -54000,20 +54372,53 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54000
54372
  const groups = [];
54001
54373
  for (let index = messages.length - 1; index >= 0; index -= 1) {
54002
54374
  const message = messages[index];
54003
- if (message === void 0 || message.role !== "user") continue;
54004
- const files = message.files ?? [];
54005
- const images = message.images ?? [];
54006
- if (files.length === 0 && images.length === 0) continue;
54375
+ if (message === void 0) continue;
54376
+ const files = message.role === "user" ? message.files ?? [] : [];
54377
+ const images = message.role === "user" ? message.images ?? [] : [];
54378
+ const artifacts = message.role === "assistant" ? message.artifacts ?? [] : [];
54379
+ if (files.length === 0 && images.length === 0 && artifacts.length === 0) continue;
54380
+ const timestamp = message.role === "work" ? void 0 : message.timestamp;
54007
54381
  groups.push({
54008
54382
  messageId: message.id,
54009
- ...message.timestamp === void 0 ? {} : { timestamp: message.timestamp },
54383
+ ...timestamp === void 0 ? {} : { timestamp },
54010
54384
  files,
54011
- images
54385
+ images,
54386
+ ...artifacts.length === 0 ? {} : { artifacts }
54012
54387
  });
54013
54388
  }
54014
54389
  return groups;
54015
54390
  }
54016
54391
  //#endregion
54392
+ //#region src/client/workspace-download.ts
54393
+ /**
54394
+ * Handing one file of the chat's workspace to the person the turn produced it
54395
+ * for.
54396
+ *
54397
+ * A produced document has no attachment-store handle, so the download is the
54398
+ * same read the files panel does: the Host returns the bytes under the chat's
54399
+ * own fence and the browser's download machinery takes it from there. Shared by
54400
+ * the panel's own viewer and the card under an answer, so the two can never
54401
+ * disagree on what "download this file" means.
54402
+ */
54403
+ /** Last segment of a workspace path, spelled with either separator. */
54404
+ function fileNameOf(path) {
54405
+ const segments = path.split(/[/\\]/u).filter((segment) => segment !== "");
54406
+ return segments[segments.length - 1] ?? path;
54407
+ }
54408
+ /** Save one workspace file under its own name. */
54409
+ function downloadWorkspaceFile(file) {
54410
+ const bytes = file.base64 === void 0 ? new TextEncoder().encode(file.text ?? "") : base64ToBytes(file.base64);
54411
+ const url = URL.createObjectURL(new Blob([bytes], { type: file.mime }));
54412
+ const anchor = document.createElement("a");
54413
+ anchor.href = url;
54414
+ anchor.download = fileNameOf(file.path);
54415
+ anchor.rel = "noreferrer";
54416
+ document.body.append(anchor);
54417
+ anchor.click();
54418
+ anchor.remove();
54419
+ URL.revokeObjectURL(url);
54420
+ }
54421
+ //#endregion
54017
54422
  //#region src/shared/documents.ts
54018
54423
  /**
54019
54424
  * Formats the files panel previews by conversion instead of by bytes.
@@ -54057,11 +54462,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54057
54462
  }
54058
54463
  //#endregion
54059
54464
  //#region src/client/components/QaWorkspaceBrowser.tsx
54060
- /** Name of one entry: the last path segment, root spelled as the directory. */
54061
- function entryName(path) {
54062
- const parts = path.split("/");
54063
- return parts[parts.length - 1] ?? path;
54064
- }
54065
54465
  /** Path join for the panel's own navigation, never leaving the chat root. */
54066
54466
  function joinPath(directory, name) {
54067
54467
  return directory === "" ? name : `${directory}/${name}`;
@@ -54083,19 +54483,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54083
54483
  }
54084
54484
  return crumbs;
54085
54485
  }
54086
- /** Hand one file to the browser's download machinery. */
54087
- function downloadFile(file) {
54088
- const bytes = file.base64 === void 0 ? new TextEncoder().encode(file.text ?? "") : base64ToBytes(file.base64);
54089
- const url = URL.createObjectURL(new Blob([bytes], { type: file.mime }));
54090
- const anchor = document.createElement("a");
54091
- anchor.href = url;
54092
- anchor.download = entryName(file.path);
54093
- anchor.rel = "noreferrer";
54094
- document.body.append(anchor);
54095
- anchor.click();
54096
- anchor.remove();
54097
- URL.revokeObjectURL(url);
54098
- }
54099
54486
  /** One row of the listing: a directory to descend into or a file to open. */
54100
54487
  function EntryRow({ entry, directory, onOpenFile, onOpenDirectory }) {
54101
54488
  const path = joinPath(directory, entry.name);
@@ -54160,13 +54547,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54160
54547
  if (file.mime.startsWith("image/") && file.base64 !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
54161
54548
  className: "dsh-qa-ws__image",
54162
54549
  "data-testid": "qa-surface-workspace-image",
54163
- alt: entryName(file.path),
54550
+ alt: fileNameOf(file.path),
54164
54551
  src: `data:${file.mime};base64,${file.base64}`
54165
54552
  });
54166
54553
  if (pdf !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
54167
54554
  className: "dsh-qa-ws__pdf",
54168
54555
  "data-testid": "qa-surface-workspace-pdf",
54169
- title: entryName(file.path),
54556
+ title: fileNameOf(file.path),
54170
54557
  src: pdf
54171
54558
  });
54172
54559
  if (converting) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -54222,7 +54609,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54222
54609
  className: "dsh-qa-ws__preview-name",
54223
54610
  "data-testid": "qa-surface-workspace-preview-name",
54224
54611
  title: file.path,
54225
- children: entryName(file.path)
54612
+ children: fileNameOf(file.path)
54226
54613
  }),
54227
54614
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
54228
54615
  type: "button",
@@ -54241,7 +54628,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54241
54628
  type: "button",
54242
54629
  className: "dsh-qa-ws__download",
54243
54630
  "data-testid": "qa-surface-workspace-download",
54244
- onClick: () => downloadFile(file),
54631
+ onClick: () => downloadWorkspaceFile(file),
54245
54632
  children: "Скачать"
54246
54633
  })
54247
54634
  ]
@@ -54277,7 +54664,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54277
54664
  * the visitor out of the chat's tree. Opening a file reads exactly that file —
54278
54665
  * text inline, anything else as a downloadable handle.
54279
54666
  */
54280
- function QaWorkspaceBrowser({ sessionId, api }) {
54667
+ function QaWorkspaceBrowser({ sessionId, api, initialFile }) {
54281
54668
  const [directory, setDirectory] = (0, react.useState)("");
54282
54669
  const [entries, setEntries] = (0, react.useState)([]);
54283
54670
  const [truncated, setTruncated] = (0, react.useState)(false);
@@ -54373,6 +54760,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54373
54760
  convert,
54374
54761
  sessionId
54375
54762
  ]);
54763
+ const arrivalOpened = (0, react.useRef)(false);
54764
+ (0, react.useEffect)(() => {
54765
+ if (arrivalOpened.current) return;
54766
+ if (initialFile === void 0 || initialFile === "") return;
54767
+ arrivalOpened.current = true;
54768
+ openFile(initialFile);
54769
+ }, [initialFile, openFile]);
54376
54770
  if (opened !== null) {
54377
54771
  const closeFile = () => {
54378
54772
  setOpened(null);
@@ -54395,7 +54789,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54395
54789
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaModal, {
54396
54790
  open: expanded && file !== void 0,
54397
54791
  size: "document",
54398
- title: file === void 0 ? "" : entryName(file.path),
54792
+ title: file === void 0 ? "" : fileNameOf(file.path),
54399
54793
  closeLabel: "Закрыть файл",
54400
54794
  onClose: () => setExpanded(false),
54401
54795
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -54515,14 +54909,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54515
54909
  * back, and the images are the same object URLs the message showed — while the
54516
54910
  * workspace section reads through the Host's fenced listing and file reads.
54517
54911
  */
54518
- function QaFilesPanel({ groups, resolveImage, onJumpToMessage, sessionId, api }) {
54912
+ function QaFilesPanel({ groups, resolveImage, onJumpToMessage, onArtifactOpen, onArtifactDownload, openArtifact, sessionId, api }) {
54519
54913
  const workspace = sessionId === void 0 || api === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
54520
54914
  className: "dsh-qa-files__workspace",
54521
54915
  "data-testid": "qa-files-workspace",
54522
54916
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Рабочий каталог" }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaWorkspaceBrowser, {
54523
54917
  sessionId,
54524
- api
54525
- })]
54918
+ api,
54919
+ initialFile: openArtifact?.path
54920
+ }, openArtifact?.stamp ?? "browse")]
54526
54921
  });
54527
54922
  if (groups.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
54528
54923
  className: "dsh-qa-files",
@@ -54559,14 +54954,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
54559
54954
  })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
54560
54955
  className: "dsh-qa-files__items",
54561
54956
  "data-testid": "qa-files-group-items",
54562
- children: [group.files.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaFileAttachment, {
54563
- name: file.name,
54564
- bytes: file.bytes,
54565
- tone: "sent"
54566
- }, file.attachmentId)), group.images.map((image) => resolveImage === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaFileImage, {
54567
- image,
54568
- resolve: resolveImage
54569
- }, image.attachmentId))]
54957
+ children: [
54958
+ group.files.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaFileAttachment, {
54959
+ name: file.name,
54960
+ bytes: file.bytes,
54961
+ tone: "sent"
54962
+ }, file.attachmentId)),
54963
+ group.images.map((image) => resolveImage === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaFileImage, {
54964
+ image,
54965
+ resolve: resolveImage
54966
+ }, image.attachmentId)),
54967
+ (group.artifacts ?? []).map((artifact) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaFileAttachment, {
54968
+ name: artifact.name,
54969
+ bytes: artifact.bytes,
54970
+ tone: "sent",
54971
+ onOpen: onArtifactOpen === void 0 ? void 0 : () => onArtifactOpen(artifact),
54972
+ onDownload: onArtifactDownload === void 0 ? void 0 : () => onArtifactDownload(artifact)
54973
+ }, artifact.path))
54974
+ ]
54570
54975
  })]
54571
54976
  }, group.messageId);
54572
54977
  })]
@@ -56172,7 +56577,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
56172
56577
  const [scopes, setScopes] = (0, react.useState)(QA_SERVICE_TOKEN_DEFAULT_SCOPES);
56173
56578
  const [busy, setBusy] = (0, react.useState)(false);
56174
56579
  const [issued, setIssued] = (0, react.useState)(null);
56175
- const [copied, setCopied] = (0, react.useState)(false);
56580
+ const { copied, impossible: copyImpossible, refused: copyRefused, copy: copySecret } = useCopyAction(issued?.token ?? "");
56176
56581
  const [pendingRevoke, setPendingRevoke] = (0, react.useState)(null);
56177
56582
  const [revoking, setRevoking] = (0, react.useState)(null);
56178
56583
  (0, react.useEffect)(() => {
@@ -56204,7 +56609,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
56204
56609
  if (busy) return;
56205
56610
  setBusy(true);
56206
56611
  setError(null);
56207
- setCopied(false);
56208
56612
  props.api.create({
56209
56613
  label: label.trim(),
56210
56614
  scopes: [...scopes],
@@ -56237,11 +56641,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
56237
56641
  await reload();
56238
56642
  });
56239
56643
  };
56240
- const copySecret = (secret) => {
56241
- const clipboard = navigator.clipboard;
56242
- if (clipboard === void 0) return;
56243
- clipboard.writeText(secret).then(() => setCopied(true), () => void 0);
56244
- };
56245
56644
  const now = Date.now();
56246
56645
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
56247
56646
  className: "dsh-qa-settings__page",
@@ -56277,18 +56676,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
56277
56676
  onFocus: (event) => event.currentTarget.select()
56278
56677
  })
56279
56678
  }),
56280
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(QaSettingsActions, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSettingsButton, {
56679
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(QaSettingsActions, { children: [copyImpossible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyHint, { testId: "qa-settings-tokens-copy-hint" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSettingsButton, {
56281
56680
  testId: "qa-settings-tokens-copy",
56282
56681
  label: copied ? "Скопировано" : "Скопировать",
56283
- onClick: () => copySecret(issued.token)
56284
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSettingsButton, {
56682
+ onClick: copySecret
56683
+ }), copyRefused ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyHint, { testId: "qa-settings-tokens-copy-hint" }) : null] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QaSettingsButton, {
56285
56684
  testId: "qa-settings-tokens-ack",
56286
56685
  tone: "primary",
56287
56686
  label: "Я сохранил токен",
56288
- onClick: () => {
56289
- setIssued(null);
56290
- setCopied(false);
56291
- }
56687
+ onClick: () => setIssued(null)
56292
56688
  })] })
56293
56689
  ]
56294
56690
  }),
@@ -56617,11 +57013,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
56617
57013
  const [busy, setBusy] = (0, react.useState)(false);
56618
57014
  const [error, setError] = (0, react.useState)(null);
56619
57015
  const [saved, setSaved] = (0, react.useState)(false);
57016
+ const [attempted, setAttempted] = (0, react.useState)(false);
56620
57017
  const tooShort = next !== "" && next.length < MIN_PASSWORD_LENGTH$1;
56621
57018
  const mismatch = repeat !== "" && repeat !== next;
56622
57019
  const incomplete = current === "" || next === "" || repeat === "";
56623
57020
  const submit = (event) => {
56624
57021
  event.preventDefault();
57022
+ setAttempted(true);
56625
57023
  if (busy || incomplete || mismatch || tooShort) return;
56626
57024
  setBusy(true);
56627
57025
  setError(null);
@@ -56635,12 +57033,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
56635
57033
  setCurrent("");
56636
57034
  setNext("");
56637
57035
  setRepeat("");
57036
+ setAttempted(false);
56638
57037
  setSaved(true);
56639
57038
  });
56640
57039
  };
56641
57040
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
56642
57041
  className: "dsh-qa-settings__page",
56643
57042
  "data-testid": "qa-settings-password",
57043
+ noValidate: true,
56644
57044
  onSubmit: submit,
56645
57045
  children: [
56646
57046
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -56694,6 +57094,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
56694
57094
  onChange: (event) => setRepeat(event.currentTarget.value)
56695
57095
  })
56696
57096
  }),
57097
+ attempted && incomplete ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
57098
+ className: "dsh-qa-settings__field-hint",
57099
+ "data-testid": "qa-settings-password-incomplete",
57100
+ children: "Заполните все поля."
57101
+ }) : null,
56697
57102
  tooShort ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
56698
57103
  className: "dsh-qa-settings__field-hint",
56699
57104
  "data-testid": "qa-settings-password-too-short",
@@ -61315,7 +61720,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
61315
61720
  //#endregion
61316
61721
  //#region src/client/admin/pages/Users.tsx
61317
61722
  const PAGE_SIZE = 25;
61318
- /** The Host's floor; the form refuses a shorter password before the round trip. */
61723
+ /** The Host's floor; the queue answers a shorter password before the round trip. */
61319
61724
  const MIN_PASSWORD_LENGTH = 8;
61320
61725
  /**
61321
61726
  * The forgotten-password queue: an account asked for a reset from the sign-in
@@ -61429,7 +61834,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
61429
61834
  type: "password",
61430
61835
  "data-testid": "qa-admin-password-reset-input",
61431
61836
  autoComplete: "new-password",
61432
- minLength: MIN_PASSWORD_LENGTH,
61433
61837
  placeholder: "не короче 8 символов",
61434
61838
  disabled: busy === request.userId,
61435
61839
  value: drafts[request.userId] ?? "",
@@ -63771,7 +64175,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
63771
64175
  const listState = (0, react.useSyncExternalStore)(props.sessions.list.subscribe, props.sessions.list.getSnapshot, props.sessions.list.getSnapshot);
63772
64176
  const ui = useSessionUiState(state.chatKey);
63773
64177
  const rail = useRightRail(ui);
63774
- const { activeTurn, setActiveTurn, variantOffsets, setVariantOffsets, agentsOpen, setAgentsOpen, pendingAttachments, setPendingAttachments } = ui;
64178
+ const { activeTurn, setActiveTurn, variantOffsets, setVariantOffsets, agentsOpen, setAgentsOpen, setRailOpen, setRailTab, pendingAttachments, setPendingAttachments } = ui;
64179
+ /**
64180
+ * The file a card asked to open, with the chat it was asked in and the stamp
64181
+ * of the request. The chat is part of the arrival because the rail survives
64182
+ * switching chats: an arrival from the previous chat must not open its file
64183
+ * in this one. The stamp makes a second click on the same card a fresh
64184
+ * arrival rather than a path that never changed.
64185
+ */
64186
+ const [artifactArrival, setArtifactArrival] = (0, react.useState)(null);
64187
+ /** Counts the open requests, so the same file twice is two arrivals. */
64188
+ const artifactStamp = (0, react.useRef)(0);
63775
64189
  (0, react.useEffect)(() => {
63776
64190
  if (!route.active) return;
63777
64191
  const frame = requestAnimationFrame(() => {
@@ -63853,6 +64267,50 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
63853
64267
  if (element === null || !scrollToTranscriptAnchor(element, messageId)) return;
63854
64268
  nearBottom.current = isNearBottom(element);
63855
64269
  }, []);
64270
+ /**
64271
+ * A produced file the reader wants to see: open the rail on its workspace
64272
+ * viewer at exactly that file. The viewer is the panel's own — it reads under
64273
+ * the chat's fence and converts a Word document into a preview — so a card
64274
+ * under an answer points at that work instead of carrying a second viewer
64275
+ * that could disagree with it.
64276
+ */
64277
+ const handleArtifactOpen = (0, react.useCallback)((artifact) => {
64278
+ artifactStamp.current += 1;
64279
+ setArtifactArrival({
64280
+ path: artifact.path,
64281
+ chatKey: state.chatKey,
64282
+ stamp: artifactStamp.current
64283
+ });
64284
+ setAgentsOpen(false);
64285
+ setRailOpen(true);
64286
+ setRailTab("files");
64287
+ }, [
64288
+ setAgentsOpen,
64289
+ setRailOpen,
64290
+ setRailTab,
64291
+ state.chatKey
64292
+ ]);
64293
+ /**
64294
+ * A produced file the reader wants to keep: the same fenced read the panel
64295
+ * uses, handed to the browser's download machinery. A read that fails opens
64296
+ * the viewer instead, which reports the refusal rather than swallowing the
64297
+ * click.
64298
+ */
64299
+ const handleArtifactDownload = (0, react.useCallback)((artifact) => {
64300
+ const sessionId = state.sessionId;
64301
+ if (sessionId === null) return;
64302
+ boundSourceApi.readWorkspaceFile(sessionId, artifact.path).then((result) => {
64303
+ if (result.ok) {
64304
+ downloadWorkspaceFile(result.value);
64305
+ return;
64306
+ }
64307
+ handleArtifactOpen(artifact);
64308
+ }, () => handleArtifactOpen(artifact));
64309
+ }, [
64310
+ boundSourceApi,
64311
+ handleArtifactOpen,
64312
+ state.sessionId
64313
+ ]);
63856
64314
  const view = useTranscriptView(state.messages, variantOffsets);
63857
64315
  const railItems = view.railItems;
63858
64316
  (0, react.useLayoutEffect)(() => {
@@ -63885,6 +64343,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
63885
64343
  const fileGroups = (0, react.useMemo)(() => collectChatFiles(state.messages), [state.messages]);
63886
64344
  const attachmentCount = (0, react.useMemo)(() => countChatAttachments(fileGroups), [fileGroups]);
63887
64345
  const filesEnabled = attachmentCount > 0 || state.sessionId !== null && config.sources.filePreview.enabled;
64346
+ const workspaceReadable = state.sessionId !== null && config.sources.filePreview.enabled;
63888
64347
  const quickQuestions = (0, react.useMemo)(() => effectiveQuickQuestions(config.accounts.starters.enabled && accountsSnapshot.stage === "authed" ? accountsSnapshot.user.starters : void 0, config.suggestedQuestions), [config, accountsSnapshot]);
63889
64348
  const ownerNames = (0, react.useMemo)(() => config.accounts.enabled && config.accounts.showOtherUsersChats && accounts !== void 0 && accountsSnapshot.stage === "authed" && accountsSnapshot.user.role === "admin" ? accounts.ownerNames() : void 0, [
63890
64349
  config,
@@ -64042,6 +64501,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
64042
64501
  groups: fileGroups,
64043
64502
  resolveImage,
64044
64503
  onJumpToMessage: handleJumpToMessage,
64504
+ onArtifactOpen: workspaceReadable ? handleArtifactOpen : void 0,
64505
+ onArtifactDownload: workspaceReadable ? handleArtifactDownload : void 0,
64506
+ openArtifact: artifactArrival !== null && artifactArrival.chatKey === state.chatKey ? {
64507
+ path: artifactArrival.path,
64508
+ stamp: artifactArrival.stamp
64509
+ } : void 0,
64045
64510
  sessionId: state.sessionId ?? void 0,
64046
64511
  api: boundSourceApi
64047
64512
  })
@@ -64287,6 +64752,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
64287
64752
  onRegenerate: isLast && message.role === "assistant" && message.status === "committed" && controller !== void 0 ? handleRegenerate : void 0,
64288
64753
  onOpenSources: config.sources.enabled && config.sources.display.footer ? rail.openSources : void 0,
64289
64754
  onSourceDetail: config.sources.enabled ? rail.openSourceDetail : void 0,
64755
+ onArtifactOpen: workspaceReadable ? handleArtifactOpen : void 0,
64756
+ onArtifactDownload: workspaceReadable ? handleArtifactDownload : void 0,
64290
64757
  onRateFeedback: rateFeedback
64291
64758
  }), group !== void 0 && group.turns.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VariantSwitcher, {
64292
64759
  count: group.turns.length,
@@ -64946,8 +65413,8 @@ body[data-dsh-qa-surface="active"] div:has(>[data-shell-overlay])>:not([data-she
64946
65413
  .dsh-qa-settings__button:disabled{opacity:.6;cursor:default}
64947
65414
  .dsh-qa-settings__button--primary{border-color:transparent;background:var(--dsh-qa-brand);color:var(--dsh-qa-accent-contrast);font-weight:600}
64948
65415
  .dsh-qa-settings__button--primary:hover:not(:disabled){background:var(--dsh-qa-accent-hover);color:var(--dsh-qa-accent-contrast)}
64949
- .dsh-qa-settings__button--danger{border-color:var(--dsw-alias-label-error);color:var(--dsw-alias-label-error)}
64950
- .dsh-qa-settings__button--danger:hover:not(:disabled){background:var(--dsw-alias-bg-error);color:var(--dsw-alias-label-error)}
65416
+ .dsh-qa-settings__button--danger{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
65417
+ .dsh-qa-settings__button--danger:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);color:var(--dsw-alias-state-error-primary)}
64951
65418
  .dsh-qa-settings__empty{display:flex;flex-direction:column;align-items:flex-start;gap:10px;padding:22px 18px;border:1px dashed var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3)}
64952
65419
  .dsh-qa-settings__empty-title{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:20px}
64953
65420
  .dsh-qa-settings__search{appearance:none;width:100%;padding:8px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:19px}
@@ -65389,11 +65856,12 @@ a.dsh-qa-srcref:hover,button.dsh-qa-srcref:hover{border-color:var(--dsw-alias-la
65389
65856
  .dsh-qa-md-image{display:block;max-width:100%;height:auto;margin:0;border-radius:8px}
65390
65857
  .dsh-qa-md-image-alt{color:var(--dsw-alias-label-tertiary);font-style:italic}
65391
65858
  .dsh-qa-md-code{margin:16px 0;border-radius:12px;background:var(--dsw-alias-markdown-code-block,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-primary)}
65392
- .dsh-qa-md-code__banner{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-radius:12px 12px 0 0;background:var(--dsw-alias-markdown-code-block-banner,var(--dsw-alias-bg-layer-3))}
65393
- .dsh-qa-md-code__lang{min-width:0;overflow:hidden;color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}
65859
+ .dsh-qa-md-code__banner{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:9px 14px;border-radius:12px 12px 0 0;background:var(--dsw-alias-markdown-code-block-banner,var(--dsw-alias-bg-layer-3))}
65860
+ .dsh-qa-md-code__lang{flex:none;min-width:0;margin-right:auto;overflow:hidden;color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}
65394
65861
  .dsh-qa-md-code__copy{appearance:none;flex:none;padding:0;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;line-height:18px;cursor:pointer}
65395
65862
  .dsh-qa-md-code__copy:hover{color:var(--dsw-alias-label-primary)}
65396
65863
  .dsh-qa-md-code__copy:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsh-qa-accent));outline-offset:2px}
65864
+ .dsh-qa-copy-hint{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px;white-space:nowrap}
65397
65865
  .dsh-qa-md-code pre{overflow-x:auto;margin:0;padding:16px;border:0;border-radius:0 0 12px 12px;background:transparent;font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:var(--dsw-font-markdown-code-block-font-size,11px);line-height:var(--dsw-font-markdown-code-block-line-height,19px);white-space:pre-wrap;word-break:break-word}
65398
65866
  .dsh-qa-md-code pre code{display:block;padding:0;border:0;background:transparent;font:inherit}
65399
65867
  .dsh-qa-md-tok[data-tok="comment"]{color:var(--shiki-token-comment)}
@@ -65573,6 +66041,12 @@ a.dsh-qa-srcref:hover,button.dsh-qa-srcref:hover{border-color:var(--dsw-alias-la
65573
66041
  .dsh-qa-file button:hover{color:var(--dsh-qa-error)}
65574
66042
  .dsh-qa-file button:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsh-qa-accent));outline-offset:1px}
65575
66043
  .dsh-qa-file button svg{width:11px;height:11px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round}
66044
+ .dsh-qa-file .dsh-qa-file__open{display:flex;align-items:center;gap:10px;flex:1;min-width:0;width:auto;height:auto;padding:0;border-radius:6px;background:0 0;color:inherit;text-align:left;place-items:stretch;cursor:pointer}
66045
+ .dsh-qa-file .dsh-qa-file__open:hover{background:0 0;color:inherit}
66046
+ .dsh-qa-file .dsh-qa-file__open:hover .dsh-qa-file__name{color:var(--dsw-alias-brand-primary)}
66047
+ .dsh-qa-file .dsh-qa-file__download{width:auto;height:auto;padding:2px 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary);font-size:11px}
66048
+ .dsh-qa-file .dsh-qa-file__download:hover{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
66049
+ .dsh-qa-message__artifacts{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}
65576
66050
  .dsh-qa-composer__image{position:relative;display:block;width:64px;height:64px;flex:none}
65577
66051
  .dsh-qa-composer__image img{display:block;width:100%;height:100%;object-fit:cover;border-radius:10px;border:1px solid var(--dsw-alias-border-l2)}
65578
66052
  .dsh-qa-composer__image button{position:absolute;top:-6px;right:-6px;display:grid;place-items:center;width:18px;height:18px;padding:0;border:0;border-radius:999px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);cursor:pointer;box-shadow:var(--dsw-shadow-lv2)}
@@ -68459,7 +68933,7 @@ textarea.qa-card-control{height:auto;padding:8px 12px;line-height:1.5;resize:ver
68459
68933
  .qa-card-chip.off{color:var(--dsw-alias-label-tertiary)}
68460
68934
  .qa-card-notice{padding:9px 11px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary)}
68461
68935
  .qa-card-notice.warn{border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}
68462
- .qa-card-error{padding:9px 11px;border-radius:8px;background:var(--dsw-alias-bg-error);color:var(--dsw-alias-label-error);font-size:11px;line-height:1.5}
68936
+ .qa-card-error{padding:9px 11px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary, #b3261e) 8%, transparent);color:var(--dsw-alias-state-error-primary, #b3261e);font-size:11px;line-height:1.5}
68463
68937
  .qa-card-advanced{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:0 12px}
68464
68938
  .qa-card-advanced summary{cursor:pointer;padding:11px 0;font-size:12px;font-weight:600}
68465
68939
  .qa-card-advanced summary:focus-visible{outline:var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));outline-offset:-1px}