@my-life-buddies/cli 0.13.3 → 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 (193) hide show
  1. package/README.md +74 -22
  2. package/dist/bin/buddy.js +29 -10
  3. package/dist/bin/buddy.js.map +2 -2
  4. package/dist/bin/core.js +1480 -611
  5. package/dist/bin/core.js.map +4 -4
  6. package/dist/bin/preview.js +324 -27
  7. package/dist/bin/preview.js.map +4 -4
  8. package/dist/web/app.css +65 -6
  9. package/dist/web/app.js +103 -39
  10. package/dist/web/diagnostics.js +184 -0
  11. package/dist/web/index.html +34 -34
  12. package/dist/web/widgets.css +59 -28
  13. package/dist/web/widgets.js +230 -76
  14. package/package.json +3 -3
  15. package/resources/README.md +6 -50
  16. package/resources/agent-template/package-lock.json +4 -4
  17. package/resources/agent-template/package.json +2 -2
  18. package/resources/buddy-creator/INSTALL.md +0 -25
  19. package/resources/buddy-creator/PATCHES.md +0 -53
  20. package/resources/buddy-creator/SKILL.md +0 -76
  21. package/resources/buddy-creator/THIRD_PARTY_NOTICES.md +0 -154
  22. package/resources/buddy-creator/agents/openai.yaml +0 -4
  23. package/resources/buddy-creator/assets/licenses/bail.txt +0 -26
  24. package/resources/buddy-creator/assets/licenses/ccount.txt +0 -26
  25. package/resources/buddy-creator/assets/licenses/character-entities-html4.txt +0 -26
  26. package/resources/buddy-creator/assets/licenses/character-entities-legacy.txt +0 -26
  27. package/resources/buddy-creator/assets/licenses/character-entities.txt +0 -26
  28. package/resources/buddy-creator/assets/licenses/character-reference-invalid.txt +0 -26
  29. package/resources/buddy-creator/assets/licenses/comma-separated-tokens.txt +0 -26
  30. package/resources/buddy-creator/assets/licenses/debug.txt +0 -24
  31. package/resources/buddy-creator/assets/licenses/decode-named-character-reference.txt +0 -26
  32. package/resources/buddy-creator/assets/licenses/dequal.txt +0 -25
  33. package/resources/buddy-creator/assets/licenses/devlop.txt +0 -26
  34. package/resources/buddy-creator/assets/licenses/escape-string-regexp.txt +0 -13
  35. package/resources/buddy-creator/assets/licenses/estree-util-is-identifier-name.txt +0 -26
  36. package/resources/buddy-creator/assets/licenses/extend.txt +0 -27
  37. package/resources/buddy-creator/assets/licenses/hast-util-to-jsx-runtime.txt +0 -26
  38. package/resources/buddy-creator/assets/licenses/hast-util-whitespace.txt +0 -26
  39. package/resources/buddy-creator/assets/licenses/html-url-attributes.txt +0 -25
  40. package/resources/buddy-creator/assets/licenses/inline-style-parser.txt +0 -13
  41. package/resources/buddy-creator/assets/licenses/is-alphabetical.txt +0 -26
  42. package/resources/buddy-creator/assets/licenses/is-alphanumerical.txt +0 -26
  43. package/resources/buddy-creator/assets/licenses/is-decimal.txt +0 -26
  44. package/resources/buddy-creator/assets/licenses/is-hexadecimal.txt +0 -26
  45. package/resources/buddy-creator/assets/licenses/is-plain-obj.txt +0 -13
  46. package/resources/buddy-creator/assets/licenses/longest-streak.txt +0 -26
  47. package/resources/buddy-creator/assets/licenses/markdown-table.txt +0 -26
  48. package/resources/buddy-creator/assets/licenses/mdast-util-find-and-replace.txt +0 -26
  49. package/resources/buddy-creator/assets/licenses/mdast-util-from-markdown.txt +0 -26
  50. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-autolink-literal.txt +0 -26
  51. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-footnote.txt +0 -26
  52. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-strikethrough.txt +0 -26
  53. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-table.txt +0 -26
  54. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-task-list-item.txt +0 -26
  55. package/resources/buddy-creator/assets/licenses/mdast-util-gfm.txt +0 -26
  56. package/resources/buddy-creator/assets/licenses/mdast-util-mdx-expression.txt +0 -26
  57. package/resources/buddy-creator/assets/licenses/mdast-util-mdx-jsx.txt +0 -26
  58. package/resources/buddy-creator/assets/licenses/mdast-util-mdxjs-esm.txt +0 -26
  59. package/resources/buddy-creator/assets/licenses/mdast-util-phrasing.txt +0 -27
  60. package/resources/buddy-creator/assets/licenses/mdast-util-to-hast.txt +0 -26
  61. package/resources/buddy-creator/assets/licenses/mdast-util-to-markdown.txt +0 -26
  62. package/resources/buddy-creator/assets/licenses/mdast-util-to-string.txt +0 -26
  63. package/resources/buddy-creator/assets/licenses/micromark-core-commonmark.txt +0 -26
  64. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-autolink-literal.txt +0 -26
  65. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-footnote.txt +0 -26
  66. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-strikethrough.txt +0 -26
  67. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-table.txt +0 -26
  68. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-tagfilter.txt +0 -26
  69. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-task-list-item.txt +0 -26
  70. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm.txt +0 -26
  71. package/resources/buddy-creator/assets/licenses/micromark-factory-destination.txt +0 -26
  72. package/resources/buddy-creator/assets/licenses/micromark-factory-label.txt +0 -26
  73. package/resources/buddy-creator/assets/licenses/micromark-factory-space.txt +0 -26
  74. package/resources/buddy-creator/assets/licenses/micromark-factory-title.txt +0 -26
  75. package/resources/buddy-creator/assets/licenses/micromark-factory-whitespace.txt +0 -26
  76. package/resources/buddy-creator/assets/licenses/micromark-util-character.txt +0 -26
  77. package/resources/buddy-creator/assets/licenses/micromark-util-chunked.txt +0 -26
  78. package/resources/buddy-creator/assets/licenses/micromark-util-classify-character.txt +0 -26
  79. package/resources/buddy-creator/assets/licenses/micromark-util-combine-extensions.txt +0 -26
  80. package/resources/buddy-creator/assets/licenses/micromark-util-decode-numeric-character-reference.txt +0 -26
  81. package/resources/buddy-creator/assets/licenses/micromark-util-decode-string.txt +0 -26
  82. package/resources/buddy-creator/assets/licenses/micromark-util-encode.txt +0 -26
  83. package/resources/buddy-creator/assets/licenses/micromark-util-html-tag-name.txt +0 -26
  84. package/resources/buddy-creator/assets/licenses/micromark-util-normalize-identifier.txt +0 -26
  85. package/resources/buddy-creator/assets/licenses/micromark-util-resolve-all.txt +0 -26
  86. package/resources/buddy-creator/assets/licenses/micromark-util-sanitize-uri.txt +0 -26
  87. package/resources/buddy-creator/assets/licenses/micromark-util-subtokenize.txt +0 -26
  88. package/resources/buddy-creator/assets/licenses/micromark-util-symbol.txt +0 -26
  89. package/resources/buddy-creator/assets/licenses/micromark-util-types.txt +0 -26
  90. package/resources/buddy-creator/assets/licenses/micromark.txt +0 -26
  91. package/resources/buddy-creator/assets/licenses/ms.txt +0 -25
  92. package/resources/buddy-creator/assets/licenses/parse-entities.txt +0 -26
  93. package/resources/buddy-creator/assets/licenses/phosphor-icons--react.txt +0 -25
  94. package/resources/buddy-creator/assets/licenses/property-information.txt +0 -26
  95. package/resources/buddy-creator/assets/licenses/react-dom.txt +0 -25
  96. package/resources/buddy-creator/assets/licenses/react-markdown.txt +0 -25
  97. package/resources/buddy-creator/assets/licenses/react.txt +0 -25
  98. package/resources/buddy-creator/assets/licenses/remark-gfm.txt +0 -26
  99. package/resources/buddy-creator/assets/licenses/remark-parse.txt +0 -25
  100. package/resources/buddy-creator/assets/licenses/remark-rehype.txt +0 -26
  101. package/resources/buddy-creator/assets/licenses/remark-stringify.txt +0 -25
  102. package/resources/buddy-creator/assets/licenses/scheduler.txt +0 -25
  103. package/resources/buddy-creator/assets/licenses/space-separated-tokens.txt +0 -26
  104. package/resources/buddy-creator/assets/licenses/stringify-entities.txt +0 -26
  105. package/resources/buddy-creator/assets/licenses/style-to-js.txt +0 -26
  106. package/resources/buddy-creator/assets/licenses/style-to-object.txt +0 -26
  107. package/resources/buddy-creator/assets/licenses/trim-lines.txt +0 -26
  108. package/resources/buddy-creator/assets/licenses/trough.txt +0 -25
  109. package/resources/buddy-creator/assets/licenses/types--debug.txt +0 -25
  110. package/resources/buddy-creator/assets/licenses/types--estree-jsx.txt +0 -25
  111. package/resources/buddy-creator/assets/licenses/types--estree.txt +0 -25
  112. package/resources/buddy-creator/assets/licenses/types--hast.txt +0 -25
  113. package/resources/buddy-creator/assets/licenses/types--mdast.txt +0 -25
  114. package/resources/buddy-creator/assets/licenses/types--ms.txt +0 -25
  115. package/resources/buddy-creator/assets/licenses/types--unist.txt +0 -25
  116. package/resources/buddy-creator/assets/licenses/ungap--structured-clone.txt +0 -19
  117. package/resources/buddy-creator/assets/licenses/unified.txt +0 -25
  118. package/resources/buddy-creator/assets/licenses/unist-util-is.txt +0 -26
  119. package/resources/buddy-creator/assets/licenses/unist-util-position.txt +0 -26
  120. package/resources/buddy-creator/assets/licenses/unist-util-stringify-position.txt +0 -26
  121. package/resources/buddy-creator/assets/licenses/unist-util-visit-parents.txt +0 -26
  122. package/resources/buddy-creator/assets/licenses/unist-util-visit.txt +0 -26
  123. package/resources/buddy-creator/assets/licenses/vfile-message.txt +0 -26
  124. package/resources/buddy-creator/assets/licenses/vfile.txt +0 -25
  125. package/resources/buddy-creator/assets/licenses/zwitch.txt +0 -26
  126. package/resources/buddy-creator/assets/preview/assets/index-B5VmkoSx.js +0 -39
  127. package/resources/buddy-creator/assets/preview/assets/index-DWKP6b-3.css +0 -4
  128. package/resources/buddy-creator/assets/preview/brand-logo.png +0 -0
  129. package/resources/buddy-creator/assets/preview/brand.css +0 -100
  130. package/resources/buddy-creator/assets/preview/index.html +0 -7
  131. package/resources/buddy-creator/assets/preview/reader.css +0 -165
  132. package/resources/buddy-creator/assets/preview/reader.js +0 -135
  133. package/resources/buddy-creator/assets/preview/widgets.css +0 -1
  134. package/resources/buddy-creator/assets/preview/widgets.js +0 -168
  135. package/resources/buddy-creator/assets/widget-reference/conversation.png +0 -0
  136. package/resources/buddy-creator/assets/widget-reference/expanded.png +0 -0
  137. package/resources/buddy-creator/assets/widget-v2/app.js +0 -62
  138. package/resources/buddy-creator/assets/widget-v2/style.css +0 -1
  139. package/resources/buddy-creator/assets/widget-v3/app.js +0 -65
  140. package/resources/buddy-creator/assets/widget-v3/style.css +0 -103
  141. package/resources/buddy-creator/assets/widget-v4/app.js +0 -66
  142. package/resources/buddy-creator/assets/widget-v4/style.css +0 -187
  143. package/resources/buddy-creator/references/artifact-schema.md +0 -70
  144. package/resources/buddy-creator/references/catalog.json +0 -401
  145. package/resources/buddy-creator/references/definition.md +0 -24
  146. package/resources/buddy-creator/references/dialogue-examples.md +0 -133
  147. package/resources/buddy-creator/references/host-guide.md +0 -219
  148. package/resources/buddy-creator/references/interview.md +0 -235
  149. package/resources/buddy-creator/references/knowledge.md +0 -40
  150. package/resources/buddy-creator/references/methods.md +0 -77
  151. package/resources/buddy-creator/references/opening.md +0 -14
  152. package/resources/buddy-creator/references/preview-panel.md +0 -66
  153. package/resources/buddy-creator/references/recovery.md +0 -46
  154. package/resources/buddy-creator/references/service.md +0 -76
  155. package/resources/buddy-creator/references/widgets-protocol.md +0 -83
  156. package/resources/buddy-creator/references/widgets.md +0 -99
  157. package/resources/buddy-creator/scripts/buddy.py +0 -108
  158. package/resources/buddy-creator/scripts/buddy_client.py +0 -67
  159. package/resources/buddy-creator/scripts/buddy_core.py +0 -865
  160. package/resources/buddy-creator/scripts/completion.py +0 -296
  161. package/resources/buddy-creator/scripts/interview.py +0 -215
  162. package/resources/buddy-creator/scripts/preview.py +0 -617
  163. package/resources/buddy-creator/scripts/preview_panel.py +0 -80
  164. package/resources/buddy-creator/scripts/sources.py +0 -239
  165. package/resources/buddy-creator/scripts/widget_render.py +0 -66
  166. package/resources/buddy-creator/scripts/widget_render_v1.py +0 -176
  167. package/resources/buddy-creator/scripts/widget_render_v2.py +0 -98
  168. package/resources/buddy-creator/scripts/widget_render_v3.py +0 -86
  169. package/resources/buddy-creator/scripts/widget_render_v4.py +0 -121
  170. package/resources/buddy-creator/scripts/widgets.py +0 -227
  171. package/resources/buddy-creator/version.json +0 -8
  172. package/resources/buddy-creator.manifest.json +0 -791
  173. package/resources/dada-object-avatar/SKILL.md +0 -37
  174. package/resources/dada-object-avatar/agents/openai.yaml +0 -4
  175. package/resources/dada-object-avatar/assets/examples/cat.png +0 -0
  176. package/resources/dada-object-avatar/assets/examples/cooking.png +0 -0
  177. package/resources/dada-object-avatar/assets/examples/divination.png +0 -0
  178. package/resources/dada-object-avatar/assets/examples/doctor.png +0 -0
  179. package/resources/dada-object-avatar/assets/examples/relationship.png +0 -0
  180. package/resources/dada-object-avatar/assets/examples/work.png +0 -0
  181. package/resources/dada-object-avatar/assets/preview.png +0 -0
  182. package/resources/dada-object-avatar/references/evaluation-rubric.md +0 -22
  183. package/resources/dada-object-avatar/references/examples.json +0 -8
  184. package/resources/dada-object-avatar/references/prompt-template.md +0 -27
  185. package/resources/dada-object-avatar/references/style-system.md +0 -54
  186. package/resources/dada-object-avatar/references/subject-design.md +0 -23
  187. package/resources/dada-object-avatar/references/training-guide.md +0 -14
  188. package/resources/dada-object-avatar/references/training-log.jsonl +0 -0
  189. package/resources/dada-object-avatar/references/training-record.schema.json +0 -20
  190. package/resources/dada-object-avatar/scripts/choose_gaze.py +0 -36
  191. package/resources/dada-object-avatar/scripts/record_example.py +0 -22
  192. package/resources/dada-object-avatar/scripts/validate_png.py +0 -52
  193. package/resources/dada-object-avatar.manifest.json +0 -85
package/dist/web/app.css CHANGED
@@ -326,7 +326,8 @@
326
326
  .empty-state p { max-width: 30rem; margin: 0; color: var(--color-ink-soft); font-size: var(--text-sm); line-height: 1.65; }
327
327
 
328
328
  .waterfall { display: grid; gap: var(--space-md); margin-block-start: var(--space-xl); padding-block-end: var(--space-lg); border-block-end: var(--rule-hair) solid var(--color-rule); }
329
- .waterfall-row { display: grid; grid-template-columns: 6rem minmax(5rem, 1fr) 4.5rem; align-items: center; gap: var(--space-md); font-size: var(--text-xs); }
329
+ .timing-heading { display:flex; justify-content:space-between; flex-wrap:wrap; gap:6px 16px; color:var(--color-muted); font-size:11px; line-height:1.7; }
330
+ .waterfall-row { display: grid; grid-template-columns: 6rem minmax(2rem, 1fr) 8.5rem; align-items: center; gap: var(--space-md); font-size: var(--text-xs); }
330
331
  .waterfall-track { height: 0.375rem; overflow: hidden; border-radius: var(--radius-pill); background: var(--color-paper-soft); }
331
332
  .waterfall-fill { display: block; height: 100%; min-width: var(--space-2xs); transform-origin: left; border-radius: inherit; background: var(--color-ink); }
332
333
  .waterfall-row strong { font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 500; text-align: end; }
@@ -553,20 +554,21 @@ body[data-hosted-preview] .hosted-page-heading p { display:flex; align-items:cen
553
554
  body[data-hosted-preview] .hosted-page-heading p span:first-child { overflow:hidden; white-space:nowrap; text-overflow:ellipsis; max-width:60%; }
554
555
  body[data-hosted-preview] .hosted-page-heading h1 { margin:0; font-size:26px; line-height:1.3; letter-spacing:-.035em; font-weight:650; }
555
556
  body[data-hosted-preview] .local-connection { border-top:1px solid var(--color-rule); }
556
- body[data-hosted-preview] .debugger > .sidebar { flex:none; position:static; width:auto; padding:0 20px; border:0; border-bottom:1px solid var(--color-rule); }
557
+ /* Hosted tabs are normal flex content, not the standalone sticky sidebar. */
558
+ body[data-hosted-preview] .debugger > .sidebar { flex:none; position:static; z-index:auto; width:auto; padding:0 20px; border:0; border-bottom:1px solid var(--color-rule); }
557
559
  body[data-hosted-preview] .sidebar-section { margin:0; }
558
560
  body[data-hosted-preview] .debug-tabs { display:flex; flex-wrap:nowrap; gap:20px; margin:0; }
559
561
  body[data-hosted-preview] .debug-tabs button { width:auto; min-height:44px; padding:10px 0; font-size:13px; gap:7px; border-radius:0; white-space:nowrap; }
560
562
  body[data-hosted-preview] .debug-tabs button svg { width:15px; height:15px; }
561
563
  body[data-hosted-preview] .debug-tabs button[aria-selected="true"] { background:transparent; box-shadow:inset 0 -2px var(--color-accent); }
562
564
  body[data-hosted-preview] .run-strip { flex:none; padding:18px 20px 14px; border-bottom:1px solid var(--color-rule); }
563
- body[data-hosted-preview] .run-heading { align-items:center; flex-wrap:nowrap; gap:16px; }
565
+ body[data-hosted-preview] .run-heading { align-items:center; flex-wrap:wrap; gap:16px; }
564
566
  body[data-hosted-preview] .run-identity { flex:1; }
565
567
  body[data-hosted-preview] .run-identity > .eyebrow { display:none; }
566
568
  body[data-hosted-preview] .run-title-line { margin:0; flex-wrap:nowrap; gap:8px; }
567
569
  body[data-hosted-preview] .run-title-line h2 { font-size:17px; letter-spacing:0; }
568
570
  body[data-hosted-preview] .status-pill { font-size:11px; padding:3px 7px; }
569
- body[data-hosted-preview] .run-controls { flex:0 1 270px; gap:6px; }
571
+ body[data-hosted-preview] .run-controls { flex:0 1 340px; min-width:min(100%,340px); max-width:100%; gap:6px; }
570
572
  body[data-hosted-preview] .run-picker-label { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); }
571
573
  body[data-hosted-preview] :is(.run-picker,.run-step) { min-height:30px; border-radius:6px; font-size:11px; padding-block:4px; }
572
574
  body[data-hosted-preview] .run-step { width:30px; }
@@ -594,7 +596,7 @@ body[data-hosted-preview] .run-summary-copy { margin:16px 0 0; font-size:13px; l
594
596
  .run-progress li[data-stage="active"] { border-color:var(--color-accent); color:var(--color-accent); font-weight:600; }
595
597
  .run-progress li[data-stage="error"] { border-color:var(--color-error); color:var(--color-error); }
596
598
  body[data-hosted-preview] .waterfall { margin:0; padding:0; border:0; gap:12px; }
597
- body[data-hosted-preview] .waterfall-row { grid-template-columns:70px minmax(0,1fr) 64px; gap:16px; font-size:12px; }
599
+ body[data-hosted-preview] .waterfall-row { grid-template-columns:84px minmax(0,1fr) 132px; gap:12px; font-size:12px; }
598
600
  body[data-hosted-preview] .waterfall-track { height:3px; background:transparent; }
599
601
  body[data-hosted-preview] .waterfall-fill { min-width:0; background:var(--color-accent); opacity:.45; }
600
602
  body[data-hosted-preview] .workbench-state { flex:none; padding:26px 24px; border-bottom:1px solid var(--color-rule); }
@@ -602,7 +604,7 @@ body[data-hosted-preview] .workbench-state h2 { font-size:18px; font-weight:600;
602
604
  body[data-hosted-preview] .workbench-state p { max-width:480px; color:var(--color-muted); font-size:13px; line-height:1.8; margin:0; }
603
605
  body[data-hosted-preview] .state-mark { display:block; width:32px; height:3px; margin-bottom:20px; background:var(--color-accent); border-radius:2px; }
604
606
  body[data-hosted-preview][data-has-runs="false"] .run-strip { display:none; }
605
- body[data-hosted-preview][data-has-runs="false"] #panel-overview { display:none; }
607
+
606
608
  body[data-hosted-preview][data-dev-phase="disconnected"][data-has-runs="false"] .workbench-state { padding-top:56px; padding-bottom:56px; }
607
609
  body[data-hosted-preview][data-dev-phase="disconnected"] .topbar-actions { display:none; }
608
610
  body[data-hosted-preview][data-dev-phase="disconnected"] :is(.run-progress li,.replying-indicator i) { animation-play-state:paused; }
@@ -660,3 +662,60 @@ body[data-hosted-preview] .pairing-panel { top:80px; }
660
662
  body[data-hosted-preview] .run-controls { flex-basis:100%; }
661
663
  body[data-hosted-preview] .simulator-area { padding-inline:12px; }
662
664
  }
665
+
666
+ /* Runtime diagnostics share the existing Preview typography and semantic colors. */
667
+ .diagnostic-source { flex:none; color:var(--color-muted); font-size:11px; }
668
+ .diagnostic-filters { display:flex; flex-wrap:wrap; gap:12px; margin:20px 0 12px; }
669
+ .diagnostic-filters label { display:flex; flex:1 1 130px; min-width:0; flex-direction:column; gap:6px; color:var(--color-ink-soft); font-size:11px; }
670
+ .diagnostic-filters .diagnostic-search { flex:1.5 1 160px; }
671
+ .diagnostic-filters :is(select,input) { width:100%; min-width:0; min-height:36px; padding:7px 10px; border:1px solid var(--color-rule-strong); border-radius:6px; background:var(--color-paper-raised); color:var(--color-ink); font:inherit; font-size:12px; }
672
+ .diagnostic-filters :is(select,input):focus-visible { outline:2px solid var(--color-accent); outline-offset:2px; }
673
+ .diagnostic-note { color:var(--color-muted); font-size:11px; line-height:1.8; margin:12px 0 16px; }
674
+ .diagnostic-entry { border-bottom:1px solid var(--color-rule); min-width:0; }
675
+ .diagnostic-entry > summary { display:flex; align-items:start; gap:10px; padding:16px 0; cursor:pointer; list-style:none; }
676
+ .diagnostic-entry > summary::-webkit-details-marker { display:none; }
677
+ .diagnostic-entry > summary::after { content:'›'; color:var(--color-muted); font-size:18px; line-height:20px; flex:none; }
678
+ .diagnostic-entry[open] > summary::after { transform:rotate(90deg); }
679
+ .diagnostic-entry > summary:hover .diagnostic-heading strong { color:var(--color-accent); }
680
+ .diagnostic-entry > summary:focus-visible { outline:2px solid var(--color-accent); outline-offset:3px; border-radius:3px; }
681
+ .diagnostic-heading { display:flex; flex-direction:column; gap:5px; min-width:0; flex:1; }
682
+ .diagnostic-heading strong { font-size:13px; font-weight:550; line-height:1.5; overflow-wrap:anywhere; }
683
+ .diagnostic-preview { color:var(--color-muted); font-size:12px; line-height:1.6; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
684
+ .diagnostic-badge { flex:none; color:var(--color-ink-soft); background:var(--color-paper-soft); border-radius:4px; padding:3px 5px; min-width:36px; font:10px/1.4 var(--font-mono); text-align:center; }
685
+ .diagnostic-badge[data-kind="response"] { color:var(--color-accent); background:var(--color-accent-soft, var(--color-paper-soft)); }
686
+ .diagnostic-entry[data-level="error"] > summary .diagnostic-badge { color:var(--color-error); background:var(--color-error-soft); }
687
+ .diagnostic-entry[data-level="warn"] > summary .diagnostic-badge { color:var(--color-warning); background:var(--color-warning-soft); }
688
+ .diagnostic-entry time { color:var(--color-muted); font:11px/1.8 var(--font-mono); white-space:nowrap; }
689
+ .diagnostic-body { padding:0 0 18px; }
690
+ .diagnostic-meta { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:4px 12px; color:var(--color-muted); font-size:11px; margin:0 0 12px; }
691
+ .diagnostic-meta > span { overflow-wrap:anywhere; min-width:0; }
692
+ .diagnostic-meta button { font-size:11px; }
693
+ .diagnostic-section { border:1px solid var(--color-rule); border-radius:6px; margin:8px 0 0; overflow:hidden; }
694
+ .diagnostic-section > summary { cursor:pointer; padding:10px 12px; font-size:12px; color:var(--color-ink-soft); }
695
+ .diagnostic-section[open] > summary { border-bottom:1px solid var(--color-rule); }
696
+ .diagnostic-section pre { margin:0; padding:14px; max-height:420px; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere; font:11px/1.85 var(--font-mono); background:var(--color-paper-soft); tab-size:2; }
697
+ .diagnostic-metrics { display:flex; flex-wrap:wrap; gap:16px 24px; margin:18px 0; }
698
+ .diagnostic-metrics div { min-width:70px; }
699
+ .diagnostic-metrics dt { color:var(--color-muted); font-size:10px; margin-bottom:6px; }
700
+ .diagnostic-metrics dd { margin:0; font:12px var(--font-mono); color:var(--color-ink); }
701
+ .metric-help { max-width:220px; margin:6px 0 0; color:var(--color-muted); font:11px/1.7 var(--font-body); white-space:normal; overflow-wrap:anywhere; }
702
+ .run-metrics dd:has(.metric-help) { overflow:visible; white-space:normal; }
703
+ .diagnostic-warning { color:var(--color-error); background:var(--color-error-soft); padding:10px 12px; font-size:12px; line-height:1.8; border-radius:6px; overflow-wrap:anywhere; }
704
+ .diagnostic-empty { padding:44px 0 52px; }
705
+ .diagnostic-empty strong { font-size:15px; font-weight:550; }
706
+ .diagnostic-empty p { max-width:420px; color:var(--color-muted); font-size:12px; line-height:1.9; }
707
+ .diagnostic-explanation { margin-top:24px; color:var(--color-muted); font-size:11px; line-height:1.9; }
708
+ .diagnostic-explanation summary { cursor:pointer; }
709
+ .diagnostic-explanation p { margin:10px 0; }
710
+ .diagnostic-history { margin-top:24px; padding-top:16px; border-top:1px solid var(--color-rule); }
711
+ .diagnostic-history > summary { cursor:pointer; font-size:13px; color:var(--color-ink-soft); }
712
+ .diagnostic-history #context-source { font-size:10px; margin-left:8px; color:var(--color-muted); }
713
+ #panel-overview .run-strip, body[data-hosted-preview] #panel-overview .run-strip { padding:0 0 16px; }
714
+ #panel-protocol .protocol-log { display:block; border:0; }
715
+ @media (max-width:650px) {
716
+ .diagnostic-filters { gap:10px; }
717
+ .diagnostic-filters .diagnostic-search { flex-basis:100%; }
718
+ .diagnostic-entry > summary { gap:7px; }
719
+ .diagnostic-entry time { font-size:10px; }
720
+ .diagnostic-source { display:none; }
721
+ }
package/dist/web/app.js CHANGED
@@ -6,6 +6,7 @@
6
6
  const connectionUrl = hosted ? new URL(`../api/buddies/${encodeURIComponent(hostedBuddyId)}/preview`, location.href) : undefined;
7
7
  let connectionInstance = "", connectionReady = !hosted, connectionBusy = false, devPollBusy = false;
8
8
  let connectionChecked = false, connectionFailure = "", devAction = "";
9
+ let eventSource, eventSourceInstance = "", liveEventsActive = false, pollingInterval = 0;
9
10
  function previewApiUrl(path) {
10
11
  if (!hosted) return path;
11
12
  const url = new URL(connectionUrl.href + path);
@@ -190,6 +191,8 @@
190
191
  idle: hosted ? "已连接但 DEV 未启动" : "未启动",
191
192
  disconnected: "未连接本地工程",
192
193
  starting: "正在启动",
194
+ reloading: "正在重载",
195
+ reload_failed: "重载失败 · 等待修正",
193
196
  ready: "DEV 运行中",
194
197
  stopping: "正在停止",
195
198
  failed: "启动失败",
@@ -200,6 +203,7 @@
200
203
  if (!next || !Number.isFinite(next.revision)) return false;
201
204
  if (Number.isFinite(state.dev?.revision) && next.revision < state.dev.revision) return false;
202
205
  if (next.phase === "ready" && state.dev.phase !== "ready") state.simulatorLoaded = false;
206
+ if (state.dev.phase === "reload_failed" && ["ready", "reloading"].includes(next.phase)) elements.errorBanner.hidden = true;
203
207
  state.dev = next;
204
208
  if (next.ready?.buddy) state.buddy = next.ready.buddy;
205
209
  return true;
@@ -253,8 +257,8 @@
253
257
  elements.connectionState.dataset.phase = phase;
254
258
  elements.connectionLabel.textContent = phase === "disconnected" ? "未连接本地工程" : phaseCopy(phase);
255
259
  elements.devControl.disabled = !connectionReady || Boolean(devAction) || phase === "starting" || phase === "stopping";
256
- elements.devControl.dataset.action = phase === "ready" ? "stop" : "start";
257
- elements.devControl.textContent = devAction === "start" ? "正在启动…" : devAction === "stop" ? "正在停止…" : phase === "failed" ? "重试启动" : phase === "ready" ? "停止 DEV" : phase === "starting" ? "正在启动…" : phase === "stopping" ? "正在停止…" : "启动 DEV";
260
+ elements.devControl.dataset.action = ["ready", "reloading", "reload_failed"].includes(phase) ? "stop" : "start";
261
+ elements.devControl.textContent = devAction === "start" ? "正在启动…" : devAction === "stop" ? "正在停止…" : phase === "failed" ? "重试启动" : ["ready", "reloading", "reload_failed"].includes(phase) ? "停止 DEV" : phase === "starting" ? "正在启动…" : phase === "stopping" ? "正在停止…" : "启动 DEV";
258
262
  elements.environmentAgent.textContent = phase === "ready"
259
263
  ? `${state.dev.ready?.agentId || "已就绪"} · CLI 托管`
260
264
  : phaseCopy(phase);
@@ -271,12 +275,12 @@
271
275
  elements.resetConversation.disabled = phase !== "ready" || Boolean(active) || state.resetting || state.sending || state.resetUnavailable;
272
276
  elements.resetConversation.title = state.resetUnavailable ? "当前平台暂不支持重开会话" : active ? "请等待当前回复完成后再清空" : "结束当前开发版对话,开始一段全新会话";
273
277
  elements.devControl.disabled ||= state.resetting;
274
- elements.messageInput.disabled = phase !== "ready" || !state.simulatorLoaded || Boolean(active) || state.resetting || state.sending;
275
- elements.sendMessage.hidden = Boolean(active);
278
+ elements.messageInput.disabled = phase !== "ready" || !state.simulatorLoaded || state.resetting || state.sending;
279
+ elements.sendMessage.hidden = false;
276
280
  updateSendButton();
277
281
  syncPolling();
278
282
  if (
279
- phase === "failed"
283
+ (phase === "failed" || phase === "reload_failed")
280
284
  && state.dev.error?.message
281
285
  && state.reportedFailureRevision !== state.dev.revision
282
286
  ) {
@@ -300,17 +304,19 @@
300
304
  const copy = !connectionChecked ? ["正在检查连接", "读取本地工程的连接状态。"]
301
305
  : phase === "disconnected" ? [connectionInstance ? "本地连接已断开" : "连接本地工程", connectionFailure || (connectionInstance ? "正在等待重新连接。保留当前记录,连接恢复后继续调试。" : "点击上方「连接工程」,复制指令到终端运行。")]
302
306
  : phase === "starting" ? ["正在启动调试", "正在准备运行环境,启动完成后即可发送消息。"]
307
+ : phase === "reloading" ? ["正在重载", "本地文件已修改,正在重新加载;当前回复可能中断。"]
308
+ : phase === "reload_failed" ? ["重载失败", state.dev.error?.message || "修正文件并保存后自动重试,也可停止 DEV。"]
303
309
  : phase === "stopping" ? ["正在停止调试", "等待运行环境退出,当前记录会保留。"]
304
310
  : phase === "failed" ? ["调试未能启动", "查看上方错误信息,修复工程后点击「重试启动」。"]
305
311
  : phase === "idle" ? ["工程已连接", "点击上方「启动 DEV」,开始体验搭子。"]
306
312
  : !state.simulatorLoaded ? ["正在读取对话", "同步当前会话,完成后即可发送消息。"]
307
- : state.sending ? ["正在发送消息", "提交成功后,这里会显示本次执行过程。"]
308
- : active ? ["等待执行记录", "消息已提交,正在同步本次执行过程。"]
309
- : ["开始一轮调试", history ? "右侧保留了历史对话。本地执行记录从本次连接开始,发一条新消息即可查看。" : "在右侧发一条消息,观察搭子的回复与执行过程。"];
310
- guide.hidden = phase === "ready" && runs.length > 0;
313
+ : state.sending ? ["正在发送消息", "提交成功后,这里会显示消息与模型日志。"]
314
+ : active ? ["等待执行记录", "消息已提交,正在同步消息与模型日志。"]
315
+ : ["开始一轮调试", history ? "右侧保留了历史对话。本地调试记录从本次连接开始,发一条新消息即可查看。" : "在右侧发一条消息,观察搭子的回复与模型日志。"];
316
+ guide.hidden = phase === "ready" && (runs.length > 0 || (state.debug.diagnostics?.records || []).some(record => record.kind !== "log"));
311
317
  $("#workbench-state-title").textContent = copy[0];
312
318
  $("#workbench-state-copy").textContent = copy[1];
313
- $("#connection-description").textContent = !connectionChecked ? "检查中" : phase === "disconnected" ? "等待连接" : "仅用于本地调试";
319
+ $("#connection-description").textContent = !connectionChecked ? "检查中" : phase === "disconnected" ? "等待连接" : "保存自动重载,可能中断回复;Widget 数据不自动迁移。";
314
320
  const connectButton = $("#get-connect-command");
315
321
  connectButton.className = `button ${connectionReady ? "button-secondary" : "button-primary"}`;
316
322
  connectButton.textContent = connectionReady ? "连接指令" : connectionInstance ? "重新连接" : "连接工程";
@@ -321,7 +327,7 @@
321
327
  if (!state.project) elements.buddyName.textContent = new URL(location.href).searchParams.get("buddyName") || "我的搭子";
322
328
  const empty = elements.chatMessages.querySelector(".chat-empty");
323
329
  if (empty) empty.textContent = phase === "ready" ? "发一条消息,开始体验" : phase === "disconnected" ? "连接后,在这里体验搭子的回复" : "启动 DEV 后,开始对话";
324
- elements.messageInput.placeholder = active ? "等待当前回复完成" : phase === "ready" ? "给搭子发一条消息" : "等待调试就绪";
330
+ elements.messageInput.placeholder = active ? "可以继续补充消息" : phase === "ready" ? "给搭子发一条消息" : "等待调试就绪";
325
331
  }
326
332
 
327
333
  function runOrdinal(run, index, runs) {
@@ -332,7 +338,7 @@
332
338
 
333
339
  function runOptionLabel(run, index, runs) {
334
340
  const date = dayKey(run.startedAt) === dayKey(new Date()) ? "" : `${dayLabel(run.startedAt) || ""} `;
335
- return `第 ${runOrdinal(run, index, runs)} 轮 · ${date}${formatTime(run.startedAt)} · ${runStatusCopy(run.status)}`;
341
+ return `第 ${runOrdinal(run, index, runs)} 轮 · ${date}${formatTime(run.startedAt)} · ${(run.completion === "no_reply" && run.status === "completed" ? "未观察到回复" : runStatusCopy(run.status))}`;
336
342
  }
337
343
 
338
344
  let runPickerSignature = "";
@@ -372,10 +378,10 @@
372
378
  elements.runId.title = run?.runId || "";
373
379
  elements.copyRunId.disabled = !run;
374
380
  elements.runStatus.dataset.status = run?.status || "idle";
375
- elements.runStatus.textContent = runStatusCopy(run?.status || "idle");
376
- elements.metricAccept.textContent = formatDuration(run?.acceptLatencyMs);
377
- elements.metricFirst.textContent = formatDuration(run?.firstTextLatencyMs);
378
- elements.metricResponse.textContent = formatDuration(run?.responseTimeMs);
381
+ elements.runStatus.textContent = run?.completion === "no_reply" && run.status === "completed" ? "未观察到回复" : runStatusCopy(run?.status || "idle");
382
+ elements.metricAccept.textContent = formatElapsedSeconds(run?.acceptLatencyMs);
383
+ elements.metricFirst.textContent = formatElapsedSeconds(run?.firstTextLatencyMs);
384
+ elements.metricResponse.textContent = run?.completion === "no_reply" ? "未观察到回复" : formatElapsedSeconds(replyEndLatency(run));
379
385
  elements.metricMessages.textContent = state.simulatorLoaded ? String(state.simulator.messages?.length || 0) : "读取中";
380
386
  renderOverview(run);
381
387
  renderTimeline(run);
@@ -405,6 +411,18 @@
405
411
  }
406
412
  }
407
413
 
414
+ function formatElapsedSeconds(value) {
415
+ return Number.isFinite(value) ? `${Number((value / 1000).toFixed(3))} 秒` : "未记录";
416
+ }
417
+
418
+ function replyEndLatency(run) {
419
+ if (run?.status !== "completed") return undefined;
420
+ // updatedAt records Preview observing the terminal state. responseTimeMs and
421
+ // completedAt use the platform message timestamp and are not reply-end timings.
422
+ const elapsed = Date.parse(run.updatedAt) - Date.parse(run.startedAt);
423
+ return Number.isFinite(elapsed) && elapsed >= 0 ? elapsed : undefined;
424
+ }
425
+
408
426
  function renderOverview(run) {
409
427
  elements.overviewEmpty.hidden = Boolean(run);
410
428
  elements.overviewContent.hidden = !run;
@@ -421,16 +439,19 @@
421
439
  ["消息已提交", "done"],
422
440
  ["等待回复", hasText || finished ? "done" : "active"],
423
441
  [finished && !hasText ? "未记录流式输出" : "生成回复", hasText ? (finished ? "done" : "active") : finished ? "skipped" : "waiting"],
424
- [run.status === "failed" ? "执行失败" : run.status === "cancelled" ? "已停止" : noReply ? "未产生回复" : "完成", finished ? (interrupted ? "error" : "done") : "waiting"],
442
+ [run.status === "failed" ? "执行失败" : run.status === "cancelled" ? "已停止" : noReply ? "未观察到回复" : "完成", finished ? (interrupted ? "error" : "done") : "waiting"],
425
443
  ];
426
444
  if (interrupted) stages.forEach(stage => { if (stage[1] === "active") stage[1] = "error"; });
427
445
  for (const [label, status] of stages) progress.append(node("li", { "data-stage": status }, label));
428
446
  }
429
447
  clear(elements.waterfall);
430
448
  // Only report observed timings. A finished run with missing telemetry never keeps counting.
431
- const timingRows = [["消息接收", run.acceptLatencyMs], ["首段文字", run.firstTextLatencyMs], ["总耗时", run.responseTimeMs]];
449
+ const timingRows = [["消息被接收", run.acceptLatencyMs], ["首字耗时", run.firstTextLatencyMs], ["回复结束", noReply ? undefined : replyEndLatency(run)]];
432
450
  const maximum = Math.max(1, ...timingRows.map(([,value]) => Number.isFinite(value) ? value : 0));
433
451
  elements.waterfall.hidden = !timingRows.some(([,value]) => Number.isFinite(value));
452
+ const timingHeading = node("div", { class: "timing-heading" });
453
+ timingHeading.append(node("span", {}, "起点:Preview 发起发送(0 秒)"), node("span", {}, "累计时间,不可相加"));
454
+ elements.waterfall.append(timingHeading);
434
455
  for (const [label, duration] of timingRows) {
435
456
  if (!Number.isFinite(duration)) continue;
436
457
  const row = node("div", { class: "waterfall-row" });
@@ -439,26 +460,29 @@
439
460
  const fill = node("span", { class: "waterfall-fill" });
440
461
  fill.style.transform = `scaleX(${duration / maximum})`;
441
462
  track.append(fill);
442
- row.append(track, node("strong", {}, formatDuration(duration)));
463
+ row.append(track, node("strong", {}, `发送后 ${formatElapsedSeconds(duration)}`));
443
464
  elements.waterfall.append(row);
444
465
  }
445
466
  elements.runSummaryCopy.textContent = run.status === "failed"
446
- ? "本轮执行失败。查看执行时间线定位问题,或展开协议日志中的错误详情。"
467
+ ? "本轮执行失败。请查看运行日志中的错误详情。"
447
468
  : run.status === "cancelled" ? "本轮已停止。已完成的平台写入不会自动撤销。"
448
- : noReply ? "本轮已结束,没有产生新的完整回复。"
449
- : run.status === "completed" ? (measuredFirstText ? `回复已完成,首段文字耗时 ${formatDuration(run.firstTextLatencyMs)}。` : "回复已完成,本轮未记录首段文字耗时。")
469
+ : noReply ? "回复状态已结束,未观察到新的可见回复;不据此判断执行是否成功。"
470
+ : run.status === "completed" ? (measuredFirstText ? "" : "本轮未记录首字耗时。")
450
471
  : run.status === "cancel_requested" ? "正在停止,结束后可以继续发送消息。"
451
472
  : hasText ? "正在生成回复,右侧同步展示输出。" : "消息已提交,等待搭子开始回复。";
473
+ elements.runSummaryCopy.hidden = !elements.runSummaryCopy.textContent;
452
474
  }
453
475
 
454
476
  let timelineSignature = "", contextSignature = "", protocolSignature = "";
455
477
  const expandedLogs = new Map();
478
+ const diagnostics = window.createPreviewDiagnostics?.({ node, formatTime, formatDuration, toast, showError });
479
+ diagnostics?.update(state.debug);
456
480
  function renderTimeline(run) {
457
481
  const signature = JSON.stringify([run?.runId, state.debug.events]);
458
482
  if (signature === timelineSignature) return;
459
483
  timelineSignature = signature;
460
484
  clear(elements.timelineList);
461
- const events = (state.debug.events || []).filter((event) => !run || event.runId === run.runId);
485
+ const events = (state.debug.events || []).filter((event) => event.type !== "run.output.delta" && (!run || event.runId === run.runId));
462
486
  if (!events.length) {
463
487
  elements.timelineList.append(node("li", { class: "timeline-empty" }, run ? "这个本地回合暂无可见事件。" : "发一条消息后,执行事件会出现在这里。"));
464
488
  return;
@@ -496,6 +520,7 @@
496
520
  }
497
521
 
498
522
  function renderProtocol() {
523
+ if (diagnostics) { diagnostics.update(state.debug); return; }
499
524
  const signature = JSON.stringify(state.debug.events);
500
525
  if (signature === protocolSignature) return;
501
526
  protocolSignature = signature;
@@ -536,19 +561,26 @@
536
561
  }
537
562
 
538
563
  const widgets = window.createSimulatorWidgets?.({ api, avatar: () => messageAvatar("assistant") });
539
- function renderWidgets() { widgets?.update({ active: state.dev.phase === "ready" && !state.resetting, conversationRevision: state.simulator.conversationRevision, devRevision: state.dev.revision }); }
564
+ function renderWidgets() { widgets?.update({ reloading: ["reloading", "reload_failed"].includes(state.dev.phase), active: state.dev.phase === "ready" && !state.resetting, conversationRevision: state.simulator.conversationRevision, devRevision: state.dev.revision }); }
540
565
 
541
566
  let messageSignature = "";
542
567
  let followMessages = true;
543
- let liveReply, liveReplyText = "";
568
+ let liveReply, liveReplyBubble, liveReplyText = "";
544
569
  function renderLiveReply() {
545
570
  if (!liveReply) return;
546
571
  const text = state.simulator.streaming?.runId === state.simulator.activeRun?.runId ? state.simulator.streaming?.text || "" : "";
547
572
  if (text === liveReplyText) return;
548
573
  liveReplyText = text;
549
- clear(liveReply);
550
- if (text) { liveReply.className = "message-content"; liveReply.append(node("div", { class: "message-bubble", "aria-label": "搭子正在回复" }, text)); }
574
+ if (text) {
575
+ if (!liveReplyBubble) {
576
+ clear(liveReply);
577
+ liveReplyBubble = node("div", { class: "message-bubble", "aria-label": "搭子正在回复" });
578
+ liveReplyBubble.append(document.createTextNode(text));
579
+ liveReply.append(liveReplyBubble);
580
+ } else liveReplyBubble.firstChild.data = text;
581
+ }
551
582
  else {
583
+ clear(liveReply); liveReplyBubble = undefined;
552
584
  const indicator = node("div", { class: "replying-indicator", "aria-label": "搭子正在准备回复" });
553
585
  indicator.append(node("i"), node("i"), node("i")); liveReply.append(indicator);
554
586
  }
@@ -568,7 +600,7 @@
568
600
  messageSignature = signature;
569
601
  const previousTop = elements.chatMessages.scrollTop;
570
602
  clear(elements.chatMessages);
571
- liveReply = undefined; liveReplyText = "";
603
+ liveReply = undefined; liveReplyBubble = undefined; liveReplyText = "";
572
604
  const messages = state.simulator.messages || [];
573
605
  if (!messages.length && !state.simulator.activeRun) {
574
606
  elements.chatMessages.append(node("div", { class: "chat-empty", "aria-label": "暂无消息" }, hosted ? (state.dev.phase === "ready" ? "发一条消息,开始体验" : "启动 DEV 后,开始对话") : ""));
@@ -645,9 +677,14 @@
645
677
  }
646
678
 
647
679
  function syncPolling() {
680
+ const interval = liveEventsActive ? 30000 : 1200;
681
+ if (state.polling !== undefined && pollingInterval !== interval) {
682
+ window.clearInterval(state.polling); state.polling = undefined;
683
+ }
648
684
  if (connectionReady && state.dev.phase === "ready" && state.polling === undefined) {
649
685
  void refreshSimulator();
650
- state.polling = window.setInterval(() => void refreshSimulator(), 1200);
686
+ pollingInterval = interval;
687
+ state.polling = window.setInterval(() => void refreshSimulator(), interval);
651
688
  } else if ((!connectionReady || state.dev.phase !== "ready") && state.polling !== undefined) {
652
689
  window.clearInterval(state.polling);
653
690
  state.polling = undefined;
@@ -657,7 +694,7 @@
657
694
  async function controlDev() {
658
695
  if (!connectionReady || state.dev.phase === "starting" || state.dev.phase === "stopping") return;
659
696
  if (devAction) return;
660
- devAction = state.dev.phase === "ready" ? "stop" : "start";
697
+ devAction = ["ready", "reloading", "reload_failed"].includes(state.dev.phase) ? "stop" : "start";
661
698
  elements.errorBanner.hidden = true;
662
699
  renderDev();
663
700
  try {
@@ -675,7 +712,7 @@
675
712
  }
676
713
 
677
714
  function updateSendButton() {
678
- const ready = connectionReady && !devAction && state.simulatorLoaded && state.dev.phase === "ready" && !state.simulator.activeRun && !state.resetting && !state.sending;
715
+ const ready = connectionReady && !devAction && state.simulatorLoaded && state.dev.phase === "ready" && !state.resetting && !state.sending;
679
716
  elements.sendMessage.disabled = !ready || !elements.messageInput.value.trim();
680
717
  }
681
718
 
@@ -684,7 +721,7 @@
684
721
  async function sendMessage(event) {
685
722
  event.preventDefault();
686
723
  const text = elements.messageInput.value;
687
- if (!text.trim() || !state.simulatorLoaded || devAction || state.dev.phase !== "ready" || state.simulator.activeRun || state.resetting || state.sending) return;
724
+ if (!text.trim() || !state.simulatorLoaded || devAction || state.dev.phase !== "ready" || state.resetting || state.sending) return;
688
725
  state.sending = true;
689
726
  elements.errorBanner.hidden = true;
690
727
  followLatestMessages();
@@ -706,10 +743,10 @@
706
743
  pendingSend = undefined;
707
744
  elements.messageInput.value = "";
708
745
  resizeComposer();
709
- state.simulator.activeRun = { runId: accepted.runId, status: "pending" };
746
+ if (state.simulator.latestRun?.runId !== accepted.runId) state.simulator.activeRun = { runId: accepted.runId, status: "pending" };
710
747
  renderDev();
711
748
  renderMessages();
712
- await refreshSimulator();
749
+ void refreshSimulator();
713
750
  } catch (cause) {
714
751
  showError(cause);
715
752
  } finally {
@@ -774,6 +811,7 @@
774
811
  }
775
812
 
776
813
  async function copyProtocol() {
814
+ if (diagnostics) return diagnostics.copyLogs();
777
815
  const text = (state.debug.events || []).map((event) => [
778
816
  event.timestamp,
779
817
  event.channel,
@@ -792,6 +830,8 @@
792
830
 
793
831
  function applySimulatorState(next) {
794
832
  if ((next.conversationRevision || 0) < (state.simulator.conversationRevision || 0)) return false;
833
+ if (Number.isFinite(next.stateRevision) && Number.isFinite(state.simulator.stateRevision)
834
+ && next.stateRevision < state.simulator.stateRevision) return false;
795
835
  state.simulator = next;
796
836
  state.simulatorLoaded = true;
797
837
  return true;
@@ -831,8 +871,20 @@
831
871
  }
832
872
 
833
873
  function connectEvents() {
834
- const events = new EventSource("/api/events");
874
+ if (eventSource && eventSourceInstance === connectionInstance) return;
875
+ closeEvents();
876
+ const events = new EventSource(previewApiUrl("/api/events"));
877
+ eventSource = events; eventSourceInstance = connectionInstance;
878
+ events.addEventListener("open", () => {
879
+ if (eventSource !== events) return;
880
+ liveEventsActive = true; syncPolling();
881
+ });
882
+ events.addEventListener("error", () => {
883
+ if (eventSource !== events) return;
884
+ liveEventsActive = false; syncPolling();
885
+ });
835
886
  events.addEventListener("dev.state", (event) => {
887
+ if (eventSource !== events) return;
836
888
  try {
837
889
  if (applyDevState(JSON.parse(event.data))) { renderDev(); renderWidgets(); }
838
890
  } catch {
@@ -840,12 +892,15 @@
840
892
  }
841
893
  });
842
894
  events.addEventListener("simulator.state", (event) => {
895
+ if (eventSource !== events || state.resetting) return;
843
896
  try {
844
897
  const value = JSON.parse(event.data);
898
+ const before = JSON.stringify([state.simulator.activeRun, Boolean(state.simulator.streaming?.text), state.simulatorLoaded]);
845
899
  if (!applySimulatorState(value.state)) return;
846
- state.debug = value.debug;
847
- renderDev();
848
- renderDebug();
900
+ if (value.debug) state.debug = value.debug;
901
+ const after = JSON.stringify([state.simulator.activeRun, Boolean(state.simulator.streaming?.text), state.simulatorLoaded]);
902
+ if (before !== after) renderDev();
903
+ if (value.debug) renderDebug();
849
904
  renderMessages();
850
905
  } catch {
851
906
  // Polling remains the reconciliation path for a malformed or missed local event.
@@ -853,6 +908,11 @@
853
908
  });
854
909
  }
855
910
 
911
+ function closeEvents() {
912
+ eventSource?.close(); eventSource = undefined; eventSourceInstance = ""; liveEventsActive = false;
913
+ }
914
+ window.addEventListener("pagehide", closeEvents);
915
+
856
916
  async function boot() {
857
917
  if (hosted && !connectionReady) { void refreshConnection(); return; }
858
918
  try {
@@ -877,6 +937,7 @@
877
937
  }
878
938
 
879
939
  function disconnected(message = "未连接本地工程") {
940
+ closeEvents();
880
941
  connectionReady = false;
881
942
  state.dev = { phase: "idle", revision: -1 };
882
943
  state.simulatorRequest++;
@@ -898,6 +959,7 @@
898
959
  $("#copy-connect-command").disabled = false;
899
960
  if (!value.connected) { disconnected(); return; }
900
961
  const changed = connectionInstance !== value.instanceId;
962
+ if (changed) closeEvents();
901
963
  const newlyConnected = !connectionReady || changed;
902
964
  connectionInstance = value.instanceId;
903
965
  connectionReady = true;
@@ -918,6 +980,7 @@
918
980
  renderDev();
919
981
  await boot();
920
982
  }
983
+ if (value.liveEvents) connectEvents();
921
984
  } catch (cause) {
922
985
  const message = cause.message || "连接已中断,正在重试…";
923
986
  connectionChecked = true; connectionFailure = message;
@@ -932,7 +995,8 @@
932
995
  devPollBusy = true;
933
996
  try {
934
997
  const [dev, debug] = await Promise.all([api("/api/dev"), api("/api/debug")]);
935
- applyDevState(dev.state); state.debug = debug.debug; renderAll();
998
+ if (applyDevState(dev.state)) renderDev();
999
+ state.debug = debug.debug; renderDebug();
936
1000
  } catch (cause) {
937
1001
  // Polling conflicts are connection transitions, already explained by the workbench state.
938
1002
  if (!connectionReady || cause?.status === 409) void refreshConnection();