clay-server 3.8.1 → 4.0.0-beta.10

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 (222) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/background-task-timing.js +105 -0
  3. package/lib/builtin-mates.js +6 -6
  4. package/lib/claude-hook-installer.js +15 -0
  5. package/lib/clay-history-mcp-server.js +62 -297
  6. package/lib/daemon.js +23 -1
  7. package/lib/debate-model-selection.js +119 -0
  8. package/lib/home-debate-tool-policy.js +164 -0
  9. package/lib/home-response-notifications.js +26 -0
  10. package/lib/knowledge-import.js +436 -0
  11. package/lib/knowledge-record-store.js +188 -0
  12. package/lib/knowledge-search.js +224 -0
  13. package/lib/mate-creation-mcp-server.js +55 -0
  14. package/lib/mate-knowledge-mcp-server.js +151 -0
  15. package/lib/mate-knowledge-migration.js +283 -0
  16. package/lib/mate-knowledge-service.js +488 -0
  17. package/lib/mate-knowledge-sync.js +482 -0
  18. package/lib/mate-ready-creation.js +95 -0
  19. package/lib/mates.js +2 -0
  20. package/lib/notes.js +2 -1
  21. package/lib/project-ask-user.js +64 -0
  22. package/lib/project-capsule-catalog.js +101 -0
  23. package/lib/project-connection.js +17 -0
  24. package/lib/project-debate-proposal.js +133 -17
  25. package/lib/project-debate.js +214 -110
  26. package/lib/project-delegated-follow-up.js +84 -0
  27. package/lib/project-delegated-session.js +53 -0
  28. package/lib/project-filesystem.js +7 -9
  29. package/lib/project-knowledge.js +23 -0
  30. package/lib/project-logs-comments.js +137 -0
  31. package/lib/project-logs-mcp-server.js +290 -0
  32. package/lib/project-logs-query.js +167 -0
  33. package/lib/project-logs-root.js +67 -0
  34. package/lib/project-logs-schema.js +259 -0
  35. package/lib/project-logs-service.js +357 -0
  36. package/lib/project-logs-snapshot.js +202 -0
  37. package/lib/project-logs-store.js +498 -0
  38. package/lib/project-logs-versioning.js +128 -0
  39. package/lib/project-logs.js +347 -0
  40. package/lib/project-mate-creation-proposal.js +120 -0
  41. package/lib/project-mate-interaction.js +14 -0
  42. package/lib/project-mate-knowledge.js +110 -0
  43. package/lib/project-memory.js +36 -7
  44. package/lib/project-models.js +157 -32
  45. package/lib/project-session-notes.js +2 -2
  46. package/lib/project-session-pair.js +8 -8
  47. package/lib/project-sessions.js +66 -51
  48. package/lib/project-vendor-login.js +414 -0
  49. package/lib/project-worker-proposal.js +25 -25
  50. package/lib/project-workspace-query.js +144 -0
  51. package/lib/project.js +433 -102
  52. package/lib/public/app.js +109 -48
  53. package/lib/public/css/avatar-imprints.css +0 -1
  54. package/lib/public/css/base.css +4 -0
  55. package/lib/public/css/capsule-ui.css +389 -0
  56. package/lib/public/css/command-palette.css +306 -6
  57. package/lib/public/css/home-capsule-library.css +243 -0
  58. package/lib/public/css/home-capsule-source.css +192 -0
  59. package/lib/public/css/home-debate-live.css +146 -0
  60. package/lib/public/css/home-debate-planning.css +125 -0
  61. package/lib/public/css/home-debates-archive.css +281 -0
  62. package/lib/public/css/home-hub.css +896 -405
  63. package/lib/public/css/home-mate-creation.css +34 -0
  64. package/lib/public/css/home-mate-model-picker.css +131 -0
  65. package/lib/public/css/home-mate-settings.css +344 -0
  66. package/lib/public/css/home-session-actions.css +265 -0
  67. package/lib/public/css/home-sidebar.css +667 -0
  68. package/lib/public/css/icon-strip.css +1 -2
  69. package/lib/public/css/input.css +79 -25
  70. package/lib/public/css/mates.css +7 -34
  71. package/lib/public/css/pane.css +1 -1
  72. package/lib/public/css/playbook.css +0 -80
  73. package/lib/public/css/project-logs.css +234 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/sticky-notes.css +53 -117
  76. package/lib/public/css/tui-attention.css +0 -44
  77. package/lib/public/css/workspace-assignment.css +45 -0
  78. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  79. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  80. package/lib/public/index.html +96 -74
  81. package/lib/public/modules/app-connection.js +100 -5
  82. package/lib/public/modules/app-dm.js +43 -160
  83. package/lib/public/modules/app-home-hub.js +328 -585
  84. package/lib/public/modules/app-message-cards.js +242 -0
  85. package/lib/public/modules/app-message-router.js +84 -0
  86. package/lib/public/modules/app-messages.js +124 -38
  87. package/lib/public/modules/app-notifications.js +8 -119
  88. package/lib/public/modules/app-panels.js +43 -2
  89. package/lib/public/modules/app-projects.js +9 -1
  90. package/lib/public/modules/app-rendering.js +21 -602
  91. package/lib/public/modules/avatar-imprint.js +11 -8
  92. package/lib/public/modules/background-tasks-ui.js +139 -16
  93. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  94. package/lib/public/modules/chat-render-runtime.js +198 -0
  95. package/lib/public/modules/command-palette.js +225 -436
  96. package/lib/public/modules/dm-render.js +82 -0
  97. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  98. package/lib/public/modules/home-capsule-library.js +146 -0
  99. package/lib/public/modules/home-capsule-source.js +383 -0
  100. package/lib/public/modules/home-chat-identity.js +35 -0
  101. package/lib/public/modules/home-chat-scroll.js +77 -0
  102. package/lib/public/modules/home-chat-stream-state.js +34 -0
  103. package/lib/public/modules/home-composer-focus.js +25 -0
  104. package/lib/public/modules/home-conversations-sheet.js +179 -0
  105. package/lib/public/modules/home-debate-activity.js +48 -0
  106. package/lib/public/modules/home-debate-controls.js +295 -0
  107. package/lib/public/modules/home-debate-launch.js +41 -0
  108. package/lib/public/modules/home-debate-live.js +284 -0
  109. package/lib/public/modules/home-debate-models.js +73 -0
  110. package/lib/public/modules/home-debate-planning.js +335 -0
  111. package/lib/public/modules/home-debates-archive.js +279 -0
  112. package/lib/public/modules/home-dock-resize.js +74 -0
  113. package/lib/public/modules/home-dock.js +446 -0
  114. package/lib/public/modules/home-mate-chat.js +496 -0
  115. package/lib/public/modules/home-mate-creation.js +109 -0
  116. package/lib/public/modules/home-mate-model-picker.js +281 -0
  117. package/lib/public/modules/home-mate-selection.js +38 -0
  118. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  119. package/lib/public/modules/home-mate-settings.js +383 -0
  120. package/lib/public/modules/home-session-actions.js +373 -0
  121. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  122. package/lib/public/modules/home-shell.js +9 -0
  123. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  124. package/lib/public/modules/home-sidebar.js +165 -0
  125. package/lib/public/modules/home-sub-surface.js +19 -0
  126. package/lib/public/modules/home-surface-boot.js +68 -0
  127. package/lib/public/modules/home-surface.js +141 -0
  128. package/lib/public/modules/home-tools.js +271 -0
  129. package/lib/public/modules/markdown.js +50 -2
  130. package/lib/public/modules/mate-management.js +86 -0
  131. package/lib/public/modules/mate-sidebar.js +0 -8
  132. package/lib/public/modules/mate-wizard.js +0 -7
  133. package/lib/public/modules/mention.js +17 -19
  134. package/lib/public/modules/pane-bridge.js +9 -0
  135. package/lib/public/modules/profile.js +4 -5
  136. package/lib/public/modules/project-logs-ambient.js +238 -0
  137. package/lib/public/modules/project-logs-render.js +359 -0
  138. package/lib/public/modules/project-logs.js +328 -0
  139. package/lib/public/modules/project-settings.js +34 -28
  140. package/lib/public/modules/project-switcher.js +11 -5
  141. package/lib/public/modules/project-workspace-assignment.js +23 -0
  142. package/lib/public/modules/scheduler.js +17 -3
  143. package/lib/public/modules/search-clay-chat.js +402 -0
  144. package/lib/public/modules/server-settings.js +14 -12
  145. package/lib/public/modules/session-actions.js +1 -1
  146. package/lib/public/modules/sidebar-mates.js +56 -552
  147. package/lib/public/modules/sidebar-mobile.js +12 -73
  148. package/lib/public/modules/sidebar-presence.js +37 -0
  149. package/lib/public/modules/sidebar-sessions.js +25 -1
  150. package/lib/public/modules/split-pair-ui.js +13 -13
  151. package/lib/public/modules/split-view.js +7 -0
  152. package/lib/public/modules/sticky-notes.js +106 -53
  153. package/lib/public/modules/tool-input-composition.js +48 -0
  154. package/lib/public/modules/tool-llm-status.js +167 -0
  155. package/lib/public/modules/tool-palette.js +76 -4
  156. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  157. package/lib/public/modules/tool-renderer-chart.js +158 -0
  158. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  159. package/lib/public/modules/tool-renderer.js +464 -0
  160. package/lib/public/modules/tool-runtime.js +309 -0
  161. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  162. package/lib/public/modules/tool-ui-tree.js +21 -0
  163. package/lib/public/modules/tui-attention.js +10 -0
  164. package/lib/public/modules/user-settings.js +16 -23
  165. package/lib/public/modules/vendor-login.js +287 -0
  166. package/lib/public/modules/worker-proposal.js +2 -2
  167. package/lib/public/modules/workspace-assignment-card.js +149 -0
  168. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  169. package/lib/public/style.css +14 -2
  170. package/lib/runtime-env.js +65 -0
  171. package/lib/sdk-bridge.js +120 -32
  172. package/lib/sdk-message-processor.js +34 -13
  173. package/lib/server-home-capsule-creation.js +36 -0
  174. package/lib/server-home-chat-events.js +287 -0
  175. package/lib/server-home-chat.js +518 -0
  176. package/lib/server-home-clay-entry.js +65 -0
  177. package/lib/server-home-clay-session-links.js +26 -0
  178. package/lib/server-home-debate-planning.js +214 -0
  179. package/lib/server-home-debates.js +137 -0
  180. package/lib/server-home-mate-creation.js +261 -0
  181. package/lib/server-home-models.js +241 -0
  182. package/lib/server-home-preferences.js +61 -0
  183. package/lib/server-mates.js +37 -0
  184. package/lib/server-tools.js +377 -0
  185. package/lib/server.js +166 -59
  186. package/lib/session-pair-mcp-server.js +6 -6
  187. package/lib/sessions.js +22 -4
  188. package/lib/tool-capsule-source.js +142 -0
  189. package/lib/tool-control-mcp-server.js +126 -0
  190. package/lib/tool-llm.js +48 -0
  191. package/lib/tool-storage.js +77 -0
  192. package/lib/tool-ui-spec-advanced.js +82 -0
  193. package/lib/tool-ui-spec.js +261 -0
  194. package/lib/tools-registry.js +286 -0
  195. package/lib/users-home-dock-preferences.js +77 -0
  196. package/lib/users-home-surface-preferences.js +99 -0
  197. package/lib/users-preferences.js +4 -7
  198. package/lib/users.js +12 -0
  199. package/lib/whats-new-content.js +3 -3
  200. package/lib/workspace-assignment-service.js +420 -0
  201. package/lib/workspace-query-mcp-server.js +154 -0
  202. package/lib/workspace-query-service.js +492 -0
  203. package/lib/ws-schema.js +107 -12
  204. package/lib/yoke/adapters/acp.js +2 -1
  205. package/lib/yoke/adapters/antigravity.js +2 -1
  206. package/lib/yoke/adapters/claude-worker.js +11 -4
  207. package/lib/yoke/adapters/claude.js +141 -46
  208. package/lib/yoke/adapters/codex.js +79 -11
  209. package/lib/yoke/adapters/kiro.js +2 -1
  210. package/lib/yoke/claude-user-input.js +38 -0
  211. package/lib/yoke/codex-app-server.js +20 -2
  212. package/lib/yoke/codex-background-tasks.js +8 -2
  213. package/lib/yoke/codex-user-input.js +64 -0
  214. package/lib/yoke/complete-once.js +164 -0
  215. package/lib/yoke/index.js +5 -1
  216. package/lib/yoke/interface.js +19 -0
  217. package/lib/yoke/mcp-bridge-server.js +2 -6
  218. package/lib/yoke/user-input.js +254 -0
  219. package/package.json +4 -3
  220. package/lib/public/css/home-chat.css +0 -380
  221. package/lib/public/modules/home-chat.js +0 -438
  222. package/lib/server-clay-home.js +0 -245
@@ -0,0 +1,84 @@
1
+ function hasEntries(value) {
2
+ return !!(value && Object.keys(value).length > 0);
3
+ }
4
+
5
+ function unsafeReason(session, isPaired) {
6
+ if (!session || session.hidden) return "Target session is unavailable.";
7
+ if (!session.cliSessionId) return "Target session must have a durable session reference.";
8
+ if (session.sessionVisibility !== "private") return "Target session must be private.";
9
+ if (session.isProcessing || session._queryStarting || session._awaitingTurnResult) return "Target session must be idle.";
10
+ if (hasEntries(session.pendingPermissions) || hasEntries(session.pendingAskUser) || hasEntries(session.pendingElicitations) || hasEntries(session.pendingUserDialogs)) return "Target session is waiting for user input.";
11
+ if ((session.mode && session.mode !== "gui") || (session.runtimeMode && session.runtimeMode !== "gui") || session.terminalId != null || session.runtimeTerminalId != null || session.tuiSuspended) return "Terminal sessions cannot receive project follow-ups.";
12
+ if (session.homeDebatePlanning || session.homeDebatePhase || session.debateSetupMode || session.debateState || session._debate) return "Debate sessions cannot receive project follow-ups.";
13
+ if (session.loop || session.spawn || session.handoff || session.assignment || session.singleTurn || session._pairDelegation || isPaired) return "Orchestrated sessions cannot receive project follow-ups.";
14
+ if (session.dangerouslySkipPermissions || session.permissionMode === "bypassPermissions") return "Full-access sessions cannot receive project follow-ups.";
15
+ if (session.destroying || session.scheduledMessage || session.rateLimitAutoContinuePending || session.taskStopRequested || session._runtimeRefreshRequested || session.pendingPush && session.pendingPush.length) return "Target session is not safely resumable right now.";
16
+ return "";
17
+ }
18
+
19
+ function attachDelegatedFollowUp(ctx) {
20
+ var sm = ctx.sm;
21
+
22
+ function inspect(principal, targetSessionRef) {
23
+ if (!principal || typeof targetSessionRef !== "string" || targetSessionRef.indexOf("session:") !== 0) {
24
+ throw new Error("A durable opaque targetSessionRef is required.");
25
+ }
26
+ var session = null;
27
+ sm.sessions.forEach(function (candidate) {
28
+ if (!session && ctx.sessionRef(candidate) === targetSessionRef) session = candidate;
29
+ });
30
+ if (!session) throw new Error("Target session was not found in this project.");
31
+ if (ctx.isMultiUser()) {
32
+ if (!principal.userId || session.ownerId !== principal.userId) throw new Error("Target session access denied.");
33
+ } else if (session.ownerId) {
34
+ throw new Error("Target session access denied.");
35
+ }
36
+ var paired = !!(ctx.isPairedSession && ctx.isPairedSession(session));
37
+ var reason = unsafeReason(session, paired);
38
+ if (reason) throw new Error(reason);
39
+ if (!ctx.canResumeVendor(session.vendor)) throw new Error("Target session provider cannot safely resume this conversation.");
40
+ return { session: session, sessionRef: targetSessionRef, title: session.title || "Existing session" };
41
+ }
42
+
43
+ async function dispatch(principal, targetSessionRef, task, metadata, onReady) {
44
+ var resolved = inspect(principal, targetSessionRef);
45
+ var session = resolved.session;
46
+ var sdk = ctx.getSdk();
47
+ if (!sdk || typeof sdk.pushMessage !== "function" || typeof sdk.startQuery !== "function") {
48
+ throw new Error("Target project runtime is unavailable.");
49
+ }
50
+ if (typeof onReady === "function") onReady(session);
51
+ sm.sendAndRecord(session, {
52
+ type: "delegated_follow_up",
53
+ text: task,
54
+ assignmentId: metadata.assignmentId,
55
+ sourceMateId: metadata.sourceMateId,
56
+ sourceProjectSlug: metadata.sourceProjectSlug,
57
+ sourceSessionRef: metadata.sourceSessionRef,
58
+ });
59
+ session.isProcessing = true;
60
+ session.lastActivity = Date.now();
61
+ session.sentToolResults = {};
62
+ if (ctx.onProcessingChanged) ctx.onProcessingChanged();
63
+ sm.broadcastSessionList();
64
+ try {
65
+ var delivered = sdk.pushMessage(session, task);
66
+ if (!delivered) await sdk.startQuery(session, task, undefined, ctx.getLinuxUserForSession(session));
67
+ } catch (error) {
68
+ session.isProcessing = false;
69
+ sm.sendAndRecord(session, {
70
+ type: "error",
71
+ text: "Delegated follow-up could not start: " + (error.message || String(error)),
72
+ assignmentId: metadata.assignmentId,
73
+ });
74
+ if (ctx.onProcessingChanged) ctx.onProcessingChanged();
75
+ sm.broadcastSessionList();
76
+ throw error;
77
+ }
78
+ return session;
79
+ }
80
+
81
+ return { inspectDelegatedFollowUp: inspect, dispatchDelegatedFollowUp: dispatch };
82
+ }
83
+
84
+ module.exports = { attachDelegatedFollowUp: attachDelegatedFollowUp, unsafeReason: unsafeReason };
@@ -0,0 +1,53 @@
1
+ function attachDelegatedSession(ctx) {
2
+ var sm = ctx.sm;
3
+
4
+ async function createDelegatedSession(principal, task, metadata, onCreated) {
5
+ if (!principal || !task) throw new Error("A bound owner and task are required.");
6
+ var selection = await ctx.resolveModel(principal);
7
+ if (!selection || selection.status !== "ready" || !selection.vendor || !selection.model) {
8
+ throw new Error(selection && selection.error ? selection.error : "No configured model is available in the target project.");
9
+ }
10
+ var sdk = ctx.getSdk();
11
+ if (!sdk || typeof sdk.startQuery !== "function") throw new Error("Target project runtime is unavailable.");
12
+ var create = typeof sm.createSessionRaw === "function" ? sm.createSessionRaw : sm.createSession;
13
+ var session = create.call(sm, {
14
+ ownerId: principal.userId || null,
15
+ sessionVisibility: "private",
16
+ vendor: selection.vendor,
17
+ model: selection.model,
18
+ });
19
+ session.title = metadata.title;
20
+ session.assignment = {
21
+ assignmentId: metadata.assignmentId,
22
+ sourceMateId: metadata.sourceMateId,
23
+ sourceProjectSlug: metadata.sourceProjectSlug,
24
+ sourceSessionRef: metadata.sourceSessionRef,
25
+ delegated: true,
26
+ };
27
+ session.singleTurn = true;
28
+ if (typeof onCreated === "function") onCreated(session);
29
+ sm.sendAndRecord(session, {
30
+ type: "delegated_work",
31
+ text: task,
32
+ assignmentId: metadata.assignmentId,
33
+ sourceMateId: metadata.sourceMateId,
34
+ sourceProjectSlug: metadata.sourceProjectSlug,
35
+ sourceSessionRef: metadata.sourceSessionRef,
36
+ });
37
+ session.isProcessing = true;
38
+ session.lastActivity = Date.now();
39
+ session.sentToolResults = {};
40
+ sm.broadcastSessionList();
41
+ try {
42
+ await Promise.resolve(sdk.startQuery(session, task, undefined, ctx.getLinuxUserForSession(session)));
43
+ } catch (error) {
44
+ session.isProcessing = false;
45
+ sm.sendAndRecord(session, { type: "error", text: error.message || String(error) });
46
+ }
47
+ return session;
48
+ }
49
+
50
+ return { createDelegatedSession: createDelegatedSession };
51
+ }
52
+
53
+ module.exports = { attachDelegatedSession: attachDelegatedSession };
@@ -13,7 +13,7 @@ var execFileSync = require("child_process").execFileSync;
13
13
  * getOsUserInfoForWs (function)
14
14
  * startFileWatch, stopFileWatch, startDirWatch (from _fileWatch)
15
15
  * usersModule, fsAsUser
16
- * validateEnvString (function)
16
+ * validateEnvString, onEnvironmentChanged (functions)
17
17
  * opts (for onGetProjectEnv, onSetProjectEnv, onGetSharedEnv, onSetSharedEnv callbacks)
18
18
  * IGNORED_DIRS, BINARY_EXTS, IMAGE_EXTS, FS_MAX_SIZE (constants)
19
19
  */
@@ -33,6 +33,7 @@ function attachFilesystem(ctx) {
33
33
  var usersModule = ctx.usersModule;
34
34
  var fsAsUser = ctx.fsAsUser;
35
35
  var validateEnvString = ctx.validateEnvString;
36
+ var onEnvironmentChanged = ctx.onEnvironmentChanged || function () {};
36
37
  var opts = ctx.opts;
37
38
  var IGNORED_DIRS = ctx.IGNORED_DIRS;
38
39
  var BINARY_EXTS = ctx.BINARY_EXTS;
@@ -234,16 +235,11 @@ function attachFilesystem(ctx) {
234
235
  // --- Project environment variables ---
235
236
  if (msg.type === "get_project_env") {
236
237
  var envrc = "";
237
- var hasEnvrc = false;
238
238
  if (typeof opts.onGetProjectEnv === "function") {
239
239
  var envResult = opts.onGetProjectEnv(msg.slug);
240
240
  envrc = envResult.envrc || "";
241
241
  }
242
- try {
243
- var envrcPath = path.join(cwd, ".envrc");
244
- hasEnvrc = fs.existsSync(envrcPath);
245
- } catch (e) {}
246
- sendTo(ws, { type: "project_env_result", slug: msg.slug, envrc: envrc, hasEnvrc: hasEnvrc });
242
+ sendTo(ws, { type: "project_env_result", slug: msg.slug, envrc: envrc });
247
243
  return true;
248
244
  }
249
245
 
@@ -255,7 +251,8 @@ function attachFilesystem(ctx) {
255
251
  return true;
256
252
  }
257
253
  var setResult = opts.onSetProjectEnv(msg.slug, msg.envrc || "");
258
- sendTo(ws, { type: "set_project_env_result", ok: setResult.ok, slug: msg.slug, error: setResult.error });
254
+ if (setResult.ok) onEnvironmentChanged();
255
+ sendTo(ws, { type: "set_project_env_result", ok: setResult.ok, slug: msg.slug, error: setResult.error, timing: setResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined });
259
256
  } else {
260
257
  sendTo(ws, { type: "set_project_env_result", ok: false, error: "Not supported" });
261
258
  }
@@ -308,7 +305,8 @@ function attachFilesystem(ctx) {
308
305
  return true;
309
306
  }
310
307
  var sharedSetResult = opts.onSetSharedEnv(msg.envrc || "");
311
- sendTo(ws, { type: "set_shared_env_result", ok: sharedSetResult.ok, error: sharedSetResult.error });
308
+ if (sharedSetResult.ok) onEnvironmentChanged();
309
+ sendTo(ws, { type: "set_shared_env_result", ok: sharedSetResult.ok, error: sharedSetResult.error, timing: sharedSetResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined });
312
310
  } else {
313
311
  sendTo(ws, { type: "set_shared_env_result", ok: false, error: "Not supported" });
314
312
  }
@@ -1,5 +1,6 @@
1
1
  var fs = require("fs");
2
2
  var path = require("path");
3
+ var mateSync = require("./mate-knowledge-sync");
3
4
 
4
5
  /**
5
6
  * Attach knowledge file management to a project context.
@@ -75,6 +76,23 @@ function attachKnowledge(ctx) {
75
76
  return files;
76
77
  }
77
78
 
79
+ // Mirror a Mate-owned knowledge file into the Clay-wide Knowledge backend
80
+ // after the legacy write has already succeeded. The legacy file stays
81
+ // authoritative; this only keeps the new scope from going stale.
82
+ function mirrorMateSource(ws, fileName) {
83
+ if (!isMate) return;
84
+ var user = (ws && ws._clayUser) || null;
85
+ mateSync.syncQuietly({
86
+ mateDir: cwd,
87
+ fileName: fileName,
88
+ actor: {
89
+ type: user ? "user" : "system",
90
+ userId: user ? user.id : null,
91
+ displayName: user ? (user.displayName || user.username) : null,
92
+ },
93
+ });
94
+ }
95
+
78
96
  function handleKnowledgeMessage(ws, msg) {
79
97
  if (msg.type === "knowledge_list") {
80
98
  sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
@@ -111,6 +129,7 @@ function attachKnowledge(ctx) {
111
129
  var knowledgeDir = path.join(cwd, "knowledge");
112
130
  fs.mkdirSync(knowledgeDir, { recursive: true });
113
131
  fs.writeFileSync(path.join(knowledgeDir, safeName), msg.content);
132
+ mirrorMateSource(ws, safeName);
114
133
  sendTo(ws, { type: "knowledge_saved", name: safeName });
115
134
  sendTo(ws, { type: "knowledge_list", files: listKnowledgeFilesBasic() });
116
135
  return true;
@@ -121,6 +140,9 @@ function attachKnowledge(ctx) {
121
140
  var safeName = path.basename(msg.name);
122
141
  var filePath = path.join(cwd, "knowledge", safeName);
123
142
  try { fs.unlinkSync(filePath); } catch (e) {}
143
+ // Reconciling after the unlink appends a tombstone, so a deleted file
144
+ // does not stay live in the new backend.
145
+ mirrorMateSource(ws, safeName);
124
146
  sendTo(ws, { type: "knowledge_deleted", name: safeName });
125
147
  sendTo(ws, { type: "knowledge_list", files: listKnowledgeFilesBasic() });
126
148
  return true;
@@ -155,6 +177,7 @@ function attachKnowledge(ctx) {
155
177
 
156
178
  return {
157
179
  handleKnowledgeMessage: handleKnowledgeMessage,
180
+ listKnowledgeFiles: listKnowledgeFiles,
158
181
  };
159
182
  }
160
183
 
@@ -0,0 +1,137 @@
1
+ // Comment and review projection for Project Logs.
2
+ //
3
+ // Split from project-logs-store.js so the store stays inside the module size
4
+ // limit.
5
+ //
6
+ // A user comment is a revision proposal, never an automatic canonical
7
+ // mutation. It starts `pending` and stays that way until the project's own
8
+ // Driver session reviews it. Reviews are append-only records that reference a
9
+ // comment id; nothing is ever edited in place, and a comment written before
10
+ // reviews existed simply projects as `pending`.
11
+
12
+ var STATUS_PENDING = "pending";
13
+ var STATUS_CLARIFICATION = "clarification-needed";
14
+ var STATUS_INCORPORATED = "incorporated";
15
+ var STATUS_DECLINED = "declined";
16
+
17
+ var STATUSES = [STATUS_PENDING, STATUS_CLARIFICATION, STATUS_INCORPORATED, STATUS_DECLINED];
18
+
19
+ // Actions the Driver may take. `incorporate` is the only one that produces a
20
+ // canonical revision.
21
+ var ACTIONS = ["incorporate", "clarify", "decline"];
22
+
23
+ var ACTION_STATUS = {
24
+ incorporate: STATUS_INCORPORATED,
25
+ clarify: STATUS_CLARIFICATION,
26
+ decline: STATUS_DECLINED,
27
+ };
28
+
29
+ // Waiting on the Driver. Only an unreviewed comment qualifies.
30
+ //
31
+ // A clarification is deliberately NOT Driver work: the Driver has already
32
+ // answered, and the ball is with the user. Because a comment is settled once
33
+ // and never re-reviewed, counting a clarification as pending would leave it in
34
+ // the queue forever with no way out. The user's reply is a new comment, which
35
+ // is pending in its own right and can be incorporated or declined.
36
+ function isPendingReview(status) {
37
+ return status === STATUS_PENDING;
38
+ }
39
+
40
+ // Answered by the Driver and now waiting on the person who commented. Shown
41
+ // permanently on the original comment; never counted as Driver work.
42
+ function isAwaitingUser(status) {
43
+ return status === STATUS_CLARIFICATION;
44
+ }
45
+
46
+ function isAction(value) {
47
+ return ACTIONS.indexOf(value) !== -1;
48
+ }
49
+
50
+ // Project the comment thread of one entry, folding review records onto the
51
+ // comments they resolve. `revisionForRecordId` maps an incorporating review
52
+ // record id to the canonical revision number it produced, so an incorporated
53
+ // comment can point at its result.
54
+ function comments(chain, options) {
55
+ var settings = options || {};
56
+ var maxComments = settings.maxComments || 200;
57
+ var revisionForRecordId = settings.revisionForRecordId || {};
58
+ var byId = {};
59
+ var order = [];
60
+
61
+ var i;
62
+ for (i = 0; i < chain.length; i++) {
63
+ var record = chain[i];
64
+ if (record.op !== "comment") continue;
65
+ if (order.length >= maxComments) break;
66
+ var comment = {
67
+ id: record.id,
68
+ body: record.body || "",
69
+ at: record.at || 0,
70
+ author: record.author || null,
71
+ // A comment with no review yet is pending, which is also how every
72
+ // comment written before reviews existed projects.
73
+ status: STATUS_PENDING,
74
+ review: null,
75
+ };
76
+ byId[record.id] = comment;
77
+ order.push(comment);
78
+ }
79
+
80
+ for (i = 0; i < chain.length; i++) {
81
+ var review = chain[i];
82
+ if (review.op !== "review") continue;
83
+ var target = byId[review.commentId];
84
+ if (!target) continue;
85
+ if (!isAction(review.action)) continue;
86
+ // Append-only: the first review of a comment settles it. A later review of
87
+ // the same comment is ignored rather than silently overwriting the record
88
+ // of what was decided.
89
+ if (target.review) continue;
90
+ target.status = ACTION_STATUS[review.action];
91
+ target.review = {
92
+ action: review.action,
93
+ response: typeof review.response === "string" ? review.response : "",
94
+ at: review.at || 0,
95
+ reviewer: review.author || null,
96
+ revision: review.action === "incorporate"
97
+ ? (revisionForRecordId[review.id] || null)
98
+ : null,
99
+ };
100
+ }
101
+
102
+ return order;
103
+ }
104
+
105
+ // How many comments are still waiting on the Driver. Used for the ledger row
106
+ // count and for the system-prompt signal, neither of which carries any body.
107
+ function pendingReviewCount(list) {
108
+ var count = 0;
109
+ for (var i = 0; i < (list || []).length; i++) {
110
+ if (isPendingReview(list[i].status)) count++;
111
+ }
112
+ return count;
113
+ }
114
+
115
+ function pendingReviewComments(list) {
116
+ var out = [];
117
+ for (var i = 0; i < (list || []).length; i++) {
118
+ if (isPendingReview(list[i].status)) out.push(list[i]);
119
+ }
120
+ return out;
121
+ }
122
+
123
+ module.exports = {
124
+ STATUS_PENDING: STATUS_PENDING,
125
+ STATUS_CLARIFICATION: STATUS_CLARIFICATION,
126
+ STATUS_INCORPORATED: STATUS_INCORPORATED,
127
+ STATUS_DECLINED: STATUS_DECLINED,
128
+ STATUSES: STATUSES,
129
+ ACTIONS: ACTIONS,
130
+ ACTION_STATUS: ACTION_STATUS,
131
+ isPendingReview: isPendingReview,
132
+ isAwaitingUser: isAwaitingUser,
133
+ isAction: isAction,
134
+ comments: comments,
135
+ pendingReviewCount: pendingReviewCount,
136
+ pendingReviewComments: pendingReviewComments,
137
+ };