clay-server 4.0.0-beta.8 → 4.0.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 (185) hide show
  1. package/lib/capsule-display-floor.js +51 -0
  2. package/lib/capsule-frame-server.js +255 -0
  3. package/lib/capsule-mate-turn.js +105 -0
  4. package/lib/capsule-pig-logic.js +300 -0
  5. package/lib/capsule-server-runtimes.js +39 -0
  6. package/lib/capsules/pig/display.js +198 -0
  7. package/lib/capsules/pig/manifest.json +9 -0
  8. package/lib/capsules/pig/ui.json +84 -0
  9. package/lib/config.js +25 -1
  10. package/lib/daemon-projects.js +13 -8
  11. package/lib/daemon.js +127 -14
  12. package/lib/git-cli.js +95 -1
  13. package/lib/knowledge-import.js +436 -0
  14. package/lib/knowledge-record-store.js +188 -0
  15. package/lib/knowledge-search.js +240 -0
  16. package/lib/mate-knowledge-mcp-server.js +151 -0
  17. package/lib/mate-knowledge-migration.js +283 -0
  18. package/lib/mate-knowledge-service.js +488 -0
  19. package/lib/mate-knowledge-sync.js +482 -0
  20. package/lib/migrate-single-user.js +2 -0
  21. package/lib/notes-lifecycle.js +149 -0
  22. package/lib/notes.js +60 -3
  23. package/lib/os-users.js +3 -1
  24. package/lib/project-capsule-catalog.js +9 -1
  25. package/lib/project-capsule-turn.js +96 -0
  26. package/lib/project-connection.js +15 -1
  27. package/lib/project-file-history.js +223 -0
  28. package/lib/project-file-watch.js +77 -49
  29. package/lib/project-filesystem.js +31 -220
  30. package/lib/project-http.js +15 -1
  31. package/lib/project-knowledge.js +22 -0
  32. package/lib/project-log-context.js +172 -0
  33. package/lib/project-log-feedback-delivery.js +118 -0
  34. package/lib/project-logs-comments.js +137 -0
  35. package/lib/project-logs-context-state.js +42 -0
  36. package/lib/project-logs-mcp-server.js +328 -0
  37. package/lib/project-logs-query.js +185 -0
  38. package/lib/project-logs-root.js +75 -0
  39. package/lib/project-logs-schema.js +259 -0
  40. package/lib/project-logs-service.js +463 -0
  41. package/lib/project-logs-snapshot.js +205 -0
  42. package/lib/project-logs-store.js +495 -0
  43. package/lib/project-logs-versioning.js +132 -0
  44. package/lib/project-logs.js +400 -0
  45. package/lib/project-mate-interaction.js +14 -0
  46. package/lib/project-mate-knowledge.js +110 -0
  47. package/lib/project-memory.js +22 -0
  48. package/lib/project-message-delivery.js +70 -0
  49. package/lib/project-pair-lifecycle.js +460 -0
  50. package/lib/project-request-access.js +66 -0
  51. package/lib/project-session-handoff.js +1 -1
  52. package/lib/project-session-notes.js +64 -13
  53. package/lib/project-session-pair.js +159 -146
  54. package/lib/project-session-spawn.js +1 -1
  55. package/lib/project-sessions.js +58 -13
  56. package/lib/project-user-message.js +53 -4
  57. package/lib/project-worker-permission.js +430 -0
  58. package/lib/project-worker-proposal.js +208 -62
  59. package/lib/project.js +185 -6
  60. package/lib/public/app.js +63 -35
  61. package/lib/public/css/admin.css +2 -2
  62. package/lib/public/css/capsule-ui.css +38 -0
  63. package/lib/public/css/command-palette.css +19 -9
  64. package/lib/public/css/git-placard.css +154 -0
  65. package/lib/public/css/home-hub.css +4 -0
  66. package/lib/public/css/home-session-actions.css +66 -0
  67. package/lib/public/css/home-sidebar.css +66 -0
  68. package/lib/public/css/input.css +84 -3
  69. package/lib/public/css/messages.css +40 -1
  70. package/lib/public/css/mobile-nav.css +80 -0
  71. package/lib/public/css/notifications-center.css +67 -0
  72. package/lib/public/css/pane.css +97 -10
  73. package/lib/public/css/project-logs.css +198 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/session-actions.css +16 -101
  76. package/lib/public/css/sidebar.css +103 -0
  77. package/lib/public/css/sticky-notes.css +237 -229
  78. package/lib/public/css/worker-proposal.css +36 -3
  79. package/lib/public/index.html +43 -11
  80. package/lib/public/modules/admin.js +8 -8
  81. package/lib/public/modules/app-connection.js +124 -6
  82. package/lib/public/modules/app-home-hub.js +12 -8
  83. package/lib/public/modules/app-message-cards.js +2 -1
  84. package/lib/public/modules/app-messages.js +92 -11
  85. package/lib/public/modules/app-misc.js +2 -14
  86. package/lib/public/modules/app-notifications.js +11 -7
  87. package/lib/public/modules/app-panels.js +3 -2
  88. package/lib/public/modules/app-projects.js +56 -18
  89. package/lib/public/modules/app-rendering.js +1 -0
  90. package/lib/public/modules/capsule-preference.js +44 -0
  91. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  92. package/lib/public/modules/clay-log-links.js +70 -0
  93. package/lib/public/modules/command-palette.js +52 -15
  94. package/lib/public/modules/context-sources.js +0 -3
  95. package/lib/public/modules/git-agent-sessions.js +85 -0
  96. package/lib/public/modules/git-panel.js +111 -84
  97. package/lib/public/modules/git-placard.js +161 -0
  98. package/lib/public/modules/home-chat-empty-state.js +39 -0
  99. package/lib/public/modules/home-chat-identity.js +9 -0
  100. package/lib/public/modules/home-conversations-sheet.js +102 -48
  101. package/lib/public/modules/home-mate-chat.js +15 -35
  102. package/lib/public/modules/home-session-actions.js +6 -0
  103. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  104. package/lib/public/modules/home-sidebar.js +7 -0
  105. package/lib/public/modules/home-surface-boot.js +7 -3
  106. package/lib/public/modules/home-tool-frame.js +182 -0
  107. package/lib/public/modules/home-tools.js +138 -1
  108. package/lib/public/modules/input.js +38 -7
  109. package/lib/public/modules/markdown.js +2 -0
  110. package/lib/public/modules/mate-sidebar.js +0 -8
  111. package/lib/public/modules/message-delivery.js +97 -0
  112. package/lib/public/modules/paste-modal.js +84 -0
  113. package/lib/public/modules/project-activation.js +45 -0
  114. package/lib/public/modules/project-logs-render.js +405 -0
  115. package/lib/public/modules/project-logs.js +461 -0
  116. package/lib/public/modules/project-removal-target.js +26 -0
  117. package/lib/public/modules/scheduler.js +17 -3
  118. package/lib/public/modules/session-actions.js +29 -105
  119. package/lib/public/modules/session-hierarchy.js +54 -0
  120. package/lib/public/modules/sidebar-mobile.js +40 -15
  121. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  122. package/lib/public/modules/sidebar-sessions.js +77 -11
  123. package/lib/public/modules/sidebar.js +17 -5
  124. package/lib/public/modules/split-group-helpers.js +8 -0
  125. package/lib/public/modules/split-pair-ui.js +15 -4
  126. package/lib/public/modules/split-view.js +5 -2
  127. package/lib/public/modules/split-worker-runtime.js +24 -0
  128. package/lib/public/modules/sticky-note-markdown.js +5 -0
  129. package/lib/public/modules/sticky-notes-browser.js +326 -0
  130. package/lib/public/modules/sticky-notes-card.js +360 -0
  131. package/lib/public/modules/sticky-notes-editor.js +291 -0
  132. package/lib/public/modules/sticky-notes-shared.js +90 -0
  133. package/lib/public/modules/sticky-notes.js +170 -946
  134. package/lib/public/modules/thinking-lifecycle.js +175 -0
  135. package/lib/public/modules/thinking-summary.js +28 -0
  136. package/lib/public/modules/thinking-view.js +75 -0
  137. package/lib/public/modules/tool-palette-order.js +143 -0
  138. package/lib/public/modules/tool-palette-overlays.js +159 -0
  139. package/lib/public/modules/tool-palette.js +21 -188
  140. package/lib/public/modules/tools.js +31 -123
  141. package/lib/public/modules/update-snooze.js +169 -0
  142. package/lib/public/modules/user-settings.js +4 -0
  143. package/lib/public/modules/worker-pane-lock.js +176 -0
  144. package/lib/public/modules/worker-proposal-state.js +16 -0
  145. package/lib/public/modules/worker-proposal.js +49 -20
  146. package/lib/public/style.css +3 -1
  147. package/lib/sdk-bridge.js +135 -46
  148. package/lib/sdk-message-processor.js +36 -19
  149. package/lib/server-experimental-settings.js +49 -0
  150. package/lib/server-global-ws.js +11 -0
  151. package/lib/server-home-chat-events.js +31 -1
  152. package/lib/server-home-chat.js +27 -4
  153. package/lib/server-home-clay-entry.js +1 -1
  154. package/lib/server-home-clay-session-links.js +34 -5
  155. package/lib/server-settings.js +5 -0
  156. package/lib/server-tools.js +127 -9
  157. package/lib/server.js +137 -22
  158. package/lib/session-driver-eligibility.js +38 -0
  159. package/lib/session-driver-orchestration.js +31 -0
  160. package/lib/session-hygiene.js +3 -0
  161. package/lib/session-notes-mcp-server.js +25 -5
  162. package/lib/session-pair-factory.js +247 -0
  163. package/lib/session-pair-mcp-server.js +46 -8
  164. package/lib/session-pair-prompts.js +79 -0
  165. package/lib/session-pair-turn-control.js +153 -0
  166. package/lib/session-provenance.js +119 -0
  167. package/lib/session-spawn-mcp-server.js +1 -1
  168. package/lib/session-split-groups.js +4 -1
  169. package/lib/session-title-policy.js +60 -0
  170. package/lib/session-visibility.js +41 -0
  171. package/lib/sessions.js +57 -10
  172. package/lib/tools-registry.js +89 -10
  173. package/lib/update-snooze.js +437 -0
  174. package/lib/users-experimental-preferences.js +30 -0
  175. package/lib/users-permissions.js +6 -6
  176. package/lib/users.js +22 -16
  177. package/lib/workspace-query-access.js +102 -0
  178. package/lib/workspace-query-service.js +24 -17
  179. package/lib/worktree.js +25 -38
  180. package/lib/ws-schema.js +38 -7
  181. package/lib/yoke/adapters/claude-worker.js +16 -6
  182. package/lib/yoke/adapters/claude.js +12 -0
  183. package/lib/yoke/adapters/codex.js +24 -14
  184. package/lib/yoke/vendor-registry.js +1 -1
  185. package/package.json +2 -2
@@ -1,6 +1,9 @@
1
1
  var usersModule = require("./users");
2
2
  var yoke = require("./yoke");
3
3
  var backgroundTaskTiming = require("./background-task-timing");
4
+ var sessionTitlePolicy = require("./session-title-policy");
5
+
6
+ var OVERLOAD_RETRY_LIMIT = 3;
4
7
 
5
8
  function attachMessageProcessor(ctx) {
6
9
  var sm = ctx.sm;
@@ -23,8 +26,6 @@ function attachMessageProcessor(ctx) {
23
26
  var saveImageFile = ctx.saveImageFile;
24
27
  var getSessionLinuxUser = ctx.getSessionLinuxUser || function () { return null; };
25
28
 
26
- var AUTO_TITLE_TURN_THRESHOLD = 2;
27
-
28
29
  function getMateIdForNotification() {
29
30
  if (!isMate) return null;
30
31
  if (typeof slug === "string" && slug.indexOf("mate-") === 0) {
@@ -61,19 +62,7 @@ function attachMessageProcessor(ctx) {
61
62
  if (session._lastAuthEmit && (now - session._lastAuthEmit) < 5000) return;
62
63
  session._lastAuthEmit = now;
63
64
 
64
- // Codex can leave a transient 401 in its app-server event stream after a
65
- // successful device login. Confirm the CLI's current credential state
66
- // before interrupting another session with a login modal.
67
65
  var vendor = session.vendor || "claude";
68
- yoke.invalidateAuthCache();
69
- var authState = yoke.checkAuth();
70
- if (authState[vendor]) {
71
- sendAndRecord(session, {
72
- type: "error",
73
- text: "Authentication is available, but " + vendor + " reported an auth-like error. Try again.",
74
- });
75
- return;
76
- }
77
66
 
78
67
  var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
79
68
  var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
@@ -500,6 +489,8 @@ function attachMessageProcessor(ctx) {
500
489
  }
501
490
 
502
491
  } else if (parsed.yokeType === "result") {
492
+ var overloadAbortRequested = !!session._overloadAbortRequested;
493
+ session._overloadAbortRequested = false;
503
494
  session.blocks = {};
504
495
  session.sentToolResults = {};
505
496
  session.pendingPermissions = {};
@@ -545,7 +536,9 @@ function attachMessageProcessor(ctx) {
545
536
  session._awaitingTurnResult = false;
546
537
  session._queuedTurnCount = 0;
547
538
  onProcessingChanged();
548
- sendAndRecord(session, { type: "error", text: "Claude error: " + execError });
539
+ if (!overloadAbortRequested) {
540
+ sendAndRecord(session, { type: "error", text: "Claude error: " + execError });
541
+ }
549
542
  sendAndRecord(session, { type: "done", code: 1 });
550
543
  sm.broadcastSessionList();
551
544
  return;
@@ -637,7 +630,7 @@ function attachMessageProcessor(ctx) {
637
630
  sm.broadcastSessionList();
638
631
 
639
632
  // Auto-generate title after N turns (skip if loop or already auto-generated)
640
- if (session.turnCount === AUTO_TITLE_TURN_THRESHOLD
633
+ if (session.turnCount === sessionTitlePolicy.generationTurn(isMate)
641
634
  && !session.titleAutoGenerated
642
635
  && !session.titleManuallySet
643
636
  && !session.loop
@@ -832,9 +825,33 @@ function attachMessageProcessor(ctx) {
832
825
  }
833
826
 
834
827
  } else if (parsed.yokeType === "api_retry") {
835
- // Transient retry notification, show in UI but don't persist in history
836
- var retryText = parsed.message || parsed.error || "Retrying API request...";
837
- sendToSession(session, { type: "system_info", text: retryText });
828
+ // The Claude SDK may otherwise paint one persistent red error for every
829
+ // retry and remain busy for many minutes. Show one transient status, and
830
+ // stop a repeatedly overloaded turn after a small bounded number of
831
+ // attempts so the user regains control and can retry or change models.
832
+ var apiRetryAttempt = parsed.attempt || 1;
833
+ if (parsed.error === "overloaded") {
834
+ if (apiRetryAttempt === 1) {
835
+ session._overloadAbortRequested = false;
836
+ sendToSession(session, {
837
+ type: "system_info",
838
+ text: "The model is overloaded. Retrying briefly...",
839
+ });
840
+ }
841
+ if (apiRetryAttempt >= OVERLOAD_RETRY_LIMIT && !session._overloadAbortRequested) {
842
+ session._overloadAbortRequested = true;
843
+ sendAndRecord(session, {
844
+ type: "error",
845
+ text: "The model remained overloaded after " + apiRetryAttempt +
846
+ " attempts, so this turn was stopped. Try again or choose another model.",
847
+ });
848
+ if (session.abortController && typeof session.abortController.abort === "function") {
849
+ try { session.abortController.abort(); } catch (e) {}
850
+ }
851
+ }
852
+ } else if (apiRetryAttempt === 1) {
853
+ sendToSession(session, { type: "system_info", text: "The model request failed temporarily. Retrying..." });
854
+ }
838
855
 
839
856
  } else if (parsed.yokeType === "commands_changed") {
840
857
  // Mid-session command list change: rebuild the union with skills (same as
@@ -0,0 +1,49 @@
1
+ function attachExperimentalSettings(ctx) {
2
+ var users = ctx.users;
3
+ var getMultiUserFromReq = ctx.getMultiUserFromReq;
4
+ var opts = ctx.opts;
5
+
6
+ function handleRequest(req, res, fullUrl) {
7
+ if (req.method !== "PUT" || fullUrl !== "/api/user/capsules-enabled") return false;
8
+ var isMultiUser = users.isMultiUser();
9
+ var mu = getMultiUserFromReq(req);
10
+ if (isMultiUser && !mu) {
11
+ res.writeHead(401, { "Content-Type": "application/json" });
12
+ res.end('{"error":"unauthorized"}');
13
+ return true;
14
+ }
15
+ var body = "";
16
+ req.on("data", function (chunk) { body += chunk; });
17
+ req.on("end", function () {
18
+ try {
19
+ var data = JSON.parse(body);
20
+ var result;
21
+ var userId = "default";
22
+ if (isMultiUser) {
23
+ userId = mu.id;
24
+ result = users.setCapsulesEnabled(userId, data.enabled === true);
25
+ } else if (typeof opts.onSetCapsulesEnabled === "function") {
26
+ result = opts.onSetCapsulesEnabled(data.enabled === true);
27
+ } else {
28
+ result = { ok: true, capsulesEnabled: false };
29
+ }
30
+ if (result.error) {
31
+ res.writeHead(400, { "Content-Type": "application/json" });
32
+ res.end(JSON.stringify({ error: result.error }));
33
+ return;
34
+ }
35
+ if (typeof ctx.onCapsulesPreferenceChanged === "function") ctx.onCapsulesPreferenceChanged(userId);
36
+ res.writeHead(200, { "Content-Type": "application/json" });
37
+ res.end(JSON.stringify({ ok: true, capsulesEnabled: result.capsulesEnabled === true }));
38
+ } catch (e) {
39
+ res.writeHead(400, { "Content-Type": "application/json" });
40
+ res.end('{"error":"Invalid request"}');
41
+ }
42
+ });
43
+ return true;
44
+ }
45
+
46
+ return { handleRequest: handleRequest };
47
+ }
48
+
49
+ module.exports = { attachExperimentalSettings: attachExperimentalSettings };
@@ -7,6 +7,7 @@
7
7
  // Bound to the slug-less `/ws` endpoint by lib/server.js. Only handles the
8
8
  // small set of messages needed to bootstrap into a project context:
9
9
  // - ping -> pong keep-alive
10
+ // - Home and Mate -> the same user-scoped handlers as project sockets
10
11
  // - browse_dir -> directory picker for the add-project modal
11
12
  // - add_project -> register an existing directory
12
13
  // - create_project -> make a new empty project
@@ -37,6 +38,8 @@ function attachGlobalWs(opts) {
37
38
  var onAddProject = opts.onAddProject;
38
39
  var onCreateProject = opts.onCreateProject;
39
40
  var onCloneProject = opts.onCloneProject;
41
+ var onHomePreferenceMessage = opts.onHomePreferenceMessage;
42
+ var onAppMessage = opts.onAppMessage;
40
43
 
41
44
  function handleMessage(ws, msg) {
42
45
  if (!msg || typeof msg !== "object") return;
@@ -46,6 +49,14 @@ function attachGlobalWs(opts) {
46
49
  return;
47
50
  }
48
51
 
52
+ // A user does not need an ordinary project before Home becomes useful.
53
+ // Route Mate discovery, Home chat/debates, and tools through the same
54
+ // handlers used by project sockets. This also lets mate_list repair an
55
+ // existing account whose built-ins were missing from its final storage.
56
+ if (typeof onAppMessage === "function" && onAppMessage(ws, msg)) return;
57
+
58
+ if (typeof onHomePreferenceMessage === "function" && onHomePreferenceMessage(ws, msg)) return;
59
+
49
60
  // --- Directory picker for the add-project modal ---
50
61
  if (msg.type === "browse_dir") {
51
62
  var rawPath = (msg.path || "").replace(/^~/, config.REAL_HOME);
@@ -134,9 +134,24 @@ function historyToHomeChat(history, debateSetupMode, mateCreationMode) {
134
134
  applyDebateHistoryEvent(messages, event);
135
135
  continue;
136
136
  }
137
+ if (event.type === "capsule_turn") {
138
+ // A Capsule engagement delivery. The transcript shows a short system
139
+ // note; the full delivery prompt is model-facing, not conversation.
140
+ flushAssistant();
141
+ messages.push({
142
+ role: "capsule_turn",
143
+ text: (event.toolName || event.toolId || "Capsule") + (event.kind === "start" ? ": a new game started." : ": your Mate is taking its turn."),
144
+ });
145
+ continue;
146
+ }
137
147
  if (event.type === "user_message" && event.text && event.askUserAnswer !== true && !interviewMode) {
138
148
  flushAssistant();
139
149
  messages.push({ role: "user", text: event.text });
150
+ } else if (!interviewMode && (event.type === "tool_start" || event.type === "tool_executing" || event.type === "tool_result")) {
151
+ // A tool call ends the current spoken burst. Without this flush the
152
+ // text on both sides of the call concatenates into one run-on bubble,
153
+ // which is exactly how a Capsule game transcript turns into a wall.
154
+ flushAssistant();
140
155
  } else if (event.type === "delta" && typeof event.text === "string" && !interviewMode) {
141
156
  pending += event.text;
142
157
  } else if (event.type === "result" || event.type === "done") {
@@ -185,13 +200,17 @@ function historyToHomeChat(history, debateSetupMode, mateCreationMode) {
185
200
  return messages;
186
201
  }
187
202
 
203
+ // The authoritative text for the bubble being finalized: only the deltas
204
+ // since the last conversational boundary. Tool calls and Capsule turn
205
+ // deliveries are boundaries too, or a session that is never driven by typed
206
+ // user messages (a Capsule game) would collapse into one run-on bubble.
188
207
  function latestAssistantText(history) {
189
208
  var text = "";
190
209
  for (var i = (history || []).length - 1; i >= 0; i--) {
191
210
  var event = history[i];
192
211
  if (!event) continue;
193
212
  if (event.type === "delta" && typeof event.text === "string") text = event.text + text;
194
- if (event.type === "user_message" || event.type === "debate_proposal" || event.type === "debate_proposal_resolved" || event.type === "project_assignment_proposal" || event.type === "project_assignment_status" || (event.type === "tool_executing" && event.name === "AskUserQuestion")) break;
213
+ if (event.type === "user_message" || event.type === "capsule_turn" || event.type === "tool_start" || event.type === "tool_executing" || event.type === "tool_result" || event.type === "debate_proposal" || event.type === "debate_proposal_resolved" || event.type === "project_assignment_proposal" || event.type === "project_assignment_status") break;
195
214
  }
196
215
  return text;
197
216
  }
@@ -209,6 +228,9 @@ function searchToolLabel(name, input) {
209
228
  if (name === "list_project_sessions") return "Reviewing project conversations";
210
229
  if (name === "list_projects") return "Reviewing your projects";
211
230
  if (name === "search_project_history") return query ? "Searching a project for \u201c" + query + "\u201d" : "Searching a project";
231
+ if (name === "search_project_logs") return query ? "Searching project logs for \u201c" + query + "\u201d" : "Searching project logs";
232
+ if (name === "list_project_logs") return "Reviewing project logs";
233
+ if (name === "read_project_log" || name === "read_project_log_revision" || name === "project_log_history") return "Reading a project log";
212
234
  return "Using " + (cleanActivityText(name, 64) || "a workspace tool");
213
235
  }
214
236
 
@@ -281,6 +303,14 @@ function transformEvent(event, mateId, session, requestId, stableSessionId) {
281
303
  if (session && session.mateCreationMode === true && event.type === "tool_executing" && event.name === "AskUserQuestion" && event.id && event.input) return Object.assign({ type: "home_mate_creation_question", toolId: event.id, questions: homeDebateQuestions(event.input) }, metadata);
282
304
  if (session && session.mateCreationMode === true && event.type === "ask_user_answered" && event.toolId) return Object.assign({ type: "home_mate_creation_question_resolved", toolId: event.toolId, status: "answered", answers: event.answers || null }, metadata);
283
305
  if (session && session.mateCreationMode === true && event.type === "home_mate_creation_question_expired" && event.toolId) return Object.assign({ type: "home_mate_creation_question_resolved", toolId: event.toolId, status: "expired", error: event.error || "This question expired. Ask Clay to repeat it." }, metadata);
306
+ if ((event.type === "tool_start" || event.type === "tool_executing") && event.name !== "AskUserQuestion") {
307
+ // A tool call ends the current spoken burst: the client seals the bubble
308
+ // it has streamed so far, and later text starts a fresh one. Without this
309
+ // a session driven by tool play (a Capsule game) streams into one
310
+ // ever-growing run-on bubble. Last so every special surface above (search
311
+ // activity, debates, interviews, AskUserQuestion) keeps precedence.
312
+ return Object.assign({ type: "home_mate_segment" }, metadata);
313
+ }
284
314
  return null;
285
315
  }
286
316
 
@@ -9,6 +9,8 @@ var attachHomeMateCreation = require("./server-home-mate-creation").attachHomeMa
9
9
  var attachHomeDebates = require("./server-home-debates").attachHomeDebates, attachHomeClayEntry = require("./server-home-clay-entry").attachHomeClayEntry;
10
10
  var homeChatEvents = require("./server-home-chat-events");
11
11
  var homeCapsuleCreation = require("./server-home-capsule-creation");
12
+ var sessionTitlePolicy = require("./session-title-policy");
13
+ var sessionProvenance = require("./session-provenance");
12
14
  var historyToHomeChat = homeChatEvents.historyToHomeChat;
13
15
  var transformEvent = homeChatEvents.transformEvent;
14
16
  function attachHomeChat(deps) {
@@ -82,8 +84,18 @@ function attachHomeChat(deps) {
82
84
  var sessionManager = found.ctx.getSessionManager();
83
85
  if (!sessionManager) return [];
84
86
  var result = [];
87
+ var visibleSessions = [];
85
88
  sessionManager.sessions.forEach(function (session) {
86
89
  if (!ownsSession(session, userId)) return;
90
+ visibleSessions.push(session);
91
+ });
92
+ var hierarchy = sessionProvenance.hierarchyFor(visibleSessions);
93
+ for (var vi = 0; vi < visibleSessions.length; vi++) {
94
+ var session = visibleSessions[vi];
95
+ if (sessionTitlePolicy.repairLegacySearchTitle(session)
96
+ && typeof sessionManager.saveSessionFile === "function") {
97
+ sessionManager.saveSessionFile(session);
98
+ }
87
99
  var cliSessionId = sessionString(session.cliSessionId, 512);
88
100
  var createdAt = sessionTimestamp(session.createdAt);
89
101
  var lastActivity = sessionTimestamp(session.lastActivity) || createdAt;
@@ -97,12 +109,16 @@ function attachHomeChat(deps) {
97
109
  createdAt: createdAt,
98
110
  lastActivity: lastActivity,
99
111
  isProcessing: !!session.isProcessing,
112
+ sessionRole: hierarchy[session.localId].role,
113
+ parentSessionId: hierarchy[session.localId].parentSessionId == null ? null : sessionReference(sessionManager.sessions.get(hierarchy[session.localId].parentSessionId)),
114
+ parentAvailable: hierarchy[session.localId].parentAvailable,
115
+ workerGeneration: hierarchy[session.localId].generation,
100
116
  });
101
117
  if (session.debateSetupMode === true) result[result.length - 1].debatePlanning = true;
102
118
  if (session.homeDebatePhase) result[result.length - 1].debatePhase = session.homeDebatePhase;
103
119
  if (session.mateCreationMode === true) result[result.length - 1].mateCreation = true;
104
120
  if (session.homeMateCreationPhase) result[result.length - 1].mateCreationPhase = session.homeMateCreationPhase;
105
- });
121
+ }
106
122
  result.sort(function (a, b) { return b.lastActivity - a.lastActivity; });
107
123
  return result;
108
124
  }
@@ -188,7 +204,12 @@ function attachHomeChat(deps) {
188
204
 
189
205
  var homeModels = attachHomeModels({ users: users, mates: mates, projects: projects, sendMessage: sendMessage });
190
206
  var homeDebates = attachHomeDebates({ mates: mates, findMateProject: findMateProject, ownsSession: ownsSession, sessionReference: sessionReference, sendMessage: sendMessage });
191
- var homeClaySessionLinks = attachHomeClaySessionLinks({ projects: projects, workspaceQueryService: deps.workspaceQueryService, sendMessage: sendMessage });
207
+ var homeClaySessionLinks = attachHomeClaySessionLinks({
208
+ projects: projects,
209
+ workspaceQueryService: deps.workspaceQueryService,
210
+ projectLogsService: deps.projectLogsService,
211
+ sendMessage: sendMessage,
212
+ });
192
213
 
193
214
  function sendHistory(ws, found, session, requestId) {
194
215
  if (ws.readyState !== 1) return;
@@ -367,7 +388,7 @@ function attachHomeChat(deps) {
367
388
 
368
389
  function handleMessage(ws, msg) {
369
390
  if (!msg || typeof msg.type !== "string") return false;
370
- if (msg.type !== "home_debates_list" && msg.type !== "home_clay_ask" && msg.type !== "home_clay_session_resolve" && 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_debate_question_response" && msg.type !== "home_debate_control" && msg.type !== "home_mate_creation_plan" && msg.type !== "home_mate_creation_question_response" && 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") {
391
+ if (msg.type !== "home_debates_list" && msg.type !== "home_clay_ask" && msg.type !== "home_clay_session_resolve" && msg.type !== "home_clay_log_resolve" && 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_debate_question_response" && msg.type !== "home_debate_control" && msg.type !== "home_mate_creation_plan" && msg.type !== "home_mate_creation_question_response" && 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") {
371
392
  return false;
372
393
  }
373
394
 
@@ -386,6 +407,7 @@ function attachHomeChat(deps) {
386
407
  if (users.isMultiUser() && !userId) {
387
408
  if (msg.type === "home_debates_list") sendMessage(ws, { type: "home_debates_state", requestId: msg.requestId || null, status: "error", debates: [], error: "Sign in to load your debates." });
388
409
  else if (msg.type === "home_clay_session_resolve") sendMessage(ws, { type: "home_clay_session_target", requestId: msg.requestId || null, sessionRef: msg.sessionRef || null, status: "error", error: "Sign in to open this session." });
410
+ else if (msg.type === "home_clay_log_resolve") sendMessage(ws, { type: "home_clay_log_target", requestId: msg.requestId || null, ref: msg.ref || null, status: "error", error: "Sign in to open this log." });
389
411
  else if (msg.type === "home_mate_models_get" || msg.type === "home_mate_model_set") homeModels.sendAccessError(ws, msg, "Not authenticated.");
390
412
  else sendError(ws, msg.mateId || null, "Not authenticated.", msg.requestId || null, msg.sessionId || null);
391
413
  return true;
@@ -394,6 +416,7 @@ function attachHomeChat(deps) {
394
416
  if (homeDebates.handle(ws, userId, msg)) return true;
395
417
  if (msg.type === "home_clay_ask") { homeClayEntry.start(ws, userId, msg); return true; }
396
418
  if (msg.type === "home_clay_session_resolve") { homeClaySessionLinks.resolve(ws, msg); return true; }
419
+ if (msg.type === "home_clay_log_resolve") { homeClaySessionLinks.resolveLog(ws, msg); return true; }
397
420
 
398
421
  if (msg.type === "home_mate_debate_plan") {
399
422
  homeDebate.start(ws, userId, msg);
@@ -513,6 +536,6 @@ function attachHomeChat(deps) {
513
536
  teardownTap(ws);
514
537
  teardownTap(ws, "_searchClayTap");
515
538
  }
516
- return { handleMessage: handleMessage, handleDisconnection: handleDisconnection };
539
+ return { handleMessage: handleMessage, handleDisconnection: handleDisconnection, findMateProject: findMateProject };
517
540
  }
518
541
  module.exports = { attachHomeChat: attachHomeChat };
@@ -2,7 +2,7 @@
2
2
 
3
3
  function attachHomeClayEntry(ctx) {
4
4
  function providerPrompt(text) {
5
- return "This conversation began in Clay Studio's global search. Respond as Clay. When the user is trying to locate prior work, use search_workspace_history before any other investigation. Search by meaning, related terms, and context, using at most three focused search passes. Do not use Bash, filesystem scans, or generic agents to search Clay conversation history. Give the user useful findings promptly, and do not mention tools or internal search mechanics. Do not force a workspace search when the request is a general question or task.\n\nUser request:\n" + text;
5
+ return "This conversation began in Clay Studio's global search. Respond as Clay. When the user is trying to locate a conversation, use search_workspace_history before any other investigation. When the user asks about a project's prior work, decisions, defects, status, rationale, or unfinished work, identify the project and search_project_logs before answering; use conversation history as additional evidence when useful. Search by meaning, related terms, and context, using at most three focused search passes. Do not use Bash, filesystem scans, or generic agents to search Clay history. Give the user useful findings promptly, and do not mention tools or internal search mechanics. Do not force a workspace or Logs search when the request is a general question or task.\n\nUser request:\n" + text;
6
6
  }
7
7
 
8
8
  function findExisting(manager, userId, requestId) {
@@ -1,13 +1,19 @@
1
- // Resolve rendered Mate session references through an exact owner-bound source session.
1
+ // Resolve rendered Mate session and Project Log references through an exact
2
+ // owner-bound source session.
2
3
 
3
4
  function attachHomeClaySessionLinks(ctx) {
4
- function resolve(ws, msg) {
5
+ function source(ws, msg) {
5
6
  var tap = msg.surface === "search" ? ws._searchClayTap : ws._homeChatTap;
6
7
  var sourceProject = tap && tap.mateSlug ? ctx.projects.get(tap.mateSlug) : null;
7
8
  var sourceManager = sourceProject && sourceProject.getSessionManager ? sourceProject.getSessionManager() : null;
8
9
  var sourceSession = sourceManager && tap ? sourceManager.sessions.get(tap.sessionId) : null;
9
- var bound = ctx.workspaceQueryService && sourceSession
10
- ? ctx.workspaceQueryService.bindProjectSession({ projectSlug: tap.mateSlug, session: sourceSession })
10
+ return { tap: tap, project: sourceProject, session: sourceSession };
11
+ }
12
+
13
+ function resolve(ws, msg) {
14
+ var selected = source(ws, msg);
15
+ var bound = ctx.workspaceQueryService && selected.session
16
+ ? ctx.workspaceQueryService.bindProjectSession({ projectSlug: selected.tap.mateSlug, session: selected.session })
11
17
  : null;
12
18
  var payload = { type: "home_clay_session_target", requestId: msg.requestId || null, sessionRef: msg.sessionRef || null, status: "error" };
13
19
  try {
@@ -20,7 +26,30 @@ function attachHomeClaySessionLinks(ctx) {
20
26
  ctx.sendMessage(ws, payload);
21
27
  }
22
28
 
23
- return { resolve: resolve };
29
+ function resolveLog(ws, msg) {
30
+ var selected = source(ws, msg);
31
+ var status = selected.project && selected.project.getStatus ? selected.project.getStatus() : null;
32
+ var bound = ctx.projectLogsService && selected.session && status
33
+ ? ctx.projectLogsService.bindMate({
34
+ projectSlug: selected.tap.mateSlug,
35
+ projectOwnerId: status.projectOwnerId || null,
36
+ isMate: true,
37
+ mateId: selected.tap.mateId,
38
+ session: selected.session,
39
+ })
40
+ : null;
41
+ var payload = { type: "home_clay_log_target", requestId: msg.requestId || null, ref: msg.ref || null, status: "error" };
42
+ try {
43
+ if (!bound) throw new Error("The source conversation is no longer available.");
44
+ payload.target = bound.resolveLogNavigation({ ref: msg.ref });
45
+ payload.status = "ready";
46
+ } catch (error) {
47
+ payload.error = error.message || "Log not available.";
48
+ }
49
+ ctx.sendMessage(ws, payload);
50
+ }
51
+
52
+ return { resolve: resolve, resolveLog: resolveLog };
24
53
  }
25
54
 
26
55
  module.exports = { attachHomeClaySessionLinks: attachHomeClaySessionLinks };
@@ -1,5 +1,6 @@
1
1
  var fs = require("fs");
2
2
  var path = require("path");
3
+ var { attachExperimentalSettings } = require("./server-experimental-settings");
3
4
 
4
5
  function attachSettings(ctx) {
5
6
  var users = ctx.users;
@@ -10,8 +11,10 @@ function attachSettings(ctx) {
10
11
  var CONFIG_DIR = ctx.CONFIG_DIR;
11
12
 
12
13
  var profilePath = path.join(CONFIG_DIR, "profile.json");
14
+ var experimentalSettings = attachExperimentalSettings(ctx);
13
15
 
14
16
  function handleRequest(req, res, fullUrl) {
17
+ if (experimentalSettings.handleRequest(req, res, fullUrl)) return true;
15
18
  // GET /api/profile
16
19
  if (req.method === "GET" && fullUrl === "/api/profile") {
17
20
  if (users.isMultiUser()) {
@@ -30,6 +33,7 @@ function attachSettings(ctx) {
30
33
  profile.chatLayout = mu.chatLayout || "channel";
31
34
  profile.mateOnboardingShown = !!mu.mateOnboardingShown;
32
35
  profile.matesEnabled = mu.matesEnabled === true;
36
+ profile.capsulesEnabled = users.getCapsulesEnabled(mu.id);
33
37
  try { profile.terminalFont = users.getTerminalFont(mu.id); } catch (e) {}
34
38
  res.writeHead(200, { "Content-Type": "application/json" });
35
39
  res.end(JSON.stringify(profile));
@@ -53,6 +57,7 @@ function attachSettings(ctx) {
53
57
  profile.chatLayout = dc.chatLayout || "channel";
54
58
  profile.mateOnboardingShown = !!dc.mateOnboardingShown;
55
59
  profile.matesEnabled = dc.matesEnabled === true;
60
+ profile.capsulesEnabled = dc.capsulesEnabled === true;
56
61
  if (dc.terminalFont && typeof dc.terminalFont === "object") {
57
62
  profile.terminalFont = {
58
63
  family: dc.terminalFont.family || "'SF Mono', Menlo, Monaco, 'Courier New', monospace",