@recombine-ai/bosun 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/bin/bosun.js +7 -0
  2. package/build/client/_app/immutable/assets/0.b0jhAoSC.css +1 -0
  3. package/build/client/_app/immutable/assets/0.b0jhAoSC.css.br +0 -0
  4. package/build/client/_app/immutable/assets/0.b0jhAoSC.css.gz +0 -0
  5. package/build/client/_app/immutable/chunks/BTX1tKMW.js +1 -0
  6. package/build/client/_app/immutable/chunks/BTX1tKMW.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/BTX1tKMW.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/BVxLHCs8.js +1 -0
  9. package/build/client/_app/immutable/chunks/BVxLHCs8.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/BVxLHCs8.js.gz +0 -0
  11. package/build/client/_app/immutable/chunks/{De8YgKS3.js → ChVBNPUb.js} +1 -1
  12. package/build/client/_app/immutable/chunks/ChVBNPUb.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/ChVBNPUb.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/E9rdsnHb.js +1 -0
  15. package/build/client/_app/immutable/chunks/E9rdsnHb.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/E9rdsnHb.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/Z1_c4us_.js +3 -0
  18. package/build/client/_app/immutable/chunks/Z1_c4us_.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/Z1_c4us_.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/{Bj8jfBTi.js → lmnC3bvv.js} +1 -1
  21. package/build/client/_app/immutable/chunks/lmnC3bvv.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/lmnC3bvv.js.gz +0 -0
  23. package/build/client/_app/immutable/entry/{app.DK_wVDeR.js → app.BY5DP2K_.js} +2 -2
  24. package/build/client/_app/immutable/entry/app.BY5DP2K_.js.br +0 -0
  25. package/build/client/_app/immutable/entry/app.BY5DP2K_.js.gz +0 -0
  26. package/build/client/_app/immutable/entry/start.BeIIwvvV.js +1 -0
  27. package/build/client/_app/immutable/entry/start.BeIIwvvV.js.br +0 -0
  28. package/build/client/_app/immutable/entry/start.BeIIwvvV.js.gz +0 -0
  29. package/build/client/_app/immutable/nodes/{0.5fEbn14Q.js → 0.Cao0LJwW.js} +1 -1
  30. package/build/client/_app/immutable/nodes/0.Cao0LJwW.js.br +0 -0
  31. package/build/client/_app/immutable/nodes/0.Cao0LJwW.js.gz +0 -0
  32. package/build/client/_app/immutable/nodes/{1.CJrSN8zL.js → 1.Kjgu6yxL.js} +1 -1
  33. package/build/client/_app/immutable/nodes/1.Kjgu6yxL.js.br +0 -0
  34. package/build/client/_app/immutable/nodes/1.Kjgu6yxL.js.gz +0 -0
  35. package/build/client/_app/immutable/nodes/{10.SZwTlOEA.js → 10.yW2mHq85.js} +1 -1
  36. package/build/client/_app/immutable/nodes/10.yW2mHq85.js.br +0 -0
  37. package/build/client/_app/immutable/nodes/10.yW2mHq85.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/{17.D-KFl7Vk.js → 17.COHkD98p.js} +3 -3
  39. package/build/client/_app/immutable/nodes/17.COHkD98p.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/17.COHkD98p.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/18.DQVit7Md.js +11 -0
  42. package/build/client/_app/immutable/nodes/18.DQVit7Md.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/18.DQVit7Md.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/2.DVwYttKv.js +35 -0
  45. package/build/client/_app/immutable/nodes/2.DVwYttKv.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/2.DVwYttKv.js.gz +0 -0
  47. package/build/client/_app/immutable/nodes/{3.B1RGTOJ8.js → 3.BhmyTb3R.js} +1 -1
  48. package/build/client/_app/immutable/nodes/3.BhmyTb3R.js.br +0 -0
  49. package/build/client/_app/immutable/nodes/3.BhmyTb3R.js.gz +0 -0
  50. package/build/client/_app/immutable/nodes/{4.D2jcIUpD.js → 4.CxATX0ci.js} +1 -1
  51. package/build/client/_app/immutable/nodes/4.CxATX0ci.js.br +0 -0
  52. package/build/client/_app/immutable/nodes/4.CxATX0ci.js.gz +0 -0
  53. package/build/client/_app/immutable/nodes/{5.CUyCIyRd.js → 5.Dal1qxB1.js} +1 -1
  54. package/build/client/_app/immutable/nodes/5.Dal1qxB1.js.br +0 -0
  55. package/build/client/_app/immutable/nodes/5.Dal1qxB1.js.gz +0 -0
  56. package/build/client/_app/immutable/nodes/{6.B8HsGA2V.js → 6.iAOxBhJw.js} +1 -1
  57. package/build/client/_app/immutable/nodes/6.iAOxBhJw.js.br +0 -0
  58. package/build/client/_app/immutable/nodes/6.iAOxBhJw.js.gz +0 -0
  59. package/build/client/_app/immutable/nodes/{8.jY56-_OU.js → 8.DFhKu3dp.js} +1 -1
  60. package/build/client/_app/immutable/nodes/8.DFhKu3dp.js.br +0 -0
  61. package/build/client/_app/immutable/nodes/8.DFhKu3dp.js.gz +0 -0
  62. package/build/client/_app/immutable/nodes/9.DRIEv5m9.js +2 -0
  63. package/build/client/_app/immutable/nodes/9.DRIEv5m9.js.br +0 -0
  64. package/build/client/_app/immutable/nodes/9.DRIEv5m9.js.gz +0 -0
  65. package/build/client/_app/version.json +1 -1
  66. package/build/client/_app/version.json.br +0 -0
  67. package/build/client/_app/version.json.gz +0 -0
  68. package/build/server/chunks/{0-4QCErf9j.js → 0-CIzAMtA3.js} +3 -3
  69. package/build/server/chunks/{0-4QCErf9j.js.map → 0-CIzAMtA3.js.map} +1 -1
  70. package/build/server/chunks/{1-Dsjy8Hzc.js → 1-D1heuNbf.js} +2 -2
  71. package/build/server/chunks/{1-Dsjy8Hzc.js.map → 1-D1heuNbf.js.map} +1 -1
  72. package/build/server/chunks/{10-8061c8ui.js → 10-CjLtjXyZ.js} +2 -2
  73. package/build/server/chunks/{10-8061c8ui.js.map → 10-CjLtjXyZ.js.map} +1 -1
  74. package/build/server/chunks/{17-3_5wsIEX.js → 17-DklEmmOx.js} +4 -4
  75. package/build/server/chunks/{17-3_5wsIEX.js.map → 17-DklEmmOx.js.map} +1 -1
  76. package/build/server/chunks/{18-D73Mw140.js → 18-BJS-eRcz.js} +4 -4
  77. package/build/server/chunks/18-BJS-eRcz.js.map +1 -0
  78. package/build/server/chunks/{2-DM0VjzFi.js → 2-DCrAVYXs.js} +4 -4
  79. package/build/server/chunks/2-DCrAVYXs.js.map +1 -0
  80. package/build/server/chunks/{3-B3zlFGkL.js → 3-BpTqa4DU.js} +2 -2
  81. package/build/server/chunks/{3-B3zlFGkL.js.map → 3-BpTqa4DU.js.map} +1 -1
  82. package/build/server/chunks/{4-tESdMZpI.js → 4-B7GjwQzI.js} +2 -2
  83. package/build/server/chunks/{4-tESdMZpI.js.map → 4-B7GjwQzI.js.map} +1 -1
  84. package/build/server/chunks/{5-BJMTlq4U.js → 5-XjS__bcp.js} +2 -2
  85. package/build/server/chunks/{5-BJMTlq4U.js.map → 5-XjS__bcp.js.map} +1 -1
  86. package/build/server/chunks/{6-CDK2Aia6.js → 6-DWkWptmB.js} +2 -2
  87. package/build/server/chunks/{6-CDK2Aia6.js.map → 6-DWkWptmB.js.map} +1 -1
  88. package/build/server/chunks/{8-BJbDytoU.js → 8-_swAx6gL.js} +2 -2
  89. package/build/server/chunks/{8-BJbDytoU.js.map → 8-_swAx6gL.js.map} +1 -1
  90. package/build/server/chunks/{9-BNfIeSqk.js → 9-NEe24O4u.js} +4 -4
  91. package/build/server/chunks/9-NEe24O4u.js.map +1 -0
  92. package/build/server/chunks/{ChatStepTrace-DQL3qlna.js → ChatStepTrace-CX2Zolmp.js} +21 -8
  93. package/build/server/chunks/ChatStepTrace-CX2Zolmp.js.map +1 -0
  94. package/build/server/chunks/{_layout.svelte-DK7yl8vI.js → _layout.svelte-PtdpfgwJ.js} +66 -4
  95. package/build/server/chunks/_layout.svelte-PtdpfgwJ.js.map +1 -0
  96. package/build/server/chunks/{_page.svelte-ZWNBigwS.js → _page.svelte-C3gizoSD.js} +7 -4
  97. package/build/server/chunks/_page.svelte-C3gizoSD.js.map +1 -0
  98. package/build/server/chunks/{_page.svelte-D6bSXGHX.js → _page.svelte-CoCBnE2l.js} +8 -5
  99. package/build/server/chunks/_page.svelte-CoCBnE2l.js.map +1 -0
  100. package/build/server/chunks/{_page.svelte-ej3vdYeF.js → _page.svelte-DZvcjON6.js} +3 -2
  101. package/build/server/chunks/_page.svelte-DZvcjON6.js.map +1 -0
  102. package/build/server/chunks/steps-DzZCg39h.js +8 -0
  103. package/build/server/chunks/steps-DzZCg39h.js.map +1 -0
  104. package/build/server/index.js +1 -1
  105. package/build/server/index.js.map +1 -1
  106. package/build/server/manifest.js +13 -13
  107. package/build/server/manifest.js.map +1 -1
  108. package/package.json +4 -4
  109. package/src/backend/lib/backend-restart.ts +15 -0
  110. package/src/backend/roleplay/router.ts +42 -5
  111. package/src/backend/roleplay/schema-file.spec.ts +38 -0
  112. package/src/backend/roleplay/schema-file.ts +33 -0
  113. package/src/backend/tests/academy.ts +4 -2
  114. package/src/backend/tests/analyze.ts +2 -1
  115. package/src/backend/tests/emulate-agents.ts +13 -21
  116. package/src/backend/tests/pii-sanitized-test-drafts.spec.ts +162 -0
  117. package/src/backend/tests/pii-sanitized-test-drafts.ts +106 -0
  118. package/src/backend/tests/pii-sanitizer.spec.ts +145 -0
  119. package/src/backend/tests/pii-sanitizer.ts +171 -0
  120. package/src/backend/tests/router.ts +37 -0
  121. package/src/lib/agent-catalog.ts +4 -0
  122. package/src/lib/agents-context.ts +8 -0
  123. package/src/lib/api-telescope/client.ts +174 -0
  124. package/src/lib/api-telescope/index.ts +4 -0
  125. package/src/lib/api-telescope/latency-timeline-view-state.spec.ts +25 -0
  126. package/src/lib/api-telescope/latency-timeline-view-state.ts +86 -0
  127. package/src/lib/api-telescope/latency.ts +252 -0
  128. package/src/lib/api-telescope/source-selection.ts +20 -0
  129. package/src/lib/api-telescope/sources.server.ts +94 -0
  130. package/src/lib/api-telescope/types.ts +143 -0
  131. package/src/lib/components/Button.svelte +62 -0
  132. package/src/lib/components/CollapseExpand.svelte +425 -0
  133. package/src/lib/components/CopyTracesDropdown.svelte +118 -0
  134. package/src/lib/components/Dialog.svelte +69 -0
  135. package/src/lib/components/Dropdown.svelte +111 -0
  136. package/src/lib/components/History.svelte +174 -0
  137. package/src/lib/components/Icon.svelte +94 -0
  138. package/src/lib/components/Input.svelte +43 -0
  139. package/src/lib/components/Link.svelte +45 -0
  140. package/src/lib/components/Modal.svelte +45 -0
  141. package/src/lib/components/Panel.svelte +12 -0
  142. package/src/lib/components/PopoverModal.svelte +34 -0
  143. package/src/lib/components/Popper.svelte +109 -0
  144. package/src/lib/components/Tab.svelte +91 -0
  145. package/src/lib/components/Tabs.svelte +140 -0
  146. package/src/lib/components/agent-colors.ts +70 -0
  147. package/src/lib/dom/text-selection.ts +97 -0
  148. package/src/lib/edit/edit-vm.spec.ts +115 -0
  149. package/src/lib/edit/edit-vm.ts +169 -0
  150. package/src/lib/edit/edit-workspace-context.ts +56 -0
  151. package/src/lib/edit/highlight-styles.spec.ts +14 -0
  152. package/src/lib/edit/prompt-highlighter.spec.ts +80 -0
  153. package/src/lib/edit/prompt-highlighter.ts +155 -0
  154. package/src/lib/edit/quote-matching.ts +97 -0
  155. package/src/lib/monaco/bosun-theme.ts +125 -0
  156. package/src/lib/monaco/font.ts +2 -0
  157. package/src/lib/monaco-workers.ts +28 -0
  158. package/src/lib/steps.ts +19 -0
  159. package/src/lib/tests/pii-sanitized-test-draft.ts +50 -0
  160. package/src/lib/trpc-client.ts +16 -0
  161. package/src/lib/ws-client.ts +144 -0
  162. package/src/lib/ws-message.ts +150 -0
  163. package/build/client/_app/immutable/assets/0.B-Br6U4N.css +0 -1
  164. package/build/client/_app/immutable/assets/0.B-Br6U4N.css.br +0 -0
  165. package/build/client/_app/immutable/assets/0.B-Br6U4N.css.gz +0 -0
  166. package/build/client/_app/immutable/chunks/BOuo-4gP.js +0 -1
  167. package/build/client/_app/immutable/chunks/BOuo-4gP.js.br +0 -0
  168. package/build/client/_app/immutable/chunks/BOuo-4gP.js.gz +0 -0
  169. package/build/client/_app/immutable/chunks/Bj8jfBTi.js.br +0 -0
  170. package/build/client/_app/immutable/chunks/Bj8jfBTi.js.gz +0 -0
  171. package/build/client/_app/immutable/chunks/CX0Ekq8t.js +0 -1
  172. package/build/client/_app/immutable/chunks/CX0Ekq8t.js.br +0 -0
  173. package/build/client/_app/immutable/chunks/CX0Ekq8t.js.gz +0 -0
  174. package/build/client/_app/immutable/chunks/Cyj62X2W.js +0 -2
  175. package/build/client/_app/immutable/chunks/Cyj62X2W.js.br +0 -0
  176. package/build/client/_app/immutable/chunks/Cyj62X2W.js.gz +0 -0
  177. package/build/client/_app/immutable/chunks/DSDSeO7L.js +0 -1
  178. package/build/client/_app/immutable/chunks/DSDSeO7L.js.br +0 -0
  179. package/build/client/_app/immutable/chunks/DSDSeO7L.js.gz +0 -0
  180. package/build/client/_app/immutable/chunks/De8YgKS3.js.br +0 -1
  181. package/build/client/_app/immutable/chunks/De8YgKS3.js.gz +0 -0
  182. package/build/client/_app/immutable/entry/app.DK_wVDeR.js.br +0 -0
  183. package/build/client/_app/immutable/entry/app.DK_wVDeR.js.gz +0 -0
  184. package/build/client/_app/immutable/entry/start.xQy-2aGo.js +0 -1
  185. package/build/client/_app/immutable/entry/start.xQy-2aGo.js.br +0 -2
  186. package/build/client/_app/immutable/entry/start.xQy-2aGo.js.gz +0 -0
  187. package/build/client/_app/immutable/nodes/0.5fEbn14Q.js.br +0 -0
  188. package/build/client/_app/immutable/nodes/0.5fEbn14Q.js.gz +0 -0
  189. package/build/client/_app/immutable/nodes/1.CJrSN8zL.js.br +0 -0
  190. package/build/client/_app/immutable/nodes/1.CJrSN8zL.js.gz +0 -0
  191. package/build/client/_app/immutable/nodes/10.SZwTlOEA.js.br +0 -0
  192. package/build/client/_app/immutable/nodes/10.SZwTlOEA.js.gz +0 -0
  193. package/build/client/_app/immutable/nodes/17.D-KFl7Vk.js.br +0 -0
  194. package/build/client/_app/immutable/nodes/17.D-KFl7Vk.js.gz +0 -0
  195. package/build/client/_app/immutable/nodes/18.DqRdJY9A.js +0 -11
  196. package/build/client/_app/immutable/nodes/18.DqRdJY9A.js.br +0 -0
  197. package/build/client/_app/immutable/nodes/18.DqRdJY9A.js.gz +0 -0
  198. package/build/client/_app/immutable/nodes/2.B3Uuk25v.js +0 -35
  199. package/build/client/_app/immutable/nodes/2.B3Uuk25v.js.br +0 -0
  200. package/build/client/_app/immutable/nodes/2.B3Uuk25v.js.gz +0 -0
  201. package/build/client/_app/immutable/nodes/3.B1RGTOJ8.js.br +0 -0
  202. package/build/client/_app/immutable/nodes/3.B1RGTOJ8.js.gz +0 -0
  203. package/build/client/_app/immutable/nodes/4.D2jcIUpD.js.br +0 -0
  204. package/build/client/_app/immutable/nodes/4.D2jcIUpD.js.gz +0 -0
  205. package/build/client/_app/immutable/nodes/5.CUyCIyRd.js.br +0 -0
  206. package/build/client/_app/immutable/nodes/5.CUyCIyRd.js.gz +0 -0
  207. package/build/client/_app/immutable/nodes/6.B8HsGA2V.js.br +0 -0
  208. package/build/client/_app/immutable/nodes/6.B8HsGA2V.js.gz +0 -0
  209. package/build/client/_app/immutable/nodes/8.jY56-_OU.js.br +0 -0
  210. package/build/client/_app/immutable/nodes/8.jY56-_OU.js.gz +0 -0
  211. package/build/client/_app/immutable/nodes/9.v5dX6L9n.js +0 -2
  212. package/build/client/_app/immutable/nodes/9.v5dX6L9n.js.br +0 -0
  213. package/build/client/_app/immutable/nodes/9.v5dX6L9n.js.gz +0 -0
  214. package/build/server/chunks/18-D73Mw140.js.map +0 -1
  215. package/build/server/chunks/2-DM0VjzFi.js.map +0 -1
  216. package/build/server/chunks/9-BNfIeSqk.js.map +0 -1
  217. package/build/server/chunks/ChatStepTrace-DQL3qlna.js.map +0 -1
  218. package/build/server/chunks/_layout.svelte-DK7yl8vI.js.map +0 -1
  219. package/build/server/chunks/_page.svelte-D6bSXGHX.js.map +0 -1
  220. package/build/server/chunks/_page.svelte-ZWNBigwS.js.map +0 -1
  221. package/build/server/chunks/_page.svelte-ej3vdYeF.js.map +0 -1
  222. /package/build/client/_app/immutable/assets/{Popper.CLTyHc88.css → steps.CLTyHc88.css} +0 -0
  223. /package/build/client/_app/immutable/assets/{Popper.CLTyHc88.css.br → steps.CLTyHc88.css.br} +0 -0
  224. /package/build/client/_app/immutable/assets/{Popper.CLTyHc88.css.gz → steps.CLTyHc88.css.gz} +0 -0
@@ -0,0 +1,80 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { createPromptHighlighter } from './prompt-highlighter'
4
+
5
+ function positionAt(text: string, offset: number): { lineNumber: number; column: number } {
6
+ const clamped = Math.max(0, Math.min(text.length, offset))
7
+ const before = text.slice(0, clamped)
8
+ const lineNumber = before.split('\n').length
9
+ const lastNl = before.lastIndexOf('\n')
10
+ const column = clamped - (lastNl === -1 ? 0 : lastNl + 1) + 1
11
+ return { lineNumber, column }
12
+ }
13
+
14
+ class Range {
15
+ constructor(
16
+ public startLineNumber: number,
17
+ public startColumn: number,
18
+ public endLineNumber: number,
19
+ public endColumn: number
20
+ ) {}
21
+ }
22
+
23
+ describe('prompt-highlighter', () => {
24
+ it('adds overview ruler marks and can focus next occurrence', () => {
25
+ const text = 'hello world\nhello again'
26
+ const model = {
27
+ getValue: () => text,
28
+ getPositionAt: (offset: number) => positionAt(text, offset)
29
+ }
30
+
31
+ const highlightCollection = { set: vi.fn(), clear: vi.fn() }
32
+ const focusCollection = { set: vi.fn(), clear: vi.fn() }
33
+ const collections = [highlightCollection, focusCollection]
34
+
35
+ const editor = {
36
+ getModel: () => model,
37
+ createDecorationsCollection: vi.fn(() => collections.shift()),
38
+ onDidChangeModelContent: vi.fn(() => ({ dispose: vi.fn() })),
39
+ setSelection: vi.fn(),
40
+ revealRangeInCenter: vi.fn(),
41
+ focus: vi.fn()
42
+ } as any
43
+
44
+ const monaco = {
45
+ Range,
46
+ editor: {
47
+ ScrollType: { Smooth: 0 },
48
+ OverviewRulerLane: { Center: 2 }
49
+ }
50
+ } as any
51
+
52
+ const highlighter = createPromptHighlighter(editor, monaco)
53
+
54
+ highlighter.setHighlights({
55
+ quotes: [{ quote: 'hello', count: 3 }],
56
+ focusQuote: null,
57
+ focusNonce: 0
58
+ })
59
+
60
+ expect(highlightCollection.set).toHaveBeenCalledTimes(1)
61
+ const decorations = highlightCollection.set.mock.calls[0][0]
62
+ expect(decorations).toHaveLength(2)
63
+ expect(decorations[0].options.inlineClassName).toContain('analysis-quote-l3')
64
+ expect(decorations[0].options.overviewRuler.position).toBe(
65
+ monaco.editor.OverviewRulerLane.Center
66
+ )
67
+ expect(decorations[0].options.overviewRuler.color).toContain('rgba(220, 38, 38')
68
+
69
+ highlighter.setHighlights({
70
+ quotes: [{ quote: 'hello', count: 3 }],
71
+ focusQuote: 'hello',
72
+ focusNonce: 1
73
+ })
74
+
75
+ expect(focusCollection.set).toHaveBeenCalledTimes(1)
76
+ expect(editor.revealRangeInCenter).toHaveBeenCalledTimes(1)
77
+ expect(editor.setSelection).toHaveBeenCalledTimes(1)
78
+ expect(editor.focus).toHaveBeenCalledTimes(1)
79
+ })
80
+ })
@@ -0,0 +1,155 @@
1
+ import type * as Monaco from 'monaco-editor'
2
+
3
+ import type { PromptHighlights, QuoteCount } from './edit-vm'
4
+ import type { OffsetRange } from './quote-matching'
5
+ import { findAllOverlappingBestEffort } from './quote-matching'
6
+
7
+ export interface PromptHighlighter {
8
+ setHighlights: (highlights: PromptHighlights | null) => void
9
+ dispose: () => void
10
+ }
11
+
12
+ export function createPromptHighlighter(
13
+ editor: Monaco.editor.IStandaloneCodeEditor,
14
+ monaco: typeof import('monaco-editor')
15
+ ): PromptHighlighter {
16
+ const highlightDecorations = editor.createDecorationsCollection()
17
+ const focusDecorations = editor.createDecorationsCollection()
18
+
19
+ let currentHighlights: PromptHighlights | null = null
20
+ let highlightRangesByQuote = new Map<string, OffsetRange[]>()
21
+ let cursorByQuote = new Map<string, number>()
22
+ let lastHandledFocusNonce = 0
23
+
24
+ function resetCaches() {
25
+ highlightRangesByQuote = new Map()
26
+ cursorByQuote = new Map()
27
+ lastHandledFocusNonce = 0
28
+ }
29
+
30
+ function clamp(n: number, min: number, max: number): number {
31
+ if (Number.isNaN(n)) return min
32
+ return Math.max(min, Math.min(max, n))
33
+ }
34
+
35
+ function overviewColorForLevel(level: number): string {
36
+ const l = clamp(level, 1, 10)
37
+ // Use a slightly higher opacity than the inline background so marks are visible in the overview ruler.
38
+ const alpha = clamp(0.22 + l * 0.06, 0.28, 0.82)
39
+ return `rgba(220, 38, 38, ${alpha})`
40
+ }
41
+
42
+ function getHighlightRanges(quote: string, text: string): OffsetRange[] {
43
+ const q = String(quote ?? '')
44
+ if (!q) return []
45
+ const cached = highlightRangesByQuote.get(q)
46
+ if (cached) return cached
47
+ const found = findAllOverlappingBestEffort(text, q)
48
+ highlightRangesByQuote.set(q, found)
49
+ return found
50
+ }
51
+
52
+ function updateHighlightDecorations(quotes: QuoteCount[]) {
53
+ const model = editor.getModel()
54
+ if (!model) return
55
+
56
+ const text = model.getValue() ?? ''
57
+ const decorations: Monaco.editor.IModelDeltaDecoration[] = []
58
+
59
+ // Weaker highlights first so stronger quotes can visually dominate overlaps.
60
+ const ordered = [...(quotes || [])].sort((a, b) => (a.count || 0) - (b.count || 0))
61
+ for (const q of ordered) {
62
+ const quote = String(q.quote || '')
63
+ if (!quote) continue
64
+ const level = clamp(Number(q.count || 0), 1, 10)
65
+ const color = overviewColorForLevel(level)
66
+ const matches = getHighlightRanges(quote, text)
67
+ for (const m of matches) {
68
+ const startPos = model.getPositionAt(m.start)
69
+ const endPos = model.getPositionAt(m.end)
70
+ decorations.push({
71
+ range: new monaco.Range(
72
+ startPos.lineNumber,
73
+ startPos.column,
74
+ endPos.lineNumber,
75
+ endPos.column
76
+ ),
77
+ options: {
78
+ inlineClassName: `analysis-quote analysis-quote-l${level}`,
79
+ overviewRuler: { color, position: monaco.editor.OverviewRulerLane.Center }
80
+ }
81
+ })
82
+ }
83
+ }
84
+
85
+ highlightDecorations.set(decorations)
86
+ }
87
+
88
+ function revealNextOccurrence(quote: string) {
89
+ const model = editor.getModel()
90
+ if (!model) return
91
+
92
+ const text = model.getValue() ?? ''
93
+ const occurrences = getHighlightRanges(quote, text)
94
+ if (occurrences.length === 0) return
95
+
96
+ const current = cursorByQuote.get(quote) ?? -1
97
+ const nextIdx = (current + 1) % occurrences.length
98
+ cursorByQuote.set(quote, nextIdx)
99
+
100
+ const chosen = occurrences[nextIdx]
101
+ const startPos = model.getPositionAt(chosen.start)
102
+ const endPos = model.getPositionAt(chosen.end)
103
+ const range = new monaco.Range(
104
+ startPos.lineNumber,
105
+ startPos.column,
106
+ endPos.lineNumber,
107
+ endPos.column
108
+ )
109
+
110
+ focusDecorations.set([{ range, options: { inlineClassName: 'analysis-quote-focus' } }])
111
+ editor.setSelection(range)
112
+ editor.revealRangeInCenter(range, monaco.editor.ScrollType.Smooth)
113
+ editor.focus()
114
+ }
115
+
116
+ function applyHighlights(next: PromptHighlights | null) {
117
+ currentHighlights = next
118
+ if (!currentHighlights) {
119
+ highlightDecorations.clear()
120
+ focusDecorations.clear()
121
+ resetCaches()
122
+ return
123
+ }
124
+
125
+ updateHighlightDecorations(currentHighlights.quotes || [])
126
+
127
+ if (currentHighlights.focusQuote && currentHighlights.focusNonce !== lastHandledFocusNonce) {
128
+ lastHandledFocusNonce = currentHighlights.focusNonce
129
+ revealNextOccurrence(currentHighlights.focusQuote)
130
+ } else if (!currentHighlights.focusQuote) {
131
+ focusDecorations.clear()
132
+ }
133
+ }
134
+
135
+ const onDidChange = editor.onDidChangeModelContent(() => {
136
+ if (!currentHighlights) return
137
+ highlightRangesByQuote = new Map()
138
+ focusDecorations.clear()
139
+ updateHighlightDecorations(currentHighlights.quotes || [])
140
+ })
141
+
142
+ return {
143
+ setHighlights: applyHighlights,
144
+ dispose: () => {
145
+ try {
146
+ onDidChange.dispose()
147
+ highlightDecorations.clear()
148
+ focusDecorations.clear()
149
+ } finally {
150
+ currentHighlights = null
151
+ resetCaches()
152
+ }
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,97 @@
1
+ export type OffsetRange = { start: number; end: number }
2
+
3
+ export function findAllOverlappingBestEffort(haystack: string, needle: string): OffsetRange[] {
4
+ const text = String(haystack ?? '')
5
+ const variants = buildNeedleVariants(needle)
6
+ const normalizedText = normalizeForMatching(text)
7
+
8
+ for (const variant of variants) {
9
+ const exact = findAllOverlappingExact(text, variant)
10
+ if (exact.length > 0) return exact
11
+
12
+ const flexible = findAllOverlappingFlexible(text, variant)
13
+ if (flexible.length > 0) return flexible
14
+
15
+ const normalizedNeedle = normalizeForMatching(variant)
16
+ if (normalizedText !== text || normalizedNeedle !== variant) {
17
+ const exactNorm = findAllOverlappingExact(normalizedText, normalizedNeedle)
18
+ if (exactNorm.length > 0) return exactNorm
19
+
20
+ const flexibleNorm = findAllOverlappingFlexible(normalizedText, normalizedNeedle)
21
+ if (flexibleNorm.length > 0) return flexibleNorm
22
+ }
23
+ }
24
+
25
+ return []
26
+ }
27
+
28
+ export function findAllOverlappingExact(haystack: string, needle: string): OffsetRange[] {
29
+ const n = String(needle ?? '')
30
+ if (!n) return []
31
+
32
+ const out: OffsetRange[] = []
33
+ let idx = 0
34
+ while (idx <= haystack.length - n.length) {
35
+ const found = haystack.indexOf(n, idx)
36
+ if (found === -1) break
37
+ out.push({ start: found, end: found + n.length })
38
+ idx = found + 1
39
+ }
40
+ return out
41
+ }
42
+
43
+ // Whitespace-insensitive overlap matching: treats any whitespace run in the quote as `\\s+` in the prompt.
44
+ export function findAllOverlappingFlexible(haystack: string, needle: string): OffsetRange[] {
45
+ const raw = String(needle ?? '')
46
+ if (!raw.trim()) return []
47
+
48
+ const pattern = escapeRegex(raw).replace(/\s+/g, '\\s+')
49
+ let re: RegExp
50
+ try {
51
+ re = new RegExp(`(?=(${pattern}))`, 'gu')
52
+ } catch {
53
+ return []
54
+ }
55
+
56
+ const out: OffsetRange[] = []
57
+ let m: RegExpExecArray | null
58
+ while ((m = re.exec(haystack)) !== null) {
59
+ const start = m.index
60
+ const matched = m[1] ?? ''
61
+ const end = start + matched.length
62
+ if (end > start) out.push({ start, end })
63
+ if (re.lastIndex === start) re.lastIndex += 1
64
+ }
65
+ return out
66
+ }
67
+
68
+ export function buildNeedleVariants(raw: string): string[] {
69
+ const base = String(raw ?? '')
70
+ const trimmed = base.trim()
71
+ const variants = new Set<string>()
72
+ for (const v of [base, trimmed]) {
73
+ if (v) variants.add(v)
74
+ }
75
+
76
+ // Common "model-added" prefixes (bullets/numbering) that can break exact matching.
77
+ const noBullet = trimmed.replace(/^[-*•]\s+/, '')
78
+ if (noBullet && noBullet !== trimmed) variants.add(noBullet)
79
+ const noNumbering = trimmed.replace(/^(?:\(?\d+\)?[.)]|[a-zA-Z][.)])\s+/, '')
80
+ if (noNumbering && noNumbering !== trimmed) variants.add(noNumbering)
81
+
82
+ return [...variants].filter((v) => v.trim().length > 0)
83
+ }
84
+
85
+ // Length-preserving normalization for matching across punctuation variants.
86
+ export function normalizeForMatching(text: string): string {
87
+ return String(text ?? '')
88
+ .replace(/\r/g, '\n')
89
+ .replace(/[\u00A0\u2007\u202F\u2009\u200A\u200B\uFEFF]/g, ' ')
90
+ .replace(/[\u201C\u201D\u201E\u00AB\u00BB]/g, '"')
91
+ .replace(/[\u2018\u2019]/g, "'")
92
+ .replace(/[\u2013\u2014]/g, '-')
93
+ }
94
+
95
+ export function escapeRegex(text: string): string {
96
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
97
+ }
@@ -0,0 +1,125 @@
1
+ import type * as Monaco from 'monaco-editor'
2
+
3
+ export interface PromptDecorations {
4
+ update: () => void
5
+ dispose: () => void
6
+ }
7
+
8
+ export function createPromptDecorations(
9
+ monaco: typeof Monaco,
10
+ editor: Monaco.editor.IStandaloneCodeEditor
11
+ ) {
12
+ const collection = editor.createDecorationsCollection()
13
+
14
+ function update() {
15
+ const model = editor.getModel()
16
+ if (!model) return
17
+
18
+ const decorations: Monaco.editor.IModelDeltaDecoration[] = []
19
+ const lineCount = model.getLineCount()
20
+
21
+ for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) {
22
+ const lineText = model.getLineContent(lineNumber)
23
+
24
+ // Headings
25
+ const match = /^(#+)\s/.exec(lineText)
26
+ if (match) {
27
+ const level = match[1].length
28
+ let cls = 'prompt-heading-3'
29
+
30
+ if (level === 1) {
31
+ cls = 'prompt-heading-1'
32
+ } else if (level === 2) {
33
+ cls = 'prompt-heading-2'
34
+ }
35
+
36
+ decorations.push({
37
+ range: new monaco.Range(lineNumber, 1, lineNumber, lineText.length + 1),
38
+ options: {
39
+ isWholeLine: true,
40
+ inlineClassName: cls
41
+ }
42
+ })
43
+ }
44
+
45
+ // Symbols '#' and '*'
46
+ for (let column = 1; column <= lineText.length; column++) {
47
+ const ch = lineText[column - 1]
48
+ if (ch === '#' || ch === '*') {
49
+ decorations.push({
50
+ range: new monaco.Range(lineNumber, column, lineNumber, column + 1),
51
+ options: {
52
+ inlineClassName: 'prompt-symbol'
53
+ }
54
+ })
55
+ }
56
+ }
57
+
58
+ // Subnumbers
59
+ const subMatch = lineText.match(/^(\s+)(\d+(?:\.\d+)+\.?)(?=[\s)]|$)/)
60
+ if (subMatch) {
61
+ const leading = subMatch[1]
62
+ const numberText = subMatch[2]
63
+
64
+ const startColumn = leading.length + 1
65
+ const endColumn = startColumn + numberText.length
66
+
67
+ decorations.push({
68
+ range: new monaco.Range(lineNumber, startColumn, lineNumber, endColumn),
69
+ options: {
70
+ inlineClassName: 'prompt-subnumber'
71
+ }
72
+ })
73
+ }
74
+ }
75
+
76
+ collection.set(decorations)
77
+ }
78
+
79
+ function dispose() {
80
+ collection.clear()
81
+ }
82
+
83
+ return { update, dispose }
84
+ }
85
+
86
+ export const bosunDarkTheme: Monaco.editor.IStandaloneThemeData = {
87
+ base: 'vs-dark',
88
+ inherit: true,
89
+ rules: [
90
+ // default text
91
+ { token: '', foreground: 'DDDDDD' },
92
+ // keywords & others
93
+ { token: 'string.md', foreground: 'DDDDDD' },
94
+ { token: 'keyword.md', foreground: '767676' },
95
+ { token: 'strong.md', foreground: 'DDDDDD', fontStyle: 'bold' },
96
+ { token: 'string.target.md', foreground: '00B58A' },
97
+ { token: 'tag.md', foreground: 'CE9178' },
98
+ { token: 'string.link.md', foreground: '569cd6', fontStyle: 'underline' }
99
+ ],
100
+ colors: {
101
+ // editor area
102
+ 'editor.background': '#1D1D1D',
103
+ 'editor.foreground': '#AFAFAF',
104
+
105
+ // (optional but nice to match)
106
+ 'editorLineNumber.foreground': '#636363',
107
+ 'editorLineNumber.activeForeground': '#767676',
108
+ 'editorCursor.foreground': '#DDDDDD',
109
+ 'editor.selectionBackground': '#4B5563D0',
110
+ 'editor.inactiveSelectionBackground': '#4B5563A0',
111
+ 'editor.lineHighlightBackground': '#FFFFFF08',
112
+ 'editorLineNumber.background': '#1D1D1D',
113
+
114
+ // Nunjucks tag highlight (DocumentHighlightProvider)
115
+ 'editor.wordHighlightBackground': '#ff480159'
116
+ }
117
+ }
118
+
119
+ let hasDefinedBosunTheme = false
120
+
121
+ export function ensureBosunTheme(monaco: typeof Monaco) {
122
+ if (hasDefinedBosunTheme) return
123
+ monaco.editor.defineTheme('bosun-dark', bosunDarkTheme)
124
+ hasDefinedBosunTheme = true
125
+ }
@@ -0,0 +1,2 @@
1
+ export const MONACO_FONT_FAMILY =
2
+ '"Ubuntu Sans Mono", "PT Mono", "DM Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
@@ -0,0 +1,28 @@
1
+ import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
2
+ import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
3
+ import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
4
+ import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
5
+ import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
6
+
7
+ export function ensureMonacoWorkers() {
8
+ if (typeof window === 'undefined') return
9
+
10
+ const env = (window as any).MonacoEnvironment as
11
+ | {
12
+ getWorker?: (moduleId: unknown, label: string) => Worker
13
+ }
14
+ | undefined
15
+
16
+ if (env?.getWorker) return
17
+ ;(window as any).MonacoEnvironment = {
18
+ ...env,
19
+ getWorker: function (_moduleId: unknown, label: string) {
20
+ if (label === 'json') return new JsonWorker()
21
+ if (label === 'css' || label === 'scss' || label === 'less') return new CssWorker()
22
+ if (label === 'html' || label === 'handlebars' || label === 'razor') return new HtmlWorker()
23
+ if (label === 'typescript' || label === 'javascript') return new TsWorker()
24
+
25
+ return new EditorWorker()
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Recognises the name of an agent's primary conversational step. This is the single definition of
3
+ * that convention in Bosun — everywhere else should call this rather than test names inline, so the
4
+ * convention can change in one place.
5
+ *
6
+ * Projects name the step `main`, or `main[<anything>]` when they need several variants of it — for
7
+ * example one per region. **The bracket contents are opaque to Bosun.** What a project puts inside
8
+ * is its own business, so Bosun recognises the shape and never parses the inside. It follows that
9
+ * Bosun cannot tell which variant is "active"; where one has to be chosen, it simply takes the
10
+ * first match.
11
+ *
12
+ * `mainReply` is kept as a legacy alias for projects that used it.
13
+ */
14
+ export function isMainStepName(name: string | null | undefined): boolean {
15
+ if (!name) return false
16
+ if (name === 'main' || name === 'mainReply') return true
17
+ // Require the bracket so unrelated names beginning with "main" (e.g. `maintenance`) don't match.
18
+ return name.startsWith('main[') && name.endsWith(']')
19
+ }
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod'
2
+
3
+ export const TraceTestSeedSchema = z
4
+ .object({
5
+ pinned: z.literal('TRUE'),
6
+ step: z.string().optional(),
7
+ evaluation_mode: z.enum(['assertion', 'json-deep-equal']),
8
+ context_mode: z.literal('replace'),
9
+ assertion_type: z.literal('llm-rubric'),
10
+ assertion_value: z.string(),
11
+ conversation: z.string(),
12
+ expected_output: z.string(),
13
+ similarity_threshold: z.string(),
14
+ timeout: z.string(),
15
+ context: z.record(z.unknown()),
16
+ mocks: z.record(z.unknown()).optional()
17
+ })
18
+ .strict()
19
+
20
+ export type TraceTestSeed = z.infer<typeof TraceTestSeedSchema>
21
+
22
+ export const TraceTestReviewContentSchema = TraceTestSeedSchema.pick({
23
+ conversation: true,
24
+ expected_output: true,
25
+ context: true,
26
+ mocks: true
27
+ }).strict()
28
+
29
+ export type TraceTestReviewContent = z.infer<typeof TraceTestReviewContentSchema>
30
+
31
+ export const PIIFindingCategorySchema = z.enum([
32
+ 'person',
33
+ 'contact',
34
+ 'address',
35
+ 'date_of_birth',
36
+ 'account_identifier',
37
+ 'vehicle_identifier',
38
+ 'financial',
39
+ 'special_category',
40
+ 'other'
41
+ ])
42
+
43
+ export const PIIFindingSchema = z
44
+ .object({
45
+ category: PIIFindingCategorySchema,
46
+ paths: z.array(z.string())
47
+ })
48
+ .strict()
49
+
50
+ export type PIIFinding = z.infer<typeof PIIFindingSchema>
@@ -0,0 +1,16 @@
1
+ import { env } from '$env/dynamic/public'
2
+ import { createTRPCClient, httpLink } from '@trpc/client'
3
+ import type { AppRouter } from '../backend/server'
4
+
5
+ const trpcUrl =
6
+ env.PUBLIC_TRPC_URL ??
7
+ `http://${env.PUBLIC_TRPC_HOSTNAME ?? 'localhost'}:${env.PUBLIC_TRPC_PORT ?? '2022'}`
8
+
9
+ // ref: https://github.com/trpc/trpc/blob/main/examples/standalone-server/src/client.ts
10
+ export const trpc = createTRPCClient<AppRouter>({
11
+ links: [
12
+ httpLink({
13
+ url: trpcUrl
14
+ })
15
+ ]
16
+ })