clay-server 4.0.0-beta.15 → 4.0.0-beta.16

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 (58) hide show
  1. package/lib/capsule-frame-server.js +26 -0
  2. package/lib/capsule-mate-turn.js +105 -0
  3. package/lib/capsule-pig-logic.js +33 -1
  4. package/lib/capsule-server-runtimes.js +0 -2
  5. package/lib/capsules/pig/display.js +8 -0
  6. package/lib/capsules/pig/manifest.json +1 -1
  7. package/lib/capsules/pig/ui.json +52 -14
  8. package/lib/daemon.js +8 -0
  9. package/lib/migrate-single-user.js +2 -0
  10. package/lib/project-capsule-turn.js +96 -0
  11. package/lib/project-logs-mcp-server.js +11 -10
  12. package/lib/project-session-pair.js +15 -5
  13. package/lib/project-worker-proposal.js +8 -8
  14. package/lib/project.js +13 -1
  15. package/lib/public/app.js +6 -15
  16. package/lib/public/css/capsule-ui.css +21 -1
  17. package/lib/public/css/home-hub.css +4 -0
  18. package/lib/public/css/input.css +84 -3
  19. package/lib/public/css/session-actions.css +16 -101
  20. package/lib/public/css/sticky-notes.css +3 -7
  21. package/lib/public/index.html +31 -9
  22. package/lib/public/modules/app-message-cards.js +2 -1
  23. package/lib/public/modules/app-messages.js +10 -2
  24. package/lib/public/modules/app-misc.js +2 -14
  25. package/lib/public/modules/app-panels.js +3 -2
  26. package/lib/public/modules/app-projects.js +6 -3
  27. package/lib/public/modules/capsule-preference.js +44 -0
  28. package/lib/public/modules/context-sources.js +0 -3
  29. package/lib/public/modules/home-chat-empty-state.js +39 -0
  30. package/lib/public/modules/home-chat-identity.js +9 -0
  31. package/lib/public/modules/home-mate-chat.js +15 -35
  32. package/lib/public/modules/home-tool-frame.js +17 -0
  33. package/lib/public/modules/home-tools.js +9 -0
  34. package/lib/public/modules/input.js +32 -6
  35. package/lib/public/modules/paste-modal.js +84 -0
  36. package/lib/public/modules/project-removal-target.js +26 -0
  37. package/lib/public/modules/session-actions.js +29 -105
  38. package/lib/public/modules/user-settings.js +4 -0
  39. package/lib/public/modules/worker-pane-lock.js +2 -0
  40. package/lib/sdk-bridge.js +14 -7
  41. package/lib/server-experimental-settings.js +49 -0
  42. package/lib/server-home-chat-events.js +28 -1
  43. package/lib/server-home-chat.js +1 -1
  44. package/lib/server-settings.js +5 -0
  45. package/lib/server-tools.js +30 -0
  46. package/lib/server.js +35 -2
  47. package/lib/session-driver-orchestration.js +31 -0
  48. package/lib/session-pair-prompts.js +24 -5
  49. package/lib/tools-registry.js +46 -1
  50. package/lib/users-experimental-preferences.js +30 -0
  51. package/lib/users.js +6 -0
  52. package/lib/ws-schema.js +2 -0
  53. package/lib/yoke/adapters/codex.js +24 -14
  54. package/lib/yoke/vendor-registry.js +1 -1
  55. package/package.json +2 -2
  56. package/lib/capsule-tictactoe-logic.js +0 -260
  57. package/lib/capsules/tictactoe/manifest.json +0 -9
  58. package/lib/capsules/tictactoe/ui.json +0 -203
@@ -2,6 +2,7 @@ var crypto = require("crypto");
2
2
  var yoke = require("./yoke");
3
3
  var buildShape = require("./session-spawn-mcp-server").buildShape;
4
4
  var driverEligibility = require("./session-driver-eligibility");
5
+ var driverOrchestration = require("./session-driver-orchestration");
5
6
  var MAX_SUMMARY_CHARS = 600;
6
7
  var MAX_PLAN_CHARS = 6000;
7
8
  var MAX_TASK_CHARS = 30000;
@@ -14,7 +15,6 @@ function modelLabel(entry) {
14
15
  if (typeof entry === "string") return entry;
15
16
  return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
16
17
  }
17
-
18
18
  function toolResult(value) {
19
19
  return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
20
20
  }
@@ -25,10 +25,9 @@ function attachWorkerProposal(ctx) {
25
25
  return !!session && sm.sessions.get(session.localId) === session;
26
26
  }
27
27
 
28
+ function canOffer(session) { return !ctx.isMate && isLiveSession(session) && driverEligibility.isEligibleDriverSession(session, sm); }
28
29
  function isEligible(session) {
29
- if (ctx.isMate || !isLiveSession(session) || !driverEligibility.isEligibleDriverSession(session, sm)) return false;
30
- if (store.groupForMember(session.localId)) return false;
31
- return true;
30
+ return canOffer(session) && !store.groupForMember(session.localId);
32
31
  }
33
32
  function safeModelsByVendor(installed) {
34
33
  var result = {};
@@ -463,8 +462,9 @@ function attachWorkerProposal(ctx) {
463
462
  return true;
464
463
  }
465
464
 
466
- function getToolDefs(session) {
467
- if (!isEligible(session)) return [];
465
+ function getToolDefs(session, options) {
466
+ var persistent = !!(options && options.persistent);
467
+ if (!isEligible(session) && !(persistent && canOffer(session))) return [];
468
468
  return [{
469
469
  name: "propose_worker",
470
470
  description: "Propose a visible Split Worker for implementation-heavy execution. This non-mutating tool only shows the user a card where they choose vendor, model, and effort. It never creates a session or delegates work until the user accepts.",
@@ -482,8 +482,8 @@ function attachWorkerProposal(ctx) {
482
482
  }
483
483
 
484
484
  function getSystemPrompt(session) {
485
- if (!isEligible(session)) return "";
486
- return "For implementation-heavy work, use propose_worker before substantial execution. Recommend an exact available vendor, model, and effort, and give a concise recommendationRationale explaining why all three fit the task. Clay always records and shows the runtime configuration card. In full-access mode Clay may auto-accept that exact validated recommendation; otherwise it waits for the user's explicit choice. After posting, end the turn. If accepted, Clay creates the exact Driver/Split Worker pair and delegates the proposed task once. If declined, continue in this Driver session. Do not call send_to_partner while unpaired.";
485
+ if (!isEligible(session) || !driverOrchestration.isHighTierDriverSession(session, sm)) return "";
486
+ return "For implementation-heavy work, use propose_worker before substantial execution. Work is implementation-heavy when it spans multiple modules, crosses client/server/data boundaries, requires a migration, or contains independent investigation and execution. Do not skip delegation merely because you can implement it yourself; preserve the Driver for decomposition, runtime selection, judgment, review, and integration. The user's explicit request to work directly takes precedence, and work that must remain sequential in this session stays here. Recommend an exact available vendor, model, and effort, and give a concise recommendationRationale explaining why all three fit the task. Clay always records and shows the runtime configuration card. In full-access mode Clay may auto-accept that exact validated recommendation; otherwise it waits for the user's explicit choice. After posting, end the turn. If accepted, Clay creates the exact Driver/Split Worker pair and delegates the proposed task once. If declined, continue in this Driver session. Do not call send_to_partner while unpaired.";
487
487
  }
488
488
 
489
489
  return {
package/lib/project.js CHANGED
@@ -43,6 +43,7 @@ var { attachEmail: attachEmailModule } = require("./project-email");
43
43
  var { attachSessionSpawn } = require("./project-session-spawn");
44
44
  var { attachDelegatedSession } = require("./project-delegated-session");
45
45
  var { attachDelegatedFollowUp } = require("./project-delegated-follow-up");
46
+ var { attachCapsuleTurn } = require("./project-capsule-turn");
46
47
  var workspaceSessionRef = require("./workspace-query-service").sessionRef;
47
48
  var { attachSessionPair } = require("./project-session-pair");
48
49
  var { attachSessionHandoff } = require("./project-session-handoff");
@@ -1321,7 +1322,7 @@ function createProjectContext(opts) {
1321
1322
  if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
1322
1323
  return;
1323
1324
  }
1324
- if (msg.type === "project_assignment_response" || msg.type === "dm_open" || msg.type === "dm_send" || msg.type === "dm_list" || msg.type === "dm_typing" || msg.type === "dm_add_favorite" || msg.type === "dm_remove_favorite" || msg.type === "mate_create" || msg.type === "mate_list" || msg.type === "mate_delete" || msg.type === "mate_update" || msg.type === "mate_readd_builtin" || msg.type === "mate_list_available_builtins" || msg.type === "email_accounts_list" || msg.type === "email_account_add" || msg.type === "email_account_remove" || msg.type === "email_account_test" || msg.type === "home_debates_list" || msg.type === "home_mate_present" || msg.type === "home_mate_open" || msg.type === "home_mate_sessions_list" || msg.type === "home_mate_session_open" || msg.type === "home_mate_send" || msg.type === "home_mate_new_session" || msg.type === "home_mate_debate_plan" || msg.type === "home_debate_proposal_response" || msg.type === "home_mate_creation_plan" || msg.type === "home_mate_creation_proposal_response" || msg.type === "home_mate_close" || msg.type === "home_mate_memory_list" || msg.type === "home_mate_knowledge_list" || msg.type === "home_mate_models_get" || msg.type === "home_mate_model_set" || msg.type === "home_dock_get" || msg.type === "home_dock_set" || msg.type === "home_surface_get" || msg.type === "home_surface_set" || msg.type === "tools_list" || msg.type === "tool_get" || msg.type === "tool_install" || msg.type === "tool_remove" || msg.type === "tool_storage_op" || msg.type === "tool_llm_op" || msg.type === "tool_llm_config_get" || msg.type === "tool_control_response" || msg.type === "tool_source_get" || msg.type === "tool_mate_access_set") {
1325
+ if (msg.type === "project_assignment_response" || msg.type === "dm_open" || msg.type === "dm_send" || msg.type === "dm_list" || msg.type === "dm_typing" || msg.type === "dm_add_favorite" || msg.type === "dm_remove_favorite" || msg.type === "mate_create" || msg.type === "mate_list" || msg.type === "mate_delete" || msg.type === "mate_update" || msg.type === "mate_readd_builtin" || msg.type === "mate_list_available_builtins" || msg.type === "email_accounts_list" || msg.type === "email_account_add" || msg.type === "email_account_remove" || msg.type === "email_account_test" || msg.type === "home_debates_list" || msg.type === "home_mate_present" || msg.type === "home_mate_open" || msg.type === "home_mate_sessions_list" || msg.type === "home_mate_session_open" || msg.type === "home_mate_send" || msg.type === "home_mate_new_session" || msg.type === "home_mate_debate_plan" || msg.type === "home_debate_proposal_response" || msg.type === "home_mate_creation_plan" || msg.type === "home_mate_creation_proposal_response" || msg.type === "home_mate_close" || msg.type === "home_mate_memory_list" || msg.type === "home_mate_knowledge_list" || msg.type === "home_mate_models_get" || msg.type === "home_mate_model_set" || msg.type === "home_dock_get" || msg.type === "home_dock_set" || msg.type === "home_surface_get" || msg.type === "home_surface_set" || msg.type === "tools_list" || msg.type === "tool_get" || msg.type === "tool_install" || msg.type === "tool_remove" || msg.type === "tool_storage_op" || msg.type === "tool_llm_op" || msg.type === "tool_llm_config_get" || msg.type === "tool_control_response" || msg.type === "tool_source_get" || msg.type === "tool_mate_access_set" || msg.type === "tool_server_control" || msg.type === "tool_frame_url") {
1325
1326
  if (typeof opts.onDmMessage === "function") {
1326
1327
  opts.onDmMessage(ws, msg, slug);
1327
1328
  }
@@ -1666,6 +1667,16 @@ function createProjectContext(opts) {
1666
1667
  return _models.resolveConfiguredModel({ _clayUser: user || null }, "standard");
1667
1668
  },
1668
1669
  });
1670
+ var _capsuleTurn = attachCapsuleTurn({
1671
+ sm: sm,
1672
+ getSdk: function () { return sdk; },
1673
+ getLinuxUserForSession: getLinuxUserForSession,
1674
+ isMultiUser: function () { return usersModule.isMultiUser(); },
1675
+ resolveModel: function (principal) {
1676
+ var user = principal && principal.userId ? usersModule.findUserById(principal.userId) : null;
1677
+ return _models.resolveConfiguredModel({ _clayUser: user || null }, "standard");
1678
+ },
1679
+ });
1669
1680
  var _delegatedFollowUp = attachDelegatedFollowUp({
1670
1681
  sm: sm,
1671
1682
  getSdk: function () { return sdk; },
@@ -2284,6 +2295,7 @@ function createProjectContext(opts) {
2284
2295
  createDelegatedSession: _delegatedSession.createDelegatedSession,
2285
2296
  inspectDelegatedFollowUp: _delegatedFollowUp.inspectDelegatedFollowUp,
2286
2297
  dispatchDelegatedFollowUp: _delegatedFollowUp.dispatchDelegatedFollowUp,
2298
+ deliverCapsuleTurn: _capsuleTurn.deliverCapsuleTurn,
2287
2299
  unregisterHomeDebateClient: function (ws) { homeDebateClients.delete(ws); },
2288
2300
  handleHomeDebateProposalResponse: function (ws, msg, session) { return _debateProposal.handleHomeMessage(ws, msg, session); },
2289
2301
  handleHomeMateCreationProposalResponse: function (ws, msg, session) { return _mateCreationProposal.handleHomeMessage(ws, msg, session); },
package/lib/public/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { avatarUrl, userAvatarUrl, mateAvatarUrl } from './modules/avatar.js';
2
- import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
2
+ import { showToast, escapeHtml } from './modules/utils.js';
3
3
  import { refreshIcons, iconHtml } from './modules/icons.js';
4
4
  import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
5
5
  import { initSidebar, updatePageTitle, spawnDustParticles } from './modules/sidebar.js';
@@ -67,7 +67,8 @@ import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, hand
67
67
  import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity } from './modules/app-favicon.js';
68
68
  import { initHeader, closeSessionInfoPopover as _hdrCloseSessionInfoPopover, updateHistorySentinel as _hdrUpdateHistorySentinel, requestMoreHistory as _hdrRequestMoreHistory, prependOlderHistory as _hdrPrependOlderHistory } from './modules/app-header.js';
69
69
  import { initSessionActions } from './modules/session-actions.js';
70
- import { initMisc, flushPendingExtMessages, showImageModal as _miscShowImageModal, closeImageModal as _miscCloseImageModal, showPasteModal as _miscShowPasteModal, closePasteModal as _miscClosePasteModal, showConfirm as _miscShowConfirm, hideConfirm as _miscHideConfirm, showForceChangePinOverlay as _miscShowForceChangePinOverlay, sendExtensionCommand as _miscSendExtensionCommand, handleExtensionResult as _miscHandleExtensionResult } from './modules/app-misc.js';
70
+ import { initMisc, flushPendingExtMessages, showImageModal as _miscShowImageModal, closeImageModal as _miscCloseImageModal, showConfirm as _miscShowConfirm, hideConfirm as _miscHideConfirm, showForceChangePinOverlay as _miscShowForceChangePinOverlay, sendExtensionCommand as _miscSendExtensionCommand, handleExtensionResult as _miscHandleExtensionResult } from './modules/app-misc.js';
71
+ import { showPasteModal } from './modules/paste-modal.js';
71
72
  import { initSkillInstall, requireSkills as _siRequireSkills, handleSkillInstallWs as _siHandleSkillInstallWs } from './modules/app-skills-install.js';
72
73
  import { initDebateUi, showDebateConcludeConfirm as _debShowDebateConcludeConfirm, exitDebateConcludeMode as _debExitDebateConcludeMode, handleDebateConcludeSend as _debHandleDebateConcludeSend, showDebateEndedMode as _debShowDebateEndedMode, exitDebateEndedMode as _debExitDebateEndedMode, showDebateUserFloor as _debShowDebateUserFloor, exitDebateFloorMode as _debExitDebateFloorMode, handleDebateFloorSend as _debHandleDebateFloorSend, renderDebateUserFloorDone as _debRenderDebateUserFloorDone, showDebateSticky as _debShowDebateSticky, showDebateBottomBar as _debShowDebateBottomBar, removeDebateBottomBar as _debRemoveDebateBottomBar, sendDebateStickyComment as _debSendDebateStickyComment, updateDebateRound as _debUpdateDebateRound } from './modules/app-debate-ui.js';
73
74
  import { initLoopUi, updateLoopInputVisibility as _loopUpdateLoopInputVisibility, updateLoopButton as _loopUpdateLoopButton, showLoopBanner as _loopShowLoopBanner, updateLoopBanner as _loopUpdateLoopBanner, updateRalphBars as _loopUpdateRalphBars, showRalphCraftingBar as _loopShowRalphCraftingBar, showRalphApprovalBar as _loopShowRalphApprovalBar, updateRalphApprovalStatus as _loopUpdateRalphApprovalStatus, openRalphPreviewModal as _loopOpenRalphPreviewModal, showExecModal as _loopShowExecModal, closeExecModal as _loopCloseExecModal, updateExecModalStatus as _loopUpdateExecModalStatus } from './modules/app-loop-ui.js';
@@ -206,16 +207,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
206
207
  }
207
208
 
208
209
  // Modal close handlers (replaces inline onclick)
209
- $("paste-modal").querySelector(".confirm-backdrop").addEventListener("click", function() {
210
- $("paste-modal").classList.add("hidden");
211
- });
212
- $("paste-modal").querySelector(".paste-modal-close").addEventListener("click", function() {
213
- $("paste-modal").classList.add("hidden");
214
- });
215
- $("paste-modal").querySelector(".paste-modal-copy").addEventListener("click", function() {
216
- var body = $("paste-modal-body");
217
- if (body) copyToClipboard(body.textContent, "Copied to clipboard");
218
- });
219
210
  $("mermaid-modal").querySelector(".confirm-backdrop").addEventListener("click", closeMermaidModal);
220
211
  $("mermaid-modal").querySelector(".mermaid-modal-btn[title='Close']").addEventListener("click", closeMermaidModal);
221
212
  $("image-modal").querySelector(".confirm-backdrop").addEventListener("click", closeImageModal);
@@ -263,9 +254,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
263
254
  // --- Header session rename, session info popover -> modules/app-header.js
264
255
  function closeSessionInfoPopover() { _hdrCloseSessionInfoPopover(); }
265
256
 
266
- // --- Modals (confirm, paste) -> modules/app-misc.js
267
- function showPasteModal(text) { _miscShowPasteModal(text); }
268
- function closePasteModal() { _miscClosePasteModal(); }
257
+ // --- Confirmation modal -> modules/app-misc.js
269
258
  function showConfirm(text, onConfirm, okLabel, destructive) { _miscShowConfirm(text, onConfirm, okLabel, destructive); }
270
259
  function hideConfirm() { _miscHideConfirm(); }
271
260
 
@@ -345,6 +334,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
345
334
  projectLogsUnread: 0,
346
335
  projectLogsSeenRevisions: {},
347
336
  installedTools: [],
337
+ capsulesEnabled: false,
338
+ capsulesPreferenceLoaded: false,
348
339
  homeToolRegistryLoaded: false,
349
340
  toolScanErrors: [],
350
341
 
@@ -384,9 +384,29 @@ textarea.tool-input { min-height: 92px; resize: vertical; }
384
384
  .tool-pagination { flex-wrap: wrap; justify-content: flex-start; }
385
385
  }
386
386
 
387
+ /* Capsule engagement note in the Mate transcript: a quiet system line, not a
388
+ speech bubble, because no one typed it. */
389
+ .home-capsule-turn-note {
390
+ align-self: center;
391
+ margin: 6px auto;
392
+ padding: 3px 12px;
393
+ border-radius: 999px;
394
+ background: color-mix(in srgb, var(--text-muted) 10%, transparent);
395
+ color: var(--text-muted);
396
+ font-size: 11.5px;
397
+ text-align: center;
398
+ }
399
+
387
400
  /* Rich Display frame: the additive sandboxed element above the floor. */
401
+ /* There is no global .hidden utility, so the capsule surfaces scope their own:
402
+ the frame stays invisible until its bridge reports ready, the floor hides
403
+ behind the toggle, and a cleared error leaves the screen. */
404
+ .home-tool-frame-wrap.hidden,
405
+ .home-tool-frame-toggle.hidden,
406
+ .home-tool-display.hidden,
407
+ .home-tool-error.hidden { display: none; }
388
408
  .home-tool-frame-wrap { width: 100%; }
389
- .home-tool-frame { display: block; width: 100%; min-height: 240px; border: 0; border-radius: 10px; background: var(--bg); }
409
+ .home-tool-frame { display: block; width: 100%; min-height: 0; border: 0; border-radius: 10px; background: var(--bg); }
390
410
  .home-tool-frame-toggle {
391
411
  display: block; margin: 6px 0 0 auto; padding: 3px 10px;
392
412
  border: 1px solid var(--border-subtle); border-radius: 999px;
@@ -12,6 +12,10 @@ body.home-active #sidebar-overlay,
12
12
  body.home-active .title-bar-content,
13
13
  body.home-active #user-island { display: none !important; }
14
14
 
15
+ body.capsules-disabled #home-tools-btn,
16
+ body.capsules-disabled #home-dock-divider,
17
+ body.capsules-disabled #home-tool-workbench { display: none !important; }
18
+
15
19
  body.home-active #main-area { border: 0; border-radius: 0; }
16
20
 
17
21
  body.home-surface-boot-pending #connect-overlay.hidden {
@@ -52,11 +52,32 @@
52
52
  background: var(--bg-alt);
53
53
  border: 1px solid var(--border);
54
54
  border-radius: 10px;
55
- padding: 6px 10px;
55
+ padding: 0;
56
56
  font-size: 12px;
57
57
  max-width: 200px;
58
58
  }
59
59
 
60
+ .pasted-chip-open {
61
+ display: inline-flex;
62
+ align-items: center;
63
+ gap: 6px;
64
+ min-width: 0;
65
+ padding: 6px 4px 6px 10px;
66
+ border: 0;
67
+ background: transparent;
68
+ color: inherit;
69
+ cursor: pointer;
70
+ }
71
+
72
+ .pasted-chip-open:hover .pasted-chip-preview,
73
+ .pasted-chip-open:focus-visible .pasted-chip-preview { color: var(--text); }
74
+
75
+ .pasted-chip-open:focus-visible,
76
+ .pasted-chip-remove:focus-visible {
77
+ outline: 2px solid var(--accent);
78
+ outline-offset: 1px;
79
+ }
80
+
60
81
  .pasted-chip-preview {
61
82
  color: var(--text-muted);
62
83
  overflow: hidden;
@@ -87,6 +108,7 @@
87
108
  cursor: pointer;
88
109
  padding: 0;
89
110
  flex-shrink: 0;
111
+ margin-right: 6px;
90
112
  }
91
113
 
92
114
  .pasted-chip-remove .lucide { width: 12px; height: 12px; }
@@ -156,6 +178,59 @@
156
178
  min-height: 0;
157
179
  }
158
180
 
181
+ .paste-modal-editor,
182
+ .paste-modal-footer { display: none; }
183
+
184
+ .paste-modal-editing .paste-modal-body { display: none; }
185
+
186
+ .paste-modal-editing .paste-modal-editor {
187
+ display: block;
188
+ width: 100%;
189
+ min-height: min(52vh, 420px);
190
+ resize: vertical;
191
+ border: 0;
192
+ border-radius: 0;
193
+ outline: 0;
194
+ padding: 16px 20px;
195
+ background: var(--bg);
196
+ color: var(--text-secondary);
197
+ font-family: "Roboto Mono", monospace;
198
+ font-size: 13px;
199
+ line-height: 1.55;
200
+ }
201
+
202
+ .paste-modal-editing .paste-modal-editor:focus {
203
+ box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
204
+ }
205
+
206
+ .paste-modal-editing .paste-modal-footer {
207
+ display: flex;
208
+ justify-content: flex-end;
209
+ gap: 8px;
210
+ padding: 12px 16px;
211
+ border-top: 1px solid var(--border-subtle);
212
+ }
213
+
214
+ .paste-modal-cancel,
215
+ .paste-modal-save {
216
+ border: 1px solid var(--border);
217
+ border-radius: 8px;
218
+ padding: 7px 12px;
219
+ font: inherit;
220
+ cursor: pointer;
221
+ }
222
+
223
+ .paste-modal-cancel {
224
+ background: transparent;
225
+ color: var(--text-secondary);
226
+ }
227
+
228
+ .paste-modal-save {
229
+ border-color: var(--accent);
230
+ background: var(--accent);
231
+ color: var(--accent-text, white);
232
+ }
233
+
159
234
  /* --- File chips (input preview) --- */
160
235
  .file-chip {
161
236
  display: inline-flex;
@@ -286,9 +361,11 @@
286
361
  #context-sources-add {
287
362
  display: inline-flex;
288
363
  align-items: center;
289
- gap: 3px;
364
+ justify-content: center;
365
+ position: relative;
366
+ width: 36px;
290
367
  height: 36px;
291
- padding: 0 10px;
368
+ padding: 0;
292
369
  border-radius: 18px;
293
370
  border: none;
294
371
  background: transparent;
@@ -322,6 +399,9 @@
322
399
  font-size: 10px;
323
400
  font-weight: 700;
324
401
  line-height: 1;
402
+ position: absolute;
403
+ top: 0;
404
+ right: -1px;
325
405
  }
326
406
 
327
407
  .context-chip {
@@ -1270,6 +1350,7 @@
1270
1350
  Attach (files/images -> path injection) and voice input stay available. */
1271
1351
  body.tui-composer-active #schedule-btn,
1272
1352
  body.tui-composer-active #ask-mate-btn,
1353
+ body.tui-composer-active .composer-session-action,
1273
1354
  body.tui-composer-active #active-vendor-indicator,
1274
1355
  body.tui-composer-active #config-chip-wrap {
1275
1356
  display: none !important;
@@ -1,119 +1,34 @@
1
- #header-session-actions-btn {
1
+ .composer-session-action {
2
2
  display: inline-flex;
3
3
  align-items: center;
4
4
  justify-content: center;
5
- gap: 6px;
6
- width: auto;
7
- height: 28px;
5
+ width: 36px;
6
+ height: 36px;
8
7
  margin: 0;
9
- padding: 0 9px;
10
- border: 1px solid var(--border-subtle);
11
- border-radius: 8px;
12
- background: color-mix(in srgb, var(--brand-indigo) 5%, var(--bg));
13
- color: var(--text-secondary);
8
+ padding: 0;
9
+ border: 0;
10
+ border-radius: 50%;
11
+ background: transparent;
12
+ color: var(--text-muted);
14
13
  cursor: pointer;
15
14
  flex-shrink: 0;
16
15
  font: inherit;
17
- font-size: 11px;
18
- font-weight: 650;
19
- white-space: nowrap;
20
- transition: color 0.15s, background 0.15s, border-color 0.15s;
16
+ transition: color 0.15s, background 0.15s, opacity 0.15s;
21
17
  }
22
18
 
23
- #header-session-actions-btn:hover,
24
- #header-session-actions-btn[aria-expanded="true"] {
19
+ .composer-session-action:hover:not(:disabled) {
25
20
  color: var(--text);
26
- background: color-mix(in srgb, var(--brand-indigo) 9%, var(--bg));
27
- border-color: color-mix(in srgb, var(--brand-indigo) 32%, var(--border));
21
+ background: rgba(var(--overlay-rgb), 0.06);
28
22
  }
29
23
 
30
- #header-session-actions-btn:focus { outline: none; }
31
- #header-session-actions-btn:focus-visible {
32
- border-color: color-mix(in srgb, var(--link) 48%, var(--border));
24
+ .composer-session-action:focus { outline: none; }
25
+ .composer-session-action:focus-visible {
33
26
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--link) 18%, transparent);
34
27
  }
35
28
 
36
- #header-session-actions-btn.hidden { display: none; }
37
- #header-session-actions-btn .lucide:first-child {
38
- width: 14px;
39
- height: 14px;
40
- color: color-mix(in srgb, var(--brand-indigo) 78%, var(--text));
41
- }
42
-
43
- #header-session-actions-btn .session-actions-trigger-chevron {
44
- width: 13px;
45
- height: 13px;
46
- transition: transform 0.16s ease;
47
- }
48
-
49
- #header-session-actions-btn[aria-expanded="true"] .session-actions-trigger-chevron {
50
- transform: rotate(180deg);
51
- }
52
-
53
- .session-actions-menu {
54
- position: fixed;
55
- z-index: 10020;
56
- width: min(300px, calc(100vw - 16px));
57
- padding: 6px;
58
- border: 1px solid var(--border);
59
- border-radius: 12px;
60
- background: color-mix(in srgb, var(--bg) 96%, transparent);
61
- box-shadow: 0 14px 38px rgba(var(--shadow-rgb), 0.32), 0 2px 8px rgba(var(--shadow-rgb), 0.2);
62
- backdrop-filter: blur(18px);
63
- }
64
-
65
- .session-actions-row {
66
- width: 100%;
67
- border: 0;
68
- background: transparent;
69
- color: var(--text);
70
- font: inherit;
71
- text-align: left;
72
- cursor: pointer;
73
- }
74
-
75
- .session-actions-row {
76
- display: grid;
77
- grid-template-columns: 30px minmax(0, 1fr) 16px;
78
- align-items: center;
79
- gap: 8px;
80
- min-height: 54px;
81
- padding: 7px 8px;
82
- border-radius: 8px;
83
- }
84
-
85
- .session-actions-row:hover { background: var(--bg-alt); }
86
- .session-actions-row.is-primary {
87
- background: color-mix(in srgb, var(--brand-indigo) 6%, transparent);
88
- }
89
- .session-actions-row.is-primary:hover {
90
- background: color-mix(in srgb, var(--brand-indigo) 10%, transparent);
91
- }
92
- .session-actions-row.is-primary .session-actions-row-icon {
93
- border-color: transparent;
94
- background: var(--brand-indigo);
95
- color: var(--brand-paper);
96
- }
97
- .session-actions-row:disabled { cursor: default; opacity: 0.42; }
98
- .session-actions-row:disabled:hover { background: transparent; }
99
-
100
- .session-actions-row-icon {
101
- display: grid;
102
- place-items: center;
103
- width: 28px;
104
- height: 28px;
105
- border: 1px solid var(--border-subtle);
106
- border-radius: 8px;
107
- background: rgba(var(--overlay-rgb), 0.035);
108
- color: color-mix(in srgb, var(--accent2) 38%, var(--text-secondary));
109
- }
110
-
111
- .session-actions-row-icon .lucide { width: 15px; height: 15px; }
112
- .session-actions-row-copy { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
113
- .session-actions-row-copy strong { font-size: 12px; font-weight: 750; }
114
- .session-actions-row-copy > span { color: var(--text-muted); font-size: 11px; line-height: 1.35; }
115
- .session-actions-row-chevron { color: var(--text-muted); }
116
- .session-actions-row-chevron .lucide { width: 13px; height: 13px; }
29
+ .composer-session-action.hidden { display: none; }
30
+ .composer-session-action:disabled { cursor: default; opacity: 0.32; }
31
+ .composer-session-action .lucide { width: 20px; height: 20px; }
117
32
 
118
33
  .handoff-modal { width: min(420px, calc(100vw - 32px)); }
119
34
  .handoff-modal-desc { margin: -10px 0 14px; color: var(--text-dimmer); font-size: 12px; line-height: 1.45; }
@@ -85,8 +85,6 @@
85
85
  min-height: 80px;
86
86
  border: 1px solid color-mix(in srgb, var(--note-border) calc(var(--note-opacity, 0.64) * 100%), transparent);
87
87
  background: color-mix(in srgb, var(--note-surface) calc(var(--note-opacity, 0.64) * 100%), transparent);
88
- -webkit-backdrop-filter: blur(7px) saturate(0.92) contrast(1.02);
89
- backdrop-filter: blur(7px) saturate(0.92) contrast(1.02);
90
88
  color: var(--note-ink);
91
89
  transition: box-shadow 0.15s;
92
90
  overflow: hidden;
@@ -97,12 +95,12 @@
97
95
  }
98
96
 
99
97
  .sticky-note.sticky-note-attention {
100
- animation: sticky-note-attention-pulse 0.65s ease-in-out 3;
98
+ animation: sticky-note-attention-pulse 1.1s ease-out 1;
101
99
  }
102
100
 
103
101
  @keyframes sticky-note-attention-pulse {
104
- 0%, 100% { box-shadow: 0 2px 12px rgba(var(--shadow-rgb), 0.12); }
105
- 50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 55%, transparent), 0 4px 20px rgba(var(--shadow-rgb), 0.2); }
102
+ 0% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent), 0 4px 20px rgba(var(--shadow-rgb), 0.2); }
103
+ 100% { box-shadow: 0 2px 12px rgba(var(--shadow-rgb), 0.12); }
106
104
  }
107
105
 
108
106
  .sticky-note.dragging,
@@ -800,8 +798,6 @@
800
798
 
801
799
  @media (prefers-reduced-transparency: reduce) {
802
800
  .sticky-note {
803
- -webkit-backdrop-filter: none;
804
- backdrop-filter: none;
805
801
  border-color: var(--note-border);
806
802
  background: var(--note-surface);
807
803
  }
@@ -50,7 +50,7 @@
50
50
  }
51
51
  </style>
52
52
  </head>
53
- <body>
53
+ <body class="capsules-disabled">
54
54
  <div id="layout">
55
55
  <!-- === Top Bar (full width, forms reverse-ㄱ with icon strip) === -->
56
56
  <div id="top-bar">
@@ -422,9 +422,6 @@
422
422
  <div id="ralph-sticky" class="hidden"></div>
423
423
  <div id="debate-sticky" class="hidden"></div>
424
424
  <div class="status">
425
- <button id="header-session-actions-btn" type="button" aria-label="Session actions" aria-haspopup="menu" aria-expanded="false">
426
- <i data-lucide="bot"></i><span>Add Split Worker</span><i class="session-actions-trigger-chevron" data-lucide="chevron-down"></i>
427
- </button>
428
425
  <button type="button" id="header-tui-font-btn" class="header-tui-font-btn hidden" title="Terminal font" aria-label="Terminal font">
429
426
  <i data-lucide="type"></i>
430
427
  </button>
@@ -551,13 +548,15 @@
551
548
  <button id="schedule-btn" type="button" aria-label="Schedule message" title="Schedule message"><i data-lucide="clock"></i></button>
552
549
  <button id="ask-mate-btn" type="button" aria-label="Ask Mate" title="Ask a Mate for advice on this session"><i data-lucide="at-sign"></i></button>
553
550
  <div id="context-sources-btn-wrap" class="desktop-only">
554
- <button id="context-sources-add" type="button" title="Add context sources"><i data-lucide="library"></i><span class="ctx-label">Context</span></button>
551
+ <button id="context-sources-add" type="button" aria-label="Add context" title="Add context sources"><i data-lucide="library"></i></button>
555
552
  <div id="context-sources-picker" class="hidden">
556
553
  <div class="context-picker-section" id="context-picker-email"></div>
557
554
  <div class="context-picker-section" id="context-picker-terminals"></div>
558
555
  <div class="context-picker-section" id="context-picker-tabs"></div>
559
556
  </div>
560
557
  </div>
558
+ <button id="composer-add-worker-btn" type="button" class="composer-session-action desktop-only" aria-label="Add Split Worker" title="Add Split Worker"><i data-lucide="bot"></i></button>
559
+ <button id="composer-handoff-btn" type="button" class="composer-session-action desktop-only" aria-label="Continue in another agent" title="Continue in another agent"><i data-lucide="send"></i></button>
561
560
  </div>
562
561
  <div id="input-bottom-right">
563
562
  <div id="active-vendor-indicator" class="hidden" title="Session vendor">
@@ -1008,6 +1007,7 @@
1008
1007
  <option value="us-appearance">Appearance</option>
1009
1008
  <option value="us-chat">Chat</option>
1010
1009
  <option value="us-mates">Mates</option>
1010
+ <option value="us-experimental">Experimental</option>
1011
1011
  <option value="us-email">Email</option>
1012
1012
  </select>
1013
1013
  <div class="us-modal-nav-items">
@@ -1015,6 +1015,7 @@
1015
1015
  <button class="us-nav-item" data-section="us-appearance"><span>Appearance</span></button>
1016
1016
  <button class="us-nav-item" data-section="us-chat"><span>Chat</span></button>
1017
1017
  <button class="us-nav-item" data-section="us-mates"><span>Mates</span></button>
1018
+ <button class="us-nav-item" data-section="us-experimental"><span>Experimental</span></button>
1018
1019
  <button class="us-nav-item" data-section="us-email"><span>Email</span></button>
1019
1020
  <div class="us-nav-separator"></div>
1020
1021
  <button class="us-nav-item us-nav-danger" data-section="us-logout"><span>Log Out</span></button>
@@ -1170,6 +1171,22 @@
1170
1171
  </div>
1171
1172
  </div>
1172
1173
 
1174
+ <!-- Experimental section -->
1175
+ <div class="us-section" data-section="us-experimental">
1176
+ <h2>Experimental</h2>
1177
+ <p class="settings-hint">Preview features may change or behave unexpectedly.</p>
1178
+ <div class="settings-card">
1179
+ <label class="settings-toggle-row">
1180
+ <div>
1181
+ <span class="settings-label">Capsules</span>
1182
+ <div class="settings-hint">Enable persistent mini-apps that you and your Mates can use.</div>
1183
+ </div>
1184
+ <input type="checkbox" id="us-capsules-enabled">
1185
+ <span class="toggle-track"><span class="toggle-thumb"></span></span>
1186
+ </label>
1187
+ </div>
1188
+ </div>
1189
+
1173
1190
  <!-- Email section -->
1174
1191
  <div class="us-section" data-section="us-email">
1175
1192
  <h2>Email Accounts</h2>
@@ -1526,17 +1543,22 @@
1526
1543
  </div>
1527
1544
  </div>
1528
1545
 
1529
- <div id="paste-modal" class="hidden">
1546
+ <div id="paste-modal" class="hidden" role="dialog" aria-modal="true" aria-labelledby="paste-modal-title">
1530
1547
  <div class="confirm-backdrop"></div>
1531
1548
  <div class="confirm-dialog paste-modal-dialog">
1532
1549
  <div class="paste-modal-header">
1533
- <span class="paste-modal-title">Pasted content</span>
1550
+ <span class="paste-modal-title" id="paste-modal-title">Pasted content</span>
1534
1551
  <div class="paste-modal-actions">
1535
- <button class="paste-modal-copy" title="Copy to clipboard"><i data-lucide="copy" style="width:15px;height:15px"></i></button>
1536
- <button class="paste-modal-close"><i data-lucide="x" style="width:16px;height:16px"></i></button>
1552
+ <button class="paste-modal-copy" type="button" title="Copy to clipboard" aria-label="Copy pasted content"><i data-lucide="copy" style="width:15px;height:15px"></i></button>
1553
+ <button class="paste-modal-close" type="button" aria-label="Close pasted content"><i data-lucide="x" style="width:16px;height:16px"></i></button>
1537
1554
  </div>
1538
1555
  </div>
1539
1556
  <pre class="paste-modal-body" id="paste-modal-body"></pre>
1557
+ <textarea class="paste-modal-editor" id="paste-modal-editor" aria-label="Pasted content"></textarea>
1558
+ <div class="paste-modal-footer" id="paste-modal-footer">
1559
+ <button class="paste-modal-cancel" type="button">Cancel</button>
1560
+ <button class="paste-modal-save" id="paste-modal-save" type="button">Save changes</button>
1561
+ </div>
1540
1562
  </div>
1541
1563
  </div>
1542
1564
 
@@ -6,7 +6,8 @@ import { escapeHtml, copyToClipboard } from './utils.js';
6
6
  import { renderMarkdown, highlightCodeBlocks } from './markdown.js';
7
7
  import { iconHtml, refreshIcons } from './icons.js';
8
8
  import { userAvatarUrl } from './avatar.js';
9
- import { showImageModal, showPasteModal } from './app-misc.js';
9
+ import { showImageModal } from './app-misc.js';
10
+ import { showPasteModal } from './paste-modal.js';
10
11
  import { addToMessages, scrollToBottom, forceScrollToBottom, getMsgTime, shouldGroupMessage, getTurnCounter, setTurnCounter, VENDOR_AVATARS } from './chat-render-runtime.js';
11
12
 
12
13
  export function addUserMessage(text, images, pastes, fromUserId, fromUserName, delegatedMeta) {
@@ -15,7 +15,7 @@ import { updateDmBadge, setMentionActive, renderUserStrip } from './sidebar-mate
15
15
  import { renderSidebarPresence } from './sidebar-presence.js';
16
16
  import { refreshMobileChatSheet } from './sidebar-mobile.js';
17
17
  import { renderMateSessionList, handleMateSearchResults, updateMateSidebarProfile } from './mate-sidebar.js';
18
- import { openHomeChat, handleHomeMateHistory, handleHomeMateDelta, handleHomeMateDone, handleHomeMateError, handleHomeMateSessionsState } from './home-mate-chat.js';
18
+ import { openHomeChat, handleHomeMateHistory, handleHomeMateDelta, handleHomeMateSegment, handleHomeMateDone, handleHomeMateError, handleHomeMateSessionsState } from './home-mate-chat.js';
19
19
  import { handleHomeSurfaceState } from './home-surface.js';
20
20
  import { handleHomeMateMemoryState, handleHomeMateKnowledgeState } from './home-mate-settings.js';
21
21
  import { renderKnowledgeList, handleKnowledgeContent } from './mate-knowledge.js';
@@ -65,7 +65,7 @@ import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateCont
65
65
  import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
66
66
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
67
67
  import { hideHomeHub, showHomeHub } from './app-home-hub.js';
68
- import { handleToolsState, handleToolInstalled, handleToolRemoved, handleToolStorageResult, handleToolLlmResult, handleToolsError, handleToolControlRequest, handleToolServerState, handleToolServerEvent } from './home-tools.js';
68
+ import { handleToolsState, handleToolInstalled, handleToolRemoved, handleToolStorageResult, handleToolLlmResult, handleToolsError, handleToolControlRequest, handleToolServerState, handleToolServerEvent, handleCapsuleGameSession } from './home-tools.js';
69
69
  import { handleHomeDockState } from './home-dock.js';
70
70
  import { handleToolFrameUrlState } from './home-tool-frame.js';
71
71
  import { enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
@@ -640,6 +640,14 @@ export function processMessage(msg) {
640
640
  handleToolFrameUrlState(msg);
641
641
  break;
642
642
 
643
+ case "capsule_game_session":
644
+ handleCapsuleGameSession(msg);
645
+ break;
646
+
647
+ case "home_mate_segment":
648
+ handleHomeMateSegment(msg);
649
+ break;
650
+
643
651
  case "input_sync":
644
652
  if (!store.get('dmMode')) handleInputSync(msg.text);
645
653
  break;