clay-server 4.0.0-beta.2 → 4.0.0-beta.21

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 (177) hide show
  1. package/lib/background-task-timing.js +105 -0
  2. package/lib/capsule-display-floor.js +51 -0
  3. package/lib/capsule-frame-server.js +255 -0
  4. package/lib/capsule-mate-turn.js +105 -0
  5. package/lib/capsule-pig-logic.js +300 -0
  6. package/lib/capsule-server-runtimes.js +39 -0
  7. package/lib/capsules/pig/display.js +198 -0
  8. package/lib/capsules/pig/manifest.json +9 -0
  9. package/lib/capsules/pig/ui.json +84 -0
  10. package/lib/config.js +25 -1
  11. package/lib/daemon-projects.js +13 -8
  12. package/lib/daemon.js +127 -14
  13. package/lib/git-cli.js +95 -1
  14. package/lib/knowledge-import.js +436 -0
  15. package/lib/knowledge-record-store.js +188 -0
  16. package/lib/knowledge-search.js +240 -0
  17. package/lib/mate-knowledge-mcp-server.js +151 -0
  18. package/lib/mate-knowledge-migration.js +283 -0
  19. package/lib/mate-knowledge-service.js +488 -0
  20. package/lib/mate-knowledge-sync.js +482 -0
  21. package/lib/migrate-single-user.js +2 -0
  22. package/lib/notes-lifecycle.js +149 -0
  23. package/lib/notes.js +60 -3
  24. package/lib/os-users.js +3 -1
  25. package/lib/project-capsule-catalog.js +9 -1
  26. package/lib/project-capsule-turn.js +96 -0
  27. package/lib/project-connection.js +14 -1
  28. package/lib/project-http.js +15 -1
  29. package/lib/project-knowledge.js +22 -0
  30. package/lib/project-log-context.js +172 -0
  31. package/lib/project-log-feedback-delivery.js +118 -0
  32. package/lib/project-logs-comments.js +137 -0
  33. package/lib/project-logs-context-state.js +42 -0
  34. package/lib/project-logs-mcp-server.js +328 -0
  35. package/lib/project-logs-query.js +185 -0
  36. package/lib/project-logs-root.js +75 -0
  37. package/lib/project-logs-schema.js +259 -0
  38. package/lib/project-logs-service.js +463 -0
  39. package/lib/project-logs-snapshot.js +205 -0
  40. package/lib/project-logs-store.js +495 -0
  41. package/lib/project-logs-versioning.js +132 -0
  42. package/lib/project-logs.js +400 -0
  43. package/lib/project-mate-interaction.js +14 -0
  44. package/lib/project-mate-knowledge.js +110 -0
  45. package/lib/project-memory.js +22 -0
  46. package/lib/project-message-delivery.js +70 -0
  47. package/lib/project-pair-lifecycle.js +460 -0
  48. package/lib/project-session-handoff.js +1 -1
  49. package/lib/project-session-notes.js +64 -13
  50. package/lib/project-session-pair.js +159 -146
  51. package/lib/project-session-spawn.js +1 -1
  52. package/lib/project-sessions.js +58 -13
  53. package/lib/project-user-message.js +53 -4
  54. package/lib/project-vendor-login.js +20 -3
  55. package/lib/project-worker-permission.js +430 -0
  56. package/lib/project-worker-proposal.js +208 -62
  57. package/lib/project.js +178 -6
  58. package/lib/public/app.js +58 -35
  59. package/lib/public/css/admin.css +2 -2
  60. package/lib/public/css/capsule-ui.css +38 -0
  61. package/lib/public/css/command-palette.css +19 -9
  62. package/lib/public/css/git-placard.css +154 -0
  63. package/lib/public/css/home-hub.css +4 -0
  64. package/lib/public/css/home-session-actions.css +66 -0
  65. package/lib/public/css/home-sidebar.css +66 -0
  66. package/lib/public/css/input.css +163 -28
  67. package/lib/public/css/mobile-nav.css +80 -0
  68. package/lib/public/css/notifications-center.css +67 -0
  69. package/lib/public/css/pane.css +52 -0
  70. package/lib/public/css/project-logs.css +198 -0
  71. package/lib/public/css/scheduler.css +1 -1
  72. package/lib/public/css/session-actions.css +16 -101
  73. package/lib/public/css/sidebar.css +103 -0
  74. package/lib/public/css/sticky-notes.css +237 -229
  75. package/lib/public/css/worker-proposal.css +34 -1
  76. package/lib/public/index.html +45 -11
  77. package/lib/public/modules/admin.js +8 -8
  78. package/lib/public/modules/app-connection.js +109 -4
  79. package/lib/public/modules/app-home-hub.js +19 -7
  80. package/lib/public/modules/app-message-cards.js +2 -1
  81. package/lib/public/modules/app-messages.js +84 -7
  82. package/lib/public/modules/app-misc.js +2 -14
  83. package/lib/public/modules/app-notifications.js +11 -7
  84. package/lib/public/modules/app-panels.js +46 -4
  85. package/lib/public/modules/app-projects.js +13 -8
  86. package/lib/public/modules/app-rendering.js +1 -0
  87. package/lib/public/modules/background-tasks-ui.js +139 -16
  88. package/lib/public/modules/capsule-preference.js +44 -0
  89. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  90. package/lib/public/modules/clay-log-links.js +70 -0
  91. package/lib/public/modules/command-palette.js +52 -15
  92. package/lib/public/modules/context-sources.js +0 -3
  93. package/lib/public/modules/git-agent-sessions.js +85 -0
  94. package/lib/public/modules/git-panel.js +111 -84
  95. package/lib/public/modules/git-placard.js +161 -0
  96. package/lib/public/modules/home-chat-empty-state.js +39 -0
  97. package/lib/public/modules/home-chat-identity.js +9 -0
  98. package/lib/public/modules/home-conversations-sheet.js +102 -48
  99. package/lib/public/modules/home-mate-chat.js +15 -35
  100. package/lib/public/modules/home-session-actions.js +6 -0
  101. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  102. package/lib/public/modules/home-sidebar.js +7 -0
  103. package/lib/public/modules/home-tool-frame.js +182 -0
  104. package/lib/public/modules/home-tools.js +138 -1
  105. package/lib/public/modules/input.js +38 -7
  106. package/lib/public/modules/markdown.js +2 -0
  107. package/lib/public/modules/mate-sidebar.js +0 -8
  108. package/lib/public/modules/message-delivery.js +97 -0
  109. package/lib/public/modules/paste-modal.js +84 -0
  110. package/lib/public/modules/project-logs-render.js +405 -0
  111. package/lib/public/modules/project-logs.js +461 -0
  112. package/lib/public/modules/project-removal-target.js +26 -0
  113. package/lib/public/modules/scheduler.js +17 -3
  114. package/lib/public/modules/session-actions.js +29 -105
  115. package/lib/public/modules/session-hierarchy.js +54 -0
  116. package/lib/public/modules/sidebar-mobile.js +40 -15
  117. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  118. package/lib/public/modules/sidebar-sessions.js +77 -11
  119. package/lib/public/modules/sidebar.js +17 -5
  120. package/lib/public/modules/split-group-helpers.js +8 -0
  121. package/lib/public/modules/split-view.js +5 -2
  122. package/lib/public/modules/sticky-note-markdown.js +5 -0
  123. package/lib/public/modules/sticky-notes-browser.js +326 -0
  124. package/lib/public/modules/sticky-notes-card.js +360 -0
  125. package/lib/public/modules/sticky-notes-editor.js +291 -0
  126. package/lib/public/modules/sticky-notes-shared.js +90 -0
  127. package/lib/public/modules/sticky-notes.js +170 -946
  128. package/lib/public/modules/tool-palette-order.js +143 -0
  129. package/lib/public/modules/tool-palette-overlays.js +159 -0
  130. package/lib/public/modules/tool-palette.js +21 -188
  131. package/lib/public/modules/tools.js +21 -1
  132. package/lib/public/modules/update-snooze.js +169 -0
  133. package/lib/public/modules/user-settings.js +4 -0
  134. package/lib/public/modules/worker-pane-lock.js +176 -0
  135. package/lib/public/modules/worker-proposal-state.js +16 -0
  136. package/lib/public/modules/worker-proposal.js +49 -12
  137. package/lib/public/style.css +3 -1
  138. package/lib/sdk-bridge.js +132 -46
  139. package/lib/sdk-message-processor.js +60 -14
  140. package/lib/server-experimental-settings.js +49 -0
  141. package/lib/server-global-ws.js +11 -0
  142. package/lib/server-home-chat-events.js +31 -1
  143. package/lib/server-home-chat.js +27 -4
  144. package/lib/server-home-clay-entry.js +1 -1
  145. package/lib/server-home-clay-session-links.js +34 -5
  146. package/lib/server-settings.js +5 -0
  147. package/lib/server-tools.js +127 -9
  148. package/lib/server.js +124 -18
  149. package/lib/session-driver-eligibility.js +38 -0
  150. package/lib/session-driver-orchestration.js +31 -0
  151. package/lib/session-hygiene.js +3 -0
  152. package/lib/session-notes-mcp-server.js +25 -5
  153. package/lib/session-pair-factory.js +247 -0
  154. package/lib/session-pair-mcp-server.js +46 -8
  155. package/lib/session-pair-prompts.js +79 -0
  156. package/lib/session-pair-turn-control.js +153 -0
  157. package/lib/session-provenance.js +119 -0
  158. package/lib/session-spawn-mcp-server.js +1 -1
  159. package/lib/session-split-groups.js +4 -1
  160. package/lib/session-title-policy.js +60 -0
  161. package/lib/session-visibility.js +39 -0
  162. package/lib/sessions.js +61 -11
  163. package/lib/tools-registry.js +89 -10
  164. package/lib/update-snooze.js +437 -0
  165. package/lib/users-experimental-preferences.js +30 -0
  166. package/lib/users-permissions.js +2 -2
  167. package/lib/users.js +22 -16
  168. package/lib/workspace-query-access.js +102 -0
  169. package/lib/workspace-query-service.js +24 -17
  170. package/lib/worktree.js +25 -38
  171. package/lib/ws-schema.js +38 -7
  172. package/lib/yoke/adapters/claude-worker.js +16 -6
  173. package/lib/yoke/adapters/claude.js +81 -4
  174. package/lib/yoke/adapters/codex.js +63 -15
  175. package/lib/yoke/codex-background-tasks.js +8 -2
  176. package/lib/yoke/vendor-registry.js +1 -1
  177. package/package.json +3 -3
@@ -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",
@@ -2,14 +2,29 @@ var toolsRegistry = require("./tools-registry");
2
2
  var toolStorage = require("./tool-storage");
3
3
  var crypto = require("crypto");
4
4
  var toolLlm = require("./tool-llm");
5
+ var capsuleFloor = require("./capsule-display-floor");
6
+ var capsuleServerRuntimes = require("./capsule-server-runtimes");
7
+ var capsuleFrameServer = require("./capsule-frame-server");
8
+ var capsuleMateTurn = require("./capsule-mate-turn");
5
9
 
6
10
  function attachTools(deps) {
7
11
  var users = deps.users;
8
12
  var projects = deps.projects;
9
- var serverLogic = Object.create(null);
13
+ var serverRuntimes = deps.serverRuntimes || capsuleServerRuntimes;
14
+ var frameServer = deps.frameServer || capsuleFrameServer.createCapsuleFrameServer({ tlsOptions: deps.tlsOptions || null });
15
+ var mateTurn = deps.mateTurn || capsuleMateTurn.attachCapsuleMateTurn({
16
+ users: users,
17
+ findMateProject: deps.findMateProject || null,
18
+ broadcastToUser: function (targetUserId, payload) { broadcastToUser(targetUserId, payload); },
19
+ });
10
20
  var controlTimeoutMs = deps.controlTimeoutMs || 15000;
11
21
  var llmControlTimeoutMs = deps.llmControlTimeoutMs || 90000;
12
22
  var pendingControl = Object.create(null);
23
+ var isCapsulesEnabled = deps.isCapsulesEnabled || function () { return true; };
24
+
25
+ function requireCapsulesEnabled(userId) {
26
+ if (!isCapsulesEnabled(userId)) throw new Error("Capsules are disabled in User Settings.");
27
+ }
13
28
 
14
29
  function contextFor(userId) {
15
30
  var multiUser = users.isMultiUser();
@@ -68,6 +83,7 @@ function attachTools(deps) {
68
83
  }
69
84
 
70
85
  function installedManifests(userId) {
86
+ if (!isCapsulesEnabled(userId)) return [];
71
87
  var ctx = contextFor(userId);
72
88
  return toolsRegistry.listTools(ctx).filter(function (manifest) { return !manifest.error; });
73
89
  }
@@ -137,20 +153,52 @@ function attachTools(deps) {
137
153
  });
138
154
  }
139
155
 
156
+ // One act pipeline for server-runtime Capsules. The human's button click and
157
+ // the Mate's clay_tool_act call both arrive here, so the same Logic enforces
158
+ // the same rules for both and neither gets a private path. The seat is set
159
+ // from the server-resolved actor, never from caller-supplied text.
160
+ //
161
+ // The caller gets state and nothing else: a Mate never sees Display. Every
162
+ // successful act additionally pushes its causal {seq, actor, action,
163
+ // previous, next} event to the user's own connected clients, so a watching
164
+ // Display re-renders on any Logic change no matter who caused it.
165
+ async function controlServerCapsule(userId, installed, actor, callerId, kind, payload) {
166
+ var runtime = serverRuntimes.createRuntime(installed.manifest.id, contextFor(userId));
167
+ if (!runtime) throw new Error("No trusted server runtime is registered for this capsule.");
168
+ var context = { userId: userId, actor: actor, callerId: callerId };
169
+ if (kind === "snapshot") return runtime.snapshot(context);
170
+ if (kind === "act") {
171
+ var result = await runtime.act(context, payload.actionId, payload.args || {});
172
+ if (result && result.event) {
173
+ broadcastToUser(userId, { type: "tool_server_event", toolId: installed.manifest.id, event: result.event });
174
+ // The game round-trips: a Mate act names the opponent seat, and a
175
+ // human act that hands the turn over wakes that Mate to play it.
176
+ if (actor === "mate" && callerId) mateTurn.rememberOpponent(userId, installed.manifest.id, callerId);
177
+ mateTurn.maybeNudge(userId, installed.manifest, actor, result.event);
178
+ return result.state;
179
+ }
180
+ return result;
181
+ }
182
+ if (kind === "set" && runtime.set) return runtime.set(context, payload.controlId, payload.value);
183
+ throw new Error("The server-runtime capsule does not support '" + kind + "'.");
184
+ }
185
+
140
186
  function controlForMate(userId, mateId, toolId, kind, payload) {
187
+ requireCapsulesEnabled(userId);
141
188
  payload = payload || {};
142
189
  var ctx = contextFor(userId);
143
190
  toolsRegistry.listTools(ctx);
144
191
  var installed = toolsRegistry.getTool(ctx, toolId);
145
192
  if (!installed) return Promise.reject(new Error("Tool is not installed."));
193
+ // Skills are gated by Display: the Mate loses a Capsule at the same moment
194
+ // the human loses the declarative floor it is operated through.
195
+ if (!capsuleFloor.hasUsableFloor(installed.manifest, installed.uiTree)) {
196
+ return Promise.reject(new Error("This Capsule has no usable human Display, so it is unavailable to Mates."));
197
+ }
146
198
  if (installed.manifest.runtime === "server") {
147
- var adapter = serverLogic[toolId];
148
- if (!adapter) return Promise.reject(new Error("No trusted server runtime is registered for this capsule."));
149
- var context = { userId: userId, callerId: mateId };
150
- if (kind === "snapshot") return Promise.resolve(adapter.snapshot(context));
151
- if (kind === "act") return Promise.resolve(adapter.act(context, payload.actionId, payload.args || {}));
152
- if (kind === "set" && adapter.set) return Promise.resolve(adapter.set(context, payload.controlId, payload.value));
153
- return Promise.reject(new Error("The server-runtime capsule does not support '" + kind + "'."));
199
+ return Promise.resolve().then(function () {
200
+ return controlServerCapsule(userId, installed, "mate", mateId, kind, payload);
201
+ });
154
202
  }
155
203
  var permissions = installed.manifest.permissions || [];
156
204
  var timeoutMs = kind === "act" && permissions.indexOf("llm") !== -1 ? llmControlTimeoutMs : controlTimeoutMs;
@@ -211,6 +259,7 @@ function attachTools(deps) {
211
259
  }
212
260
 
213
261
  function installForMate(userId, input) {
262
+ requireCapsulesEnabled(userId);
214
263
  var installed = toolsRegistry.installTool(contextFor(userId), input);
215
264
  refreshMateCatalogs(userId);
216
265
  broadcastToUser(userId, { type: "tool_installed", tool: installed });
@@ -218,6 +267,7 @@ function attachTools(deps) {
218
267
  }
219
268
 
220
269
  function sourceForMate(userId, toolId) {
270
+ requireCapsulesEnabled(userId);
221
271
  var ctx = contextFor(userId);
222
272
  var installed = toolsRegistry.getTool(ctx, toolId);
223
273
  if (!installed) throw new Error("Tool not found.");
@@ -229,6 +279,7 @@ function attachTools(deps) {
229
279
  }
230
280
 
231
281
  function updateForMate(userId, toolId, input) {
282
+ requireCapsulesEnabled(userId);
232
283
  var ctx = contextFor(userId);
233
284
  var installed = toolsRegistry.getTool(ctx, toolId);
234
285
  if (!installed) throw new Error("Tool not found.");
@@ -243,6 +294,7 @@ function attachTools(deps) {
243
294
  }
244
295
 
245
296
  function setMateAccess(userId, toolId, allowed, requestId) {
297
+ requireCapsulesEnabled(userId);
246
298
  var ctx = contextFor(userId);
247
299
  var tool = toolsRegistry.getTool(ctx, toolId);
248
300
  if (!tool) throw new Error("Tool not found.");
@@ -255,6 +307,7 @@ function attachTools(deps) {
255
307
  }
256
308
 
257
309
  function removeForMate(userId, toolId) {
310
+ requireCapsulesEnabled(userId);
258
311
  var removed = toolsRegistry.removeTool(contextFor(userId), toolId);
259
312
  if (!removed) throw new Error("Tool not found.");
260
313
  refreshMateCatalogs(userId);
@@ -262,8 +315,19 @@ function attachTools(deps) {
262
315
  return { removed: true, toolId: toolId };
263
316
  }
264
317
 
318
+ // The human half of the shared server-Capsule pipeline. Same Logic, same
319
+ // rules, no AI in the path.
320
+ function controlForUser(userId, toolId, kind, payload) {
321
+ requireCapsulesEnabled(userId);
322
+ var ctx = contextFor(userId);
323
+ var installed = toolsRegistry.getTool(ctx, toolId);
324
+ if (!installed) throw new Error("Tool is not installed.");
325
+ if (installed.manifest.runtime !== "server") throw new Error("This Capsule does not run on the server.");
326
+ return controlServerCapsule(userId, installed, "human", "user", kind, payload || {});
327
+ }
328
+
265
329
  function handleMessage(ws, msg) {
266
- var messageTypes = ["tools_list", "tool_get", "tool_install", "tool_remove", "tool_storage_op", "tool_llm_op", "tool_llm_config_get", "tool_control_response", "tool_source_get", "tool_mate_access_set"];
330
+ var messageTypes = ["tools_list", "tool_get", "tool_install", "tool_remove", "tool_storage_op", "tool_llm_op", "tool_llm_config_get", "tool_control_response", "tool_source_get", "tool_mate_access_set", "tool_server_control", "tool_frame_url"];
267
331
  if (!msg || messageTypes.indexOf(msg.type) === -1) return false;
268
332
  var userId;
269
333
  if (users.isMultiUser()) {
@@ -279,6 +343,12 @@ function attachTools(deps) {
279
343
  return true;
280
344
  }
281
345
 
346
+ if (!isCapsulesEnabled(userId)) {
347
+ if (msg.type === "tools_list") send(ws, { type: "tools_state", tools: [] });
348
+ else sendError(ws, msg, new Error("Capsules are disabled in User Settings."));
349
+ return true;
350
+ }
351
+
282
352
  if (msg.type === "tools_list") {
283
353
  run(ws, msg, function () {
284
354
  send(ws, { type: "tools_state", tools: fullToolList(ctx) });
@@ -312,6 +382,52 @@ function attachTools(deps) {
312
382
  });
313
383
  return true;
314
384
  }
385
+ if (msg.type === "tool_frame_url") {
386
+ // The rich element renders on a separate anonymous origin, so the frame
387
+ // URL is minted here, on the authenticated socket, as a short-lived
388
+ // one-time token bound to this user's tools root. The rich element is
389
+ // additive only: it exists solely when the floor already validated at
390
+ // registration, and losing it never costs the human the floor.
391
+ Promise.resolve().then(function () {
392
+ var ctx = contextFor(userId);
393
+ var installed = toolsRegistry.getTool(ctx, msg.toolId);
394
+ if (!installed) throw new Error("Tool is not installed.");
395
+ if (!installed.hasRichDisplay) throw new Error("This Capsule ships no rich Display element.");
396
+ return frameServer.issueFrameUrl(ctx, msg.toolId);
397
+ }).then(function (frame) {
398
+ send(ws, { type: "tool_frame_url_state", toolId: msg.toolId, requestId: msg.requestId || null, ok: true, frame: frame });
399
+ }).catch(function (error) {
400
+ send(ws, {
401
+ type: "tool_frame_url_state",
402
+ toolId: msg.toolId,
403
+ requestId: msg.requestId || null,
404
+ ok: false,
405
+ error: error && error.message ? error.message : "The rich Display is unavailable.",
406
+ });
407
+ });
408
+ return true;
409
+ }
410
+ if (msg.type === "tool_server_control") {
411
+ Promise.resolve().then(function () {
412
+ return controlForUser(userId, msg.toolId, msg.kind, {
413
+ actionId: msg.actionId,
414
+ args: msg.args || {},
415
+ controlId: msg.controlId,
416
+ value: msg.value,
417
+ });
418
+ }).then(function (state) {
419
+ send(ws, { type: "tool_server_state", toolId: msg.toolId, requestId: msg.requestId || null, ok: true, state: state });
420
+ }).catch(function (error) {
421
+ send(ws, {
422
+ type: "tool_server_state",
423
+ toolId: msg.toolId,
424
+ requestId: msg.requestId || null,
425
+ ok: false,
426
+ error: error && error.message ? error.message : "The Capsule action failed.",
427
+ });
428
+ });
429
+ return true;
430
+ }
315
431
  if (msg.type === "tool_install") {
316
432
  run(ws, msg, function () {
317
433
  installForMate(userId, {
@@ -366,11 +482,13 @@ function attachTools(deps) {
366
482
  handleMessage: handleMessage,
367
483
  installedManifests: installedManifests,
368
484
  controlForMate: controlForMate,
485
+ controlForUser: controlForUser,
369
486
  installForMate: installForMate,
370
487
  sourceForMate: sourceForMate,
371
488
  updateForMate: updateForMate,
372
489
  setMateAccess: setMateAccess,
373
490
  removeForMate: removeForMate,
491
+ refreshMateCatalogs: refreshMateCatalogs,
374
492
  };
375
493
  }
376
494