clay-server 5.3.0-beta.6 → 5.3.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 (111) hide show
  1. package/lib/project-admin-overview.js +22 -11
  2. package/lib/project-background-tasks.js +76 -0
  3. package/lib/project-connection.js +1 -1
  4. package/lib/project-sessions.js +12 -5
  5. package/lib/project-split-views.js +12 -3
  6. package/lib/project-view-store.js +7 -5
  7. package/lib/project-worker-conversation.js +5 -0
  8. package/lib/public/app.js +7 -0
  9. package/lib/public/css/contextual-clay.css +2 -1
  10. package/lib/public/css/mate-conversation.css +1 -1
  11. package/lib/public/css/pending-message-queue.css +24 -19
  12. package/lib/public/css/project-admin-overview.css +42 -13
  13. package/lib/public/css/shared-browser.css +31 -8
  14. package/lib/public/css/split-view-layout.css +92 -7
  15. package/lib/public/css/tool-pane.css +4 -7
  16. package/lib/public/index.html +5 -5
  17. package/lib/public/modules/agent-message-bubbles.js +96 -0
  18. package/lib/public/modules/app-header.js +6 -0
  19. package/lib/public/modules/app-messages.js +26 -9
  20. package/lib/public/modules/background-task-previews.js +8 -0
  21. package/lib/public/modules/background-task-viewer.js +97 -0
  22. package/lib/public/modules/background-tasks-ui.js +34 -125
  23. package/lib/public/modules/chat-bubble-renderer.js +11 -5
  24. package/lib/public/modules/contextual-clay-geometry.js +7 -3
  25. package/lib/public/modules/pane-activation.js +5 -3
  26. package/lib/public/modules/pane-header-tooltips.js +32 -0
  27. package/lib/public/modules/pending-message-queue-drag.js +69 -0
  28. package/lib/public/modules/pending-message-queue.js +3 -33
  29. package/lib/public/modules/permission-control.js +6 -5
  30. package/lib/public/modules/project-admin-overview.js +125 -10
  31. package/lib/public/modules/project-grouping.js +9 -0
  32. package/lib/public/modules/server-settings-controls.js +33 -4
  33. package/lib/public/modules/session-folder-toolbar.js +1 -1
  34. package/lib/public/modules/session-tool-item.js +86 -0
  35. package/lib/public/modules/settings-select.js +2 -1
  36. package/lib/public/modules/shared-browser-tabs.js +37 -21
  37. package/lib/public/modules/sidebar-projects.js +1 -10
  38. package/lib/public/modules/split-session-empty-pane.js +47 -0
  39. package/lib/public/modules/split-view-create-menu.js +74 -65
  40. package/lib/public/modules/split-view-layout-scope.js +4 -0
  41. package/lib/public/modules/split-view-picker-actions.js +71 -0
  42. package/lib/public/modules/split-view-picker-render.js +81 -0
  43. package/lib/public/modules/split-view-presentation-renderer.js +8 -8
  44. package/lib/public/modules/split-view-worker-activity.js +2 -2
  45. package/lib/public/modules/split-view-worker-transcript.js +70 -33
  46. package/lib/public/modules/split-view-workers.js +6 -4
  47. package/lib/public/modules/terminal-viewport.js +14 -0
  48. package/lib/public/modules/terminal.js +3 -4
  49. package/lib/public/modules/theme-palette.js +177 -0
  50. package/lib/public/modules/theme.js +1 -174
  51. package/lib/public/modules/tool-pane-chrome.js +7 -1
  52. package/lib/public/modules/tools.js +5 -89
  53. package/lib/public/modules/tooltip.js +32 -6
  54. package/lib/public/modules/wireframe-workbench.js +2 -2
  55. package/lib/public/modules/worker-message-previews.js +39 -0
  56. package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
  57. package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
  58. package/lib/public/pane-assets/{chunk-BMHTBRMO.js → chunk-7OVNC5HT.js} +1 -1
  59. package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
  60. package/lib/public/pane-assets/{chunk-2AIWCI6Y.js → chunk-EMLN2Y7L.js} +1 -1
  61. package/lib/public/pane-assets/{chunk-VKYBAVWT.js → chunk-ENPAHXLB.js} +1 -1
  62. package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
  63. package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
  64. package/lib/public/pane-assets/{chunk-6NYCNWVF.js → chunk-FQTNR4ZS.js} +1 -1
  65. package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
  66. package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
  67. package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
  68. package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
  69. package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
  70. package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
  71. package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
  72. package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
  73. package/lib/public/pane-assets/{chunk-PV4PCH2V.js → chunk-SJOT74VW.js} +1 -1
  74. package/lib/public/pane-assets/{chunk-LDIZLKB2.js → chunk-SJTGHIN5.js} +1 -1
  75. package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
  76. package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
  77. package/lib/public/pane-assets/{chunk-VNAV23CM.js → chunk-UPXU2IPC.js} +1 -1
  78. package/lib/public/pane-assets/{chunk-TMGSDFJI.js → chunk-VEBYD7TL.js} +1 -1
  79. package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
  80. package/lib/public/pane-assets/manifest.json +411 -399
  81. package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
  82. package/lib/sdk-message-processor.js +5 -2
  83. package/lib/server-storage.js +27 -3
  84. package/lib/server.js +4 -2
  85. package/lib/sessions.js +1 -1
  86. package/lib/storage-usage.js +29 -7
  87. package/lib/terminal-manager.js +2 -2
  88. package/lib/worker-message-previews.js +50 -0
  89. package/lib/ws-schema.js +1 -1
  90. package/lib/yoke/adapters/claude-worker.js +8 -0
  91. package/lib/yoke/adapters/claude.js +17 -2
  92. package/lib/yoke/claude-task-reader.js +94 -0
  93. package/lib/yoke/interface.js +1 -0
  94. package/package.json +1 -1
  95. package/lib/public/pane-assets/app-7UZ727NA.js +0 -1
  96. package/lib/public/pane-assets/chunk-6MIHIQ2W.js +0 -1
  97. package/lib/public/pane-assets/chunk-6UJ2ALOB.js +0 -1
  98. package/lib/public/pane-assets/chunk-6XK2OATZ.js +0 -1
  99. package/lib/public/pane-assets/chunk-BKIDR2ME.js +0 -1
  100. package/lib/public/pane-assets/chunk-GEFCFZEN.js +0 -1
  101. package/lib/public/pane-assets/chunk-GN2NQEVC.js +0 -2
  102. package/lib/public/pane-assets/chunk-IMLMBPT4.js +0 -166
  103. package/lib/public/pane-assets/chunk-K5S6AILM.js +0 -1
  104. package/lib/public/pane-assets/chunk-OWNNLZ36.js +0 -1
  105. package/lib/public/pane-assets/chunk-P2C3LOWL.js +0 -3
  106. package/lib/public/pane-assets/chunk-R4LMPTKU.js +0 -1
  107. package/lib/public/pane-assets/chunk-VWTHIQCE.js +0 -1
  108. package/lib/public/pane-assets/chunk-WM3K5AFH.js +0 -1
  109. package/lib/public/pane-assets/chunk-XOB3O327.js +0 -1
  110. package/lib/public/pane-assets/chunk-XOOUCFYT.js +0 -1
  111. package/lib/public/pane-assets/style-ZM7OOU3M.css +0 -1
@@ -405,13 +405,23 @@ body.split-view-resizing .split-pane-frame { pointer-events: none; }
405
405
  .split-view-worker-popover-header strong { flex: 1; min-width: 0; font-size: 12px; }
406
406
  .split-view-worker-popover-source { padding: 0 13px 10px; color: var(--text-dimmer); font-size: 10px; }
407
407
  .split-view-worker-popover-body { flex: 1; min-height: 0; overflow: auto; padding: 14px; }
408
- .split-view-worker-popover-body .message { display: flex; flex-direction: column; align-items: stretch; margin: 0 0 18px; padding: 0; max-width: none; }
409
- .split-view-worker-popover-body .author { display: block; margin-bottom: 4px; font-size: 11px; color: var(--text-muted); }
410
- .split-view-worker-popover-body .message-body { white-space: normal; overflow-wrap: anywhere; font-size: 12px; line-height: 1.65; }
411
- .split-view-worker-popover-body .message-body pre { white-space: pre-wrap; }
412
- .split-view-worker-popover-body .worker-tool-message pre { max-height: 280px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; }
413
- .split-view-worker-popover-body .worker-tool-message summary { cursor: pointer; font-size: 13px; }
414
- .split-view-worker-popover-body .worker-tool-message { padding: 6px 8px; border-radius: 5px; background: var(--bg-alt); }
408
+ /* Use session anatomy unchanged; only transcript typography is reduced. */
409
+ .readonly-session-transcript { --content-width: 100%; font-size: 12px; }
410
+ .readonly-session-transcript .md-content,
411
+ .readonly-session-transcript .msg-user .bubble,
412
+ body.wide-view .readonly-session-transcript .msg-user .bubble,
413
+ .readonly-session-transcript .dm-bubble-name,
414
+ .readonly-session-transcript .tool-name,
415
+ .readonly-session-transcript .tool-desc,
416
+ .readonly-session-transcript .tool-group-label { font-size: 12px; }
417
+ .readonly-session-transcript .dm-bubble-time,
418
+ .readonly-session-transcript .tool-subtitle,
419
+ .readonly-session-transcript .tool-subtitle-text,
420
+ .readonly-session-transcript .clay-tool-evidence-label { font-size: 9.6px; }
421
+ .readonly-session-transcript .tool-result-block,
422
+ .readonly-session-transcript .tool-result-block pre { font-size: 10.4px; }
423
+ .readonly-session-transcript .clay-tool-technical-name,
424
+ .readonly-session-transcript .msg-copy-hint { font-size: 8.8px; }
415
425
  .split-view-worker-popover-footer { padding: 8px 13px; border-top: 1px solid var(--border-subtle); color: var(--text-dimmer); font-size: 10px; }
416
426
  .split-view-worker-page-status { display: block; margin-top: 3px; line-height: 1.4; }
417
427
  .split-view-worker-popover-footer button { margin-left: 8px; padding: 4px 6px; font-size: 10px; }
@@ -496,3 +506,78 @@ body.is-dragging .split-pane-header, body.is-dragging .split-pane-drag-space { c
496
506
  box-shadow: inset 0 0 0 1px var(--accent-20);
497
507
  pointer-events: none;
498
508
  }
509
+
510
+ /* Native tasks use the same compact conversation affordance as Split Workers. */
511
+ .background-task-tabs { border-bottom: 1px solid var(--border-subtle); }
512
+ .background-task-tabs .split-view-worker-name { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
513
+ .background-task-viewer { top: 42px; right: 12px; width: min(640px, calc(100vw - 24px)); max-height: calc(100dvh - 58px); }
514
+ .background-task-viewer .split-view-worker-popover-body > pre { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; }
515
+ .background-task-viewer [hidden] { display: none; }
516
+ .background-task-ambient { opacity: .65; }
517
+
518
+ .agent-message-bubble {
519
+ position: fixed; z-index: 60; box-sizing: border-box; padding: 10px 12px;
520
+ border: 1px solid var(--border); border-radius: 9px; background: var(--bg);
521
+ color: var(--text); box-shadow: 0 5px 16px rgba(0, 0, 0, .16);
522
+ font-size: 12px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere;
523
+ pointer-events: none; max-height: 180px;
524
+ }
525
+ .agent-message-bubble::before {
526
+ content: ''; position: absolute; top: -5px; left: var(--bubble-tip-x, 20px); width: 8px; height: 8px;
527
+ background: var(--bg); border-left: 1px solid var(--border); border-top: 1px solid var(--border); transform: rotate(45deg);
528
+ }
529
+ .agent-message-bubble[data-live="true"] { animation: agent-message-fade 7s linear forwards; }
530
+ .agent-message-bubble[data-held="true"] { opacity: 1 !important; }
531
+ @keyframes agent-message-fade { from { opacity: 1; } to { opacity: 0; } }
532
+ @media (prefers-reduced-motion: reduce) { .agent-message-bubble[data-live="true"] { animation: none; } }
533
+
534
+ .agent-message-bubble-text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
535
+
536
+ /* Shared compact, anchored session insertion picker. */
537
+ .split-session-picker { padding: 5px; color: var(--text); }
538
+ .split-session-picker[data-stage="direction"] { width: min(212px, calc(100vw - 16px)); }
539
+ .split-session-picker[data-stage="sessions"] { width: min(360px, calc(100vw - 16px)); }
540
+ .split-session-directions { display: flex; flex-direction: column; gap: 2px; }
541
+ .split-session-directions button { display: flex; align-items: center; gap: 8px; height: 29px; padding: 4px 8px; text-align: left; font-size: 12px; }
542
+ .split-session-picker svg { width: 15px; height: 15px; flex-shrink: 0; }
543
+ .split-session-search { display: flex; align-items: center; gap: 7px; margin: 0 4px 5px; padding: 5px 7px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); }
544
+ .split-session-search:focus-within { outline: 2px solid var(--accent); outline-offset: -1px; }
545
+ .split-session-search input { width: 100%; min-width: 0; border: 0; padding: 0; background: transparent; color: var(--text); font: inherit; font-size: 13px; outline: 0; }
546
+ .split-session-section { padding: 7px 8px 4px; font-size: 10px; color: var(--text-muted); }
547
+ .split-session-results { max-height: min(260px, 35dvh); overflow-y: auto; }
548
+ .split-session-picker .split-session-result { display: flex; align-items: center; width: 100%; min-height: 34px; gap: 8px; padding: 6px 8px; font-size: 12px; text-align: left; }
549
+ .split-session-result img { width: 16px; height: 16px; border-radius: 4px; flex-shrink: 0; }
550
+ .split-session-result-title { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
551
+ .split-session-result small { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }
552
+ .split-session-move { display: block; overflow: hidden; text-overflow: ellipsis; }
553
+ .split-session-new { margin-top: 5px; border-top: 1px solid var(--border-subtle); }
554
+ .split-session-vendors { display: flex; flex-wrap: wrap; gap: 5px; padding: 4px 7px 7px; }
555
+ .split-session-vendors button { display: flex; align-items: center; justify-content: center; height: 30px; min-width: 30px; padding: 5px; border: 1px solid var(--border); border-radius: 6px; }
556
+ .split-session-vendors img { width: 20px; height: 20px; }
557
+ .split-session-empty { padding: 8px; font-size: 11px; color: var(--text-muted); }
558
+ .split-session-status { margin: 0; padding: 0 7px; font-size: 11px; color: var(--text-secondary); }
559
+ .split-session-hint { display: block; border-top: 1px solid var(--border-subtle); padding: 6px 7px 2px; font-size: 10px; color: var(--text-muted); }
560
+
561
+ .agent-message-bubble-text { white-space: normal; max-height: 55.8px; }
562
+ .agent-message-bubble-text p { margin: 0 0 4px; }
563
+ .agent-message-bubble-text > :last-child { margin-bottom: 0; }
564
+ .agent-message-bubble-text :is(ul, ol) { margin: 3px 0; padding-left: 18px; }
565
+ .agent-message-bubble-text code { font-size: 11px; background: var(--bg-alt); border-radius: 3px; padding: 1px 3px; }
566
+ .agent-message-bubble-text pre { margin: 4px 0; white-space: pre-wrap; overflow-wrap: anywhere; }
567
+ .agent-message-bubble-text pre code { padding: 0; }
568
+ .agent-message-bubble-text blockquote { margin: 4px 0; padding-left: 8px; border-left: 2px solid var(--border); }
569
+
570
+ /* A split reserves its destination before a session is selected. */
571
+ .split-session-empty-pane { position: absolute; display: flex; flex-direction: column; min-width: 0; min-height: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: var(--bg); }
572
+ .split-session-empty-body { flex: 1; min-width: 0; min-height: 0; display: flex; align-items: safe center; justify-content: center; padding: 16px; overflow: auto; }
573
+ .split-session-empty-body > .split-session-picker { position: relative; flex: 0 1 360px; width: 360px; min-width: 0; max-width: 100%; max-height: 100%; margin: auto 0; display: flex; flex-direction: column; }
574
+ .split-session-empty-body .split-session-results { flex: 1 1 auto; min-height: 36px; max-height: 220px; }
575
+ .split-session-empty-body .split-view-arrange-header, .split-session-empty-body .split-session-search, .split-session-empty-body .split-session-section, .split-session-empty-body .split-session-vendors, .split-session-empty-body .split-session-status, .split-session-empty-body .split-session-hint { flex-shrink: 0; }
576
+ .split-session-reserved-divider { position: absolute; pointer-events: none; }
577
+ @media (max-width: 700px) { .split-session-empty-body { padding: 8px; } }
578
+ .split-session-empty-body > .split-session-picker[hidden] { display: none; }
579
+
580
+ .agent-message-bubble-meta { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; color: var(--text-muted); font-size: 10px; line-height: 16px; white-space: nowrap; }
581
+ .agent-message-bubble-avatar { width: 16px; height: 16px; border-radius: 4px; object-fit: contain; flex: none; }
582
+ .agent-message-bubble-vendor { overflow: hidden; text-overflow: ellipsis; }
583
+ .agent-message-bubble-time { margin-left: auto; flex: none; font-variant-numeric: tabular-nums; }
@@ -9,21 +9,18 @@
9
9
  .tool-pane-header .workbench-layout-toggle { width: 24px !important; height: 24px !important; flex-basis: 24px !important; }
10
10
  .tool-pane-header .workbench-layout-toggle svg { width: 13px !important; height: 13px !important; }
11
11
  /* Tabs use the compact inset pills of the Split Worker row. */
12
- .tool-pane-tab-row, .tool-pane-header ~ .shared-browser-tabs {
12
+ .tool-pane-tab-row {
13
13
  display: flex; align-items: center; gap: 5px; padding: 4px 11px; min-height: 30px; height: auto;
14
14
  flex-shrink: 0; overflow-x: auto; background: var(--bg); border-bottom: 0; scrollbar-width: thin;
15
15
  }
16
16
  .tool-pane-tab-row .terminal-tabs, .tool-pane-tab-row .file-viewer-tabs { display: flex; gap: 5px; min-width: 0; overflow: auto; border: 0; }
17
- .tool-pane-tab-row :is(.terminal-tab, .file-viewer-tab), .tool-pane-header ~ .shared-browser-tabs .shared-browser-tab {
17
+ .tool-pane-tab-row :is(.terminal-tab, .file-viewer-tab) {
18
18
  display: inline-flex; align-items: center; flex: 0 0 auto; gap: 5px; min-width: 0; max-width: 284px;
19
19
  height: 22px; min-height: 22px; padding: 0 6px; border: 1px solid var(--border-subtle); border-radius: 5px;
20
20
  background: var(--bg-alt); color: var(--text-muted); font-size: 11px; line-height: 20px;
21
21
  }
22
- .tool-pane-tab-row :is(.terminal-tab.active, .file-viewer-tab.active), .tool-pane-header ~ .shared-browser-tabs .shared-browser-tab:has([aria-selected="true"]) { border-color: var(--accent-20); background: var(--accent-bg); color: var(--text-secondary); }
23
- .tool-pane-header ~ .shared-browser-tabs .shared-browser-tab { padding: 0; gap: 0; }
24
- .tool-pane-header ~ .shared-browser-tabs .shared-browser-tab > [role="tab"] { min-width: 0; padding: 0 6px; height: 20px; border: 0; background: transparent; font-size: 11px; }
25
- .tool-pane-header ~ .shared-browser-tabs .shared-browser-tab small { display: none; }
26
- .tool-pane-tab-row :is(.terminal-tab-more, .file-viewer-tab-close), .tool-pane-header ~ .shared-browser-tabs .shared-browser-tab > button:not([role="tab"]) { width: 20px; height: 20px; padding: 0; border: 0; background: transparent; color: var(--text-dimmer); }
22
+ .tool-pane-tab-row :is(.terminal-tab.active, .file-viewer-tab.active) { border-color: var(--accent-20); background: var(--accent-bg); color: var(--text-secondary); }
23
+ .tool-pane-tab-row :is(.terminal-tab-more, .file-viewer-tab-close) { width: 20px; height: 20px; padding: 0; border: 0; background: transparent; color: var(--text-dimmer); }
27
24
  .tool-pane-tab-row .terminal-tab-more svg { width: 11px; height: 11px; }
28
25
  .tool-pane-tab-row .terminal-new-tab-btn { height: 22px; width: 22px; padding: 3px; flex-shrink: 0; }
29
26
  @media (min-width: 1024px) {
@@ -75,8 +75,8 @@ body { background: transparent; color: inherit; }
75
75
  <button id="cmd-palette-btn" class="cmd-palette-searchbar" data-tip="Search conversations across all vendors.&#10;Ask Clay to find past work, reviews, decisions, and unfinished sessions." data-tip-suppress-when-open=".cmd-palette" aria-label="Search or ask Clay"><i data-lucide="search" aria-hidden="true"></i><span class="cmd-palette-searchbar-text">Search or ask Clay, like “What was I working on recently?”</span><kbd class="cmd-palette-searchbar-kbd"></kbd></button>
76
76
  <div class="top-bar-actions">
77
77
  <!-- Pill badges -->
78
- <div id="skip-perms-pill" class="top-bar-pill pill-error hidden"><i data-lucide="shield-off"></i> <span>Skip perms</span></div>
79
- <div id="client-count" class="top-bar-pill pill-accent hidden"><i data-lucide="users"></i> <span id="client-count-text"></span></div>
78
+ <div id="skip-perms-pill" class="top-bar-pill pill-error hidden" data-tip="Permission prompts are skipped"><i data-lucide="shield-off"></i> <span>Skip perms</span></div>
79
+ <div id="client-count" class="top-bar-pill pill-accent hidden" data-tip="Browsers connected to this project"><i data-lucide="users"></i> <span id="client-count-text"></span></div>
80
80
  <!-- Right workbench controls: same nodes and handlers as before; app JS returns them to the title bar on mobile. -->
81
81
  <button type="button" id="shared-browser-toggle-btn" class="hidden" title="Browser" aria-label="Browser" aria-controls="shared-browser-panel" aria-expanded="false" disabled><i data-lucide="globe"></i><span class="shared-browser-count hidden" aria-hidden="true"></span></button>
82
82
  <button id="terminal-toggle-btn" title="Terminal"><i data-lucide="square-terminal"></i><span id="terminal-count" class="hidden"></span></button>
@@ -307,9 +307,9 @@ body { background: transparent; color: inherit; }
307
307
  <button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
308
308
  <div id="header-full-access-btn" class="session-permission-control hidden" role="group" aria-label="Session permission mode">
309
309
  <div class="session-permission-segmented">
310
- <button type="button" data-permission-mode="default" aria-label="Ask permissions" title="Ask permissions"><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>
311
- <button type="button" data-permission-mode="auto" aria-label="Auto permissions" title="Auto permissions"><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>
312
- <button type="button" data-permission-mode="bypassPermissions" aria-label="YOLO permissions" title="YOLO permissions"><span class="permission-label">YOLO</span><span class="permission-spinner" aria-hidden="true"></span></button>
310
+ <button type="button" data-permission-mode="default" aria-label="Ask permissions" data-tip="Ask before tool actions that need permission. Previously allowed actions can run without asking again."><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>
311
+ <button type="button" data-permission-mode="auto" aria-label="Auto permissions" data-tip="Let the runtime assess tool actions and approve them automatically when allowed. Some actions may still need your permission."><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>
312
+ <button type="button" data-permission-mode="bypassPermissions" aria-label="YOLO permissions" data-tip="Run tools, file changes and commands without asking for permission. Clay access restrictions still apply."><span class="permission-label">YOLO</span><span class="permission-spinner" aria-hidden="true"></span></button>
313
313
  </div>
314
314
  <span class="session-permission-status hidden" aria-live="polite"></span>
315
315
  </div>
@@ -0,0 +1,96 @@
1
+ import { dismissTooltip } from './tooltip.js';
2
+ import { renderMarkdown } from './markdown.js';
3
+ import { store } from './store.js';
4
+
5
+ var timer = null;
6
+ function context() { return JSON.stringify([store.get('myUserId') || null, store.get('currentSlug'), store.get('projectSplitViewSocketGeneration') || 0]); }
7
+ export function messagePreviewKey(kind, identities) { return JSON.stringify([context(), kind].concat(identities)); }
8
+ function element() { return document.getElementById('agent-message-bubble'); }
9
+ function anchor(key) { return Array.from(document.querySelectorAll('[data-message-preview]')).find(function (node) { return node.dataset.messagePreview === key && node.isConnected && node.getClientRects().length && !node.closest('[hidden], .split-pane-cached'); }); }
10
+ function remove() {
11
+ if (timer) clearTimeout(timer); timer = null;
12
+ var node = element(); if (node) node.remove();
13
+ document.querySelectorAll('[aria-describedby="agent-message-bubble"]').forEach(function (chip) { chip.removeAttribute('aria-describedby'); });
14
+ if (store.get('agentMessageBubble')) store.set({ agentMessageBubble: null });
15
+ }
16
+ function position() {
17
+ var state = store.get('agentMessageBubble'), node = element(), chip = state && anchor(state.key);
18
+ if (!chip || !node) { remove(); return; }
19
+ var rect = chip.getBoundingClientRect(), width = Math.min(360, innerWidth - 20);
20
+ var left = Math.max(10, Math.min(rect.left, innerWidth - width - 10));
21
+ node.style.width = width + 'px'; node.style.left = left + 'px'; node.style.top = Math.min(rect.bottom + 8, innerHeight - 70) + 'px';
22
+ node.style.setProperty('--bubble-tip-x', Math.max(16, Math.min(width - 16, rect.left + Math.min(rect.width / 2, 40) - left)) + 'px');
23
+ }
24
+ function renderMetadata(node, latest) {
25
+ var meta = node.querySelector('.agent-message-bubble-meta');
26
+ if (!meta) { meta = document.createElement('div'); meta.className = 'agent-message-bubble-meta'; node.prepend(meta); }
27
+ meta.textContent = '';
28
+ var vendor = (store.get('vendorInfo') || {})[latest.vendor] || {};
29
+ if (vendor.avatar) {
30
+ var avatar = document.createElement('img'); avatar.className = 'agent-message-bubble-avatar';
31
+ avatar.src = vendor.avatar; avatar.alt = ''; meta.appendChild(avatar);
32
+ }
33
+ var name = document.createElement('span'); name.className = 'agent-message-bubble-vendor';
34
+ name.textContent = vendor.displayName || 'Assistant'; meta.appendChild(name);
35
+ var time = document.createElement('time'); time.className = 'agent-message-bubble-time';
36
+ if (latest.timestamp && Number.isFinite(new Date(latest.timestamp).getTime())) {
37
+ var date = new Date(latest.timestamp); time.dateTime = date.toISOString();
38
+ time.textContent = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
39
+ time.setAttribute('aria-label', 'Message time: ' + date.toLocaleString());
40
+ } else time.textContent = 'Time unavailable';
41
+ meta.appendChild(time);
42
+ }
43
+ function show(key, held, fresh) {
44
+ var latest = (store.get('agentMessagePreviews') || {})[key], chip = anchor(key);
45
+ if (!latest || !chip || store.get('splitViewWorkerConversation') || store.get('backgroundTaskViewer') || store.get('paneSuspended') || document.hidden || !store.get('connected')) return;
46
+ var old = store.get('agentMessageBubble'), expires = fresh ? Date.now() + 7000 : old && old.key === key ? old.expiresAt : 0;
47
+ var node = element();
48
+ if (!node || !old || old.key !== key || fresh) {
49
+ if (node) node.remove();
50
+ document.querySelectorAll('[aria-describedby="agent-message-bubble"]').forEach(function (other) { other.removeAttribute('aria-describedby'); });
51
+ node = document.createElement('div'); node.id = 'agent-message-bubble'; node.className = 'agent-message-bubble';
52
+ node.setAttribute('role', 'tooltip'); var text = document.createElement('div'); text.className = 'agent-message-bubble-text'; node.appendChild(text); document.body.appendChild(node);
53
+ }
54
+ renderMetadata(node, latest);
55
+ node.querySelector('.agent-message-bubble-text').innerHTML = DOMPurify.sanitize(renderMarkdown(latest.text), { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li', 'blockquote', 'del', 's'], ALLOWED_ATTR: [], ALLOW_DATA_ATTR: false }); node.dataset.held = String(!!held); node.dataset.live = String(expires > Date.now());
56
+ dismissTooltip();
57
+ chip.setAttribute('aria-describedby', node.id);
58
+ store.set({ agentMessageBubble: { key: key, expiresAt: expires, held: !!held } }); position();
59
+ if (timer) clearTimeout(timer);
60
+ if (expires > Date.now()) timer = setTimeout(function () {
61
+ timer = null; var state = store.get('agentMessageBubble'); if (state && state.key === key && !state.held) remove();
62
+ }, Math.max(0, expires - Date.now()));
63
+ }
64
+ export function rememberAgentMessage(key, preview, live) {
65
+ if (!preview || typeof preview.id !== 'string' || typeof preview.text !== 'string' || !preview.text.trim()) return;
66
+ var all = Object.assign({}, store.get('agentMessagePreviews') || {}), previous = all[key];
67
+ var fresh = !previous || previous.id !== preview.id;
68
+ all[key] = { id: preview.id, text: preview.text.slice(0, 1200), vendor: preview.vendor || previous && previous.vendor || null, timestamp: typeof preview.timestamp === 'number' && Number.isFinite(preview.timestamp) && preview.timestamp > 0 ? preview.timestamp : previous && previous.id === preview.id ? previous.timestamp : null };
69
+ var keys = Object.keys(all); while (keys.length > 128) delete all[keys.shift()];
70
+ store.set({ agentMessagePreviews: all });
71
+ var state = store.get('agentMessageBubble'), chip = anchor(key);
72
+ var held = !!(chip && (chip.matches(':hover') || chip.contains(document.activeElement)));
73
+ if (live && fresh || held || state && state.key === key) show(key, held || !!(state && state.key === key && state.held), !!live && fresh);
74
+ }
75
+ export function bindAgentMessageBubble(chip, key) {
76
+ chip.dataset.messagePreview = key;
77
+ if (chip.dataset.previewBound) return;
78
+ chip.dataset.previewBound = 'true';
79
+ function enter() { show(chip.dataset.messagePreview, true, false); }
80
+ function leave() {
81
+ var state = store.get('agentMessageBubble'); if (!state || state.key !== chip.dataset.messagePreview || chip.matches(':hover') || chip.contains(document.activeElement)) return;
82
+ if (state.expiresAt <= Date.now()) remove(); else show(state.key, false, false);
83
+ }
84
+ chip.addEventListener('pointerenter', enter); chip.addEventListener('pointerleave', leave);
85
+ chip.addEventListener('focusin', enter); chip.addEventListener('focusout', leave);
86
+ var state = store.get('agentMessageBubble'); if (state && state.key === key) requestAnimationFrame(position);
87
+ }
88
+ store.subscribe(function (state, previous) {
89
+ if (state.splitViewWorkerConversation && !previous.splitViewWorkerConversation || state.backgroundTaskViewer && !previous.backgroundTaskViewer) remove();
90
+ if (state.sessionOriginId !== previous.sessionOriginId || state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration || state.connected === false && previous.connected !== false || state.projectSplitViewActiveId !== previous.projectSplitViewActiveId || state.paneSuspended === true && previous.paneSuspended !== true) {
91
+ remove(); if (Object.keys(state.agentMessagePreviews || {}).length) store.set({ agentMessagePreviews: {} });
92
+ }
93
+ });
94
+ document.addEventListener('keydown', function (event) { if (event.key === 'Escape') remove(); });
95
+ document.addEventListener('visibilitychange', function () { if (document.hidden) remove(); });
96
+ window.addEventListener('resize', position); window.addEventListener('scroll', function (event) { if (!element() || event.target === element()) return; position(); }, true);
@@ -1,3 +1,4 @@
1
+ import { highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
1
2
  import { beginProjectDebatePrepend, endProjectDebatePrepend } from './project-debate-workspace.js';
2
3
  // app-header.js - Session rename, session info popover, progressive history loading
3
4
  // Extracted from app.js (PR-34)
@@ -422,6 +423,11 @@ export function prependOlderHistory(items, meta) {
422
423
  updateContextPanel();
423
424
  updateUsagePanel();
424
425
 
426
+ // History prepend deliberately skipped per-message enhancements during replay.
427
+ // Run the same completed-replay pass after restoring the live render state.
428
+ highlightCodeBlocks(messagesEl);
429
+ renderMermaidBlocks(messagesEl);
430
+
425
431
  // Fix scroll: restore anchor element to same visual position
426
432
  if (anchorEl) {
427
433
  var newTop = anchorEl.getBoundingClientRect().top;
@@ -1,3 +1,7 @@
1
+ import { handleSplitSessionCreated, finishPickerCreation } from './split-view-picker-actions.js';
2
+ import { handleWorkerMessagePreview } from './worker-message-previews.js';
3
+ import { handleBackgroundTaskPreview } from './background-task-previews.js';
4
+ import { handleBackgroundTaskRead } from './background-task-viewer.js';
1
5
  import { showCreatedWorkerPresentation } from './worker-created-presentation.js';
2
6
  import { handleWorkerRemovalResult } from './split-view-worker-removal.js';
3
7
  import { handleStudioDocumentMessage } from './clay-studio-document.js';
@@ -152,6 +156,7 @@ export function processMessage(msg) {
152
156
  if (handleScheduledTaskMessage(msg)) return;
153
157
  if (handleKnowledgeWorkbenchMessage(msg)) return;
154
158
  if (handleSessionCreateMessage(msg)) return;
159
+ if (handleSplitSessionCreated(msg)) return;
155
160
  if (msg && msg.type === "new_session_result" && handleNewSessionResult(msg)) return;
156
161
  if (handleDefaultVendorMessage(msg)) return;
157
162
  if (handleContextViewMessage(msg)) return;
@@ -281,14 +286,6 @@ export function processMessage(msg) {
281
286
  if (messagesEl && msg.sessionId === store.get("activeSessionId") && msg.sessionOriginId === store.get("sessionOriginId")) { messagesEl.dataset.controlSessionId = String(msg.sessionId); messagesEl.dataset.controlSessionOrigin = msg.sessionOriginId; messagesEl.dataset.controlHistoryRevision = msg.controlHistoryRevision || ""; }
282
287
  store.set({ replayingHistory: false });
283
288
  resumeThinkingAfterReplay(store.get('sessionIsProcessing'));
284
- // Batched syntax highlight + mermaid pass for the entire replayed
285
- // transcript. Per-message highlights are skipped during replay
286
- // (see markdown.js) to avoid cascading reflows that the sticky-
287
- // bottom observer chases for several seconds on long sessions.
288
- if (messagesEl) {
289
- highlightCodeBlocks(messagesEl);
290
- renderMermaidBlocks(messagesEl);
291
- }
292
289
  // Compact dead-session todo widgets (unfinished items will never
293
290
  // resolve — the agent isn't coming back) so they don't anchor
294
291
  // visual position mid-page on resume.
@@ -314,6 +311,15 @@ export function processMessage(msg) {
314
311
  markAllToolsDone();
315
312
  finalizeAssistantBlock();
316
313
  }
314
+ // Enhance only after the final replay flush has finished writing Markdown.
315
+ // Batched syntax highlight + mermaid pass for the entire replayed
316
+ // transcript. Per-message highlights are skipped during replay
317
+ // (see markdown.js) to avoid cascading reflows that the sticky-
318
+ // bottom observer chases for several seconds on long sessions.
319
+ if (messagesEl) {
320
+ highlightCodeBlocks(messagesEl);
321
+ renderMermaidBlocks(messagesEl);
322
+ }
317
323
  stopUrgentBlink();
318
324
  // Clean up debate UI if debate is not active after replay
319
325
  if (!isDebateActive()) {
@@ -790,6 +796,7 @@ export function processMessage(msg) {
790
796
  renderMateSessionList(msg.sessions || []);
791
797
  renderSessionList(msg.sessions || []);
792
798
  store.set({ paneBindingSessions: msg.sessions || [] });
799
+ finishPickerCreation();
793
800
  requestProjectSplitViews();
794
801
  refreshPaneActivation();
795
802
  syncGithubSessions(msg.sessions || []);
@@ -1467,8 +1474,18 @@ export function processMessage(msg) {
1467
1474
  updateSubagentTaskStatus(msg.parentToolId, msg.patch);
1468
1475
  break;
1469
1476
 
1477
+ case "split_worker_message_preview":
1478
+ handleWorkerMessagePreview(msg);
1479
+ break;
1480
+ case "background_task_preview":
1481
+ handleBackgroundTaskPreview(msg);
1482
+ break;
1483
+ case "background_task_read":
1484
+ handleBackgroundTaskRead(msg);
1485
+ break;
1486
+
1470
1487
  case "active_background_tasks":
1471
- store.set({ activeBackgroundTasks: Array.isArray(msg.tasks) ? msg.tasks : [] });
1488
+ store.set({ activeBackgroundTasks: Array.isArray(msg.tasks) ? msg.tasks : [], backgroundTaskCatalog: Array.isArray(msg.catalog) ? msg.catalog : msg.tasks || [] });
1472
1489
  break;
1473
1490
 
1474
1491
  case "result":
@@ -0,0 +1,8 @@
1
+ import { store } from './store.js';
2
+ import { messagePreviewKey, rememberAgentMessage } from './agent-message-bubbles.js';
3
+ export function backgroundPreviewKey(task) { return messagePreviewKey('background', [store.get('sessionOriginId'), task.task_id, task.run_id || null]); }
4
+ export function handleBackgroundTaskPreview(message) {
5
+ if (!store.get('connected') || message.sessionOriginId !== store.get('sessionOriginId')) return;
6
+ var task = (store.get('backgroundTaskCatalog') || []).find(function (task) { return task.task_id === message.taskId && (task.run_id || null) === (message.runId || null); });
7
+ if (task) rememberAgentMessage(backgroundPreviewKey(task), message.preview, message.live === true);
8
+ }
@@ -0,0 +1,97 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { appendWorkerTranscript } from './split-view-worker-transcript.js';
4
+ import { captureWorkerDisclosures, restoreWorkerDisclosures, workerActivityNames } from './split-view-worker-activity.js';
5
+ import { iconHtml, refreshIcons } from './icons.js';
6
+
7
+ function panel() { return document.getElementById('background-task-viewer'); }
8
+ function current(state) {
9
+ return state && store.get('connected') && state.socketGeneration === store.get('projectSplitViewSocketGeneration') && state.origin === store.get('sessionOriginId') &&
10
+ state.slug === store.get('currentSlug') && state.owner === store.get('myUserId') &&
11
+ (store.get('backgroundTaskCatalog') || []).some(function (task) { return task.task_id === state.taskId && (task.run_id || null) === state.runId; });
12
+ }
13
+ export function closeBackgroundTask(focus) {
14
+ var state = store.get('backgroundTaskViewer');
15
+ if (state && state.timer) clearTimeout(state.timer);
16
+ if (state && state.timeout) clearTimeout(state.timeout);
17
+ store.set({ backgroundTaskViewer: null });
18
+ var element = panel(); if (element) element.remove();
19
+ if (focus && state) {
20
+ var chip = document.querySelector('[data-background-task="' + CSS.escape(state.taskId) + '"]'); if (chip) chip.focus();
21
+ }
22
+ }
23
+ export function openBackgroundTask(task) {
24
+ closeBackgroundTask(false);
25
+ var state = { taskId: task.task_id, runId: task.run_id || null, title: task.description || 'Background task',
26
+ origin: store.get('sessionOriginId'), slug: store.get('currentSlug'), owner: store.get('myUserId'), socketGeneration: store.get('projectSplitViewSocketGeneration'), offset: 0 };
27
+ if (!current(state)) return;
28
+ store.set({ backgroundTaskViewer: state });
29
+ var element = document.createElement('section'); element.id = 'background-task-viewer'; element.className = 'split-view-worker-popover background-task-viewer';
30
+ element.setAttribute('role', 'dialog'); element.setAttribute('aria-label', 'Background task conversation');
31
+ element.innerHTML = '<header class="split-view-worker-popover-header"><strong></strong><button type="button" aria-label="Close background task">' + iconHtml('x') + '</button></header>' +
32
+ '<div class="split-view-worker-popover-source">Read-only · Native background task</div><div class="split-view-worker-popover-body readonly-session-transcript" tabindex="0"></div>' +
33
+ '<footer class="split-view-worker-popover-footer"><span role="status"></span><button type="button" data-page="previous">Previous</button><button type="button" data-page="next">Next</button><button type="button" data-retry>Refresh</button></footer>';
34
+ element.querySelector('strong').textContent = state.title;
35
+ element.querySelector('[aria-label="Close background task"]').addEventListener('click', function () { closeBackgroundTask(true); });
36
+ element.querySelector('[data-retry]').addEventListener('click', request);
37
+ element.querySelectorAll('[data-page]').forEach(function (button) { button.addEventListener('click', function () {
38
+ var live = store.get('backgroundTaskViewer'); if (!current(live) || live.pending) return;
39
+ store.set({ backgroundTaskViewer: Object.assign({}, live, { offset: button.dataset.page === 'next' ? live.offset + 100 : Math.max(0, live.offset - 100) }) }); request();
40
+ }); });
41
+ document.body.appendChild(element); refreshIcons(); request();
42
+ element.querySelector('button').focus();
43
+ }
44
+ function request() {
45
+ var state = store.get('backgroundTaskViewer');
46
+ if (!current(state)) { closeBackgroundTask(false); return; }
47
+ if (state.pending) return;
48
+ if (state.timer) clearTimeout(state.timer);
49
+ var id = crypto.randomUUID();
50
+ var timeout = setTimeout(function () {
51
+ var live = store.get('backgroundTaskViewer'); if (!live || live.requestId !== id) return;
52
+ store.set({ backgroundTaskViewer: Object.assign({}, live, { pending: false, timeout: null }) });
53
+ var element = panel(); if (element) element.querySelector('[role="status"]').textContent = 'Task read timed out. Use Refresh to retry.';
54
+ }, 18000);
55
+ store.set({ backgroundTaskViewer: Object.assign({}, state, { requestId: id, pending: true, timeout: timeout, timer: null }) });
56
+ var sent = getWs().send(JSON.stringify({ type: 'background_task_read', requestId: id, taskId: state.taskId, runId: state.runId, sessionOriginId: state.origin, offset: state.offset }));
57
+ if (sent === false) { clearTimeout(timeout); var live = store.get('backgroundTaskViewer'); if (live && live.requestId === id) { store.set({ backgroundTaskViewer: Object.assign({}, live, { pending: false, timeout: null }) }); panel().querySelector('[role="status"]').textContent = 'Task pane is not ready. Use Refresh to retry.'; } }
58
+ }
59
+ export function handleBackgroundTaskRead(message) {
60
+ var state = store.get('backgroundTaskViewer'), element = panel();
61
+ if (!element || !current(state) || message.requestId !== state.requestId || message.sessionOriginId !== state.origin ||
62
+ message.taskId !== state.taskId || (message.runId || null) !== state.runId) return;
63
+ clearTimeout(state.timeout);
64
+ var body = element.querySelector('.split-view-worker-popover-body'), footer = element.querySelector('[role="status"]');
65
+ var data = message.data;
66
+ if (message.error) { footer.textContent = message.error; state = Object.assign({}, state, { failures: (state.failures || 0) + 1 }); }
67
+ else if (data) {
68
+ var signature = JSON.stringify(data), changed = signature !== state.signature;
69
+ if (changed) {
70
+ var follow = body.scrollHeight - body.scrollTop - body.clientHeight < 48, top = body.scrollTop;
71
+ var disclosures = captureWorkerDisclosures(body); body.textContent = '';
72
+ if (data.kind === 'shell') { var pre = document.createElement('pre'); pre.textContent = data.output || 'No output yet.'; body.appendChild(pre); }
73
+ else appendWorkerTranscript(body, data.messages || [], { vendor: 'claude', userName: 'Parent agent', assistantName: 'Background agent' });
74
+ restoreWorkerDisclosures(body, disclosures);
75
+ body.scrollTop = follow ? body.scrollHeight : top;
76
+ }
77
+ footer.textContent = (data.active ? 'Running' : 'Finished') + (data.truncated ? ' · Last 8 KiB of output' : '') +
78
+ (data.kind === 'agent' && !data.messages.length ? ' · No transcript messages yet' : '');
79
+ element.querySelector('[data-page="previous"]').hidden = data.kind !== 'agent' || state.offset === 0;
80
+ element.querySelector('[data-page="next"]').hidden = !data.hasMore;
81
+ state = Object.assign({}, state, { signature: signature, failures: 0 });
82
+ }
83
+ var task = (store.get('backgroundTaskCatalog') || []).find(function (item) { return item.task_id === state.taskId; });
84
+ var timer = task && task.active !== false && (state.failures || 0) < 3 ? setTimeout(function () { if (!document.hidden) request(); else schedule(); }, 2000) : null;
85
+ store.set({ backgroundTaskViewer: Object.assign({}, state, { pending: false, timeout: null, timer: timer }) });
86
+ }
87
+ function schedule() {
88
+ var state = store.get('backgroundTaskViewer'); if (!current(state)) return;
89
+ var timer = setTimeout(function () { if (!document.hidden) request(); else schedule(); }, 2000);
90
+ store.set({ backgroundTaskViewer: Object.assign({}, state, { timer: timer }) });
91
+ }
92
+ store.subscribe(function (state, prev) {
93
+ if (state.backgroundTaskViewer && (state.sessionOriginId !== prev.sessionOriginId || state.currentSlug !== prev.currentSlug ||
94
+ state.myUserId !== prev.myUserId || state.projectSplitViewSocketGeneration !== prev.projectSplitViewSocketGeneration || !state.connected || state.backgroundTaskCatalog !== prev.backgroundTaskCatalog && !current(state.backgroundTaskViewer))) closeBackgroundTask(false);
95
+ });
96
+ document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && panel()) { event.preventDefault(); closeBackgroundTask(true); } });
97
+ document.addEventListener('click', function (event) { var element = panel(); if (element && !element.contains(event.target) && !event.target.closest('[data-background-task]')) closeBackgroundTask(false); });