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,128 @@
1
+ // Comment review and revision control for Project Logs.
2
+ //
3
+ // Split from project-logs-store.js so the store stays inside the module size
4
+ // limit. Everything here is Project Driver territory: reviewing a user comment
5
+ // and moving the canonical record between revisions. The authorization layer
6
+ // decides who may call these; this module only guarantees the record shape.
7
+
8
+ var logsSchema = require("./project-logs-schema");
9
+ var logsSnapshot = require("./project-logs-snapshot");
10
+ var logsComments = require("./project-logs-comments");
11
+ var recordStore = require("./knowledge-record-store");
12
+
13
+ function attachVersioning(ctx) {
14
+ var read = ctx.read;
15
+ var requireLive = ctx.requireLive;
16
+ var findChain = ctx.findChain;
17
+ var appendRevision = ctx.appendRevision;
18
+ var nextSnapshot = ctx.nextSnapshot;
19
+ var cleanText = ctx.cleanText;
20
+ var MAX_LINKS = ctx.maxLinks;
21
+ var MAX_RESPONSE_CHARS = ctx.maxResponseChars;
22
+ var scopeId = ctx.scopeId;
23
+ var store = ctx.store;
24
+ var normalizeAuthor = ctx.normalizeAuthor;
25
+
26
+ // Review a user comment. `clarify` and `decline` resolve the comment and
27
+ // create no canonical revision. `incorporate` appends exactly one record that
28
+ // both resolves the comment and carries the new canonical snapshot, so the
29
+ // two can never drift apart.
30
+ function review(ref, input, author) {
31
+ var located = requireLive(ref);
32
+ var data = input || {};
33
+ if (!logsComments.isAction(data.action)) {
34
+ throw new Error("A review action is required: " + logsComments.ACTIONS.join(", "));
35
+ }
36
+ var entry = read(ref, false);
37
+ var target = null;
38
+ for (var i = 0; i < entry.comments.length; i++) {
39
+ if (entry.comments[i].id === data.commentId) { target = entry.comments[i]; break; }
40
+ }
41
+ if (!target) throw new Error("Comment not found on this entry.");
42
+ if (target.review) throw new Error("That comment has already been reviewed.");
43
+
44
+ var response = cleanText(data.response || "", MAX_RESPONSE_CHARS);
45
+ if (data.action !== "incorporate" && !response) {
46
+ throw new Error("A " + data.action + " review requires a response explaining the decision.");
47
+ }
48
+
49
+ if (data.action !== "incorporate") {
50
+ store.append({
51
+ id: recordStore.newRecordId(),
52
+ rootId: located.rootId,
53
+ op: "review",
54
+ scope: scopeId,
55
+ action: data.action,
56
+ commentId: data.commentId,
57
+ response: response,
58
+ author: normalizeAuthor(author),
59
+ at: Date.now(),
60
+ });
61
+ return read(ref, false);
62
+ }
63
+
64
+ var validated = logsSchema.validateRevision(data);
65
+ var current = logsSnapshot.cloneSnapshot(entry);
66
+ var snapshot = nextSnapshot(current, validated, data);
67
+ if (logsSnapshot.sameSnapshot(current, snapshot)) {
68
+ throw new Error("Incorporating a comment requires an actual canonical change.");
69
+ }
70
+ appendRevision(located.rootId, "review", snapshot, author, {
71
+ action: "incorporate",
72
+ commentId: data.commentId,
73
+ response: response,
74
+ });
75
+ return read(ref, false);
76
+ }
77
+
78
+ // Restore an earlier revision by appending a new one. Later history is never
79
+ // erased: a revert is itself a revision, with the source recorded.
80
+ function revert(ref, revision, reason, author) {
81
+ var located = requireLive(ref);
82
+ var target = Number(revision);
83
+ if (!Number.isInteger(target) || target < 1) throw new Error("A revision number is required.");
84
+ var trimmedReason = cleanText(reason || "", MAX_RESPONSE_CHARS);
85
+ if (!trimmedReason) throw new Error("A revert requires a reason.");
86
+
87
+ var history = logsSnapshot.revisions(located.chain, MAX_LINKS);
88
+ if (target > history.revisions.length) {
89
+ throw new Error("This entry has only " + history.revisions.length + " revisions.");
90
+ }
91
+ var source = history.revisions[target - 1];
92
+ var current = history.revisions[history.revisions.length - 1];
93
+ if (logsSnapshot.sameSnapshot(current.snapshot, source.snapshot)) {
94
+ throw new Error("Revision " + target + " is identical to the current one.");
95
+ }
96
+ appendRevision(located.rootId, "revert", logsSnapshot.cloneSnapshot(source.snapshot), author, {
97
+ revertedFrom: target,
98
+ reason: trimmedReason,
99
+ });
100
+ return read(ref, false);
101
+ }
102
+
103
+ // The exact reconstructed snapshot as of one revision.
104
+ function readRevision(ref, revision) {
105
+ var located = findChain(ref);
106
+ if (!located) throw new Error("Log entry not found.");
107
+ var target = Number(revision);
108
+ if (!Number.isInteger(target) || target < 1) throw new Error("A revision number is required.");
109
+ var history = logsSnapshot.revisions(located.chain, MAX_LINKS);
110
+ if (target > history.revisions.length) {
111
+ throw new Error("This entry has only " + history.revisions.length + " revisions.");
112
+ }
113
+ var found = history.revisions[target - 1];
114
+ var described = logsSnapshot.describeRevision(found);
115
+ described.ref = ref;
116
+ described.snapshot = found.snapshot;
117
+ return described;
118
+ }
119
+
120
+
121
+ return {
122
+ review: review,
123
+ revert: revert,
124
+ readRevision: readRevision,
125
+ };
126
+ }
127
+
128
+ module.exports = { attachVersioning: attachVersioning };
@@ -0,0 +1,347 @@
1
+ // Project Logs message handling and session-bound MCP wiring.
2
+ //
3
+ // Every capability here is derived server-side. The WebSocket path binds from
4
+ // ws._clayUser plus this project's own slug, and the MCP path binds from the
5
+ // exact live session object. Neither accepts identity or project scope from
6
+ // the message, so a client cannot widen its own reach by asking.
7
+ //
8
+ // Authorship is split deliberately. Canonical entries are created and revised
9
+ // only by this project's agent sessions through `clay-logs`; a connected human
10
+ // reads the ledger and appends attributed comments. That keeps the log an
11
+ // honest record of the work rather than a shared wiki.
12
+
13
+ var logsMcp = require("./project-logs-mcp-server");
14
+
15
+ var LIST_LIMIT = 50;
16
+ var MAX_NOTICE_TITLE_CHARS = 160;
17
+ var MAX_NOTICE_SUMMARY_CHARS = 240;
18
+
19
+ // Canonical writes that move the ledger forward. A plain comment, a
20
+ // clarification, and a decline are participation, not revisions, so they never
21
+ // notify.
22
+ var NOTIFYING_OPS = ["create", "update", "link", "incorporate", "revert"];
23
+
24
+ function clip(value, max) {
25
+ if (typeof value !== "string") return "";
26
+ return value.length > max ? value.substring(0, max) : value;
27
+ }
28
+
29
+ // Bounded ledger metadata only. No body, no comment text, no author identity
30
+ // beyond the vendor that wrote it.
31
+ function updateNotice(entry, op) {
32
+ return {
33
+ type: "project_log_updated",
34
+ ref: entry.ref,
35
+ revision: entry.revisions,
36
+ op: op,
37
+ category: entry.category,
38
+ priority: entry.priority,
39
+ title: clip(entry.title, MAX_NOTICE_TITLE_CHARS),
40
+ summary: clip(entry.summary, MAX_NOTICE_SUMMARY_CHARS),
41
+ at: entry.updatedAt,
42
+ vendor: (entry.updatedBy && entry.updatedBy.vendor) || null,
43
+ };
44
+ }
45
+
46
+ var SYSTEM_PROMPT_LABEL = "--- Project Logs (durable project record; manage via clay-logs tools) ---";
47
+
48
+ function parseJsonArray(value) {
49
+ if (Array.isArray(value)) return value;
50
+ if (typeof value !== "string" || !value.trim()) return undefined;
51
+ var parsed;
52
+ try {
53
+ parsed = JSON.parse(value);
54
+ } catch (e) {
55
+ throw new Error("Expected a JSON array.");
56
+ }
57
+ if (!Array.isArray(parsed)) throw new Error("Expected a JSON array.");
58
+ return parsed;
59
+ }
60
+
61
+ // The tool surface accepts JSON-encoded arrays because MCP shapes are flat
62
+ // scalars. The service and store only ever see real arrays.
63
+ function coerceToolArgs(args) {
64
+ var out = Object.assign({}, args || {});
65
+ if (out.tags !== undefined) out.tags = parseJsonArray(out.tags);
66
+ if (out.links !== undefined) out.links = parseJsonArray(out.links);
67
+ return out;
68
+ }
69
+
70
+ var HANDLED_TYPES = [
71
+ "project_logs_list",
72
+ "project_log_read",
73
+ "project_log_comment",
74
+ // Retired human mutations. Still claimed so an older client gets an
75
+ // explicit refusal rather than silence.
76
+ "project_log_create",
77
+ "project_log_update",
78
+ ];
79
+
80
+ var RETIRED_TYPES = ["project_log_create", "project_log_update"];
81
+
82
+ function attachProjectLogs(ctx) {
83
+ var service = ctx.service;
84
+ var sm = ctx.sm;
85
+ var slug = ctx.projectSlug;
86
+ var isMate = ctx.isMate === true;
87
+ var mateId = ctx.mateId || null;
88
+ var sendTo = ctx.sendTo;
89
+ var getClients = ctx.getClients || function () { return []; };
90
+ var getProjectOwnerId = ctx.getProjectOwnerId || function () { return null; };
91
+
92
+ // Every socket in this project's client set passed the project access check
93
+ // at WebSocket upgrade, so a broadcast here is already scoped to authorized
94
+ // human users of this project. Nothing is read from any payload. Pane
95
+ // connections are excluded, matching how sticky notes broadcast.
96
+ function broadcastUpdate(entry, op) {
97
+ if (isMate || !entry) return;
98
+ if (NOTIFYING_OPS.indexOf(op) === -1) return;
99
+ var notice = updateNotice(entry, op);
100
+ var clients = getClients();
101
+ for (var client of clients) {
102
+ if (!client || client.readyState !== 1 || client._clayPane) continue;
103
+ sendTo(client, notice);
104
+ }
105
+ }
106
+
107
+ // Wrap a canonical write so a successful one notifies. A throw never reaches
108
+ // here, so a failed or no-op write is silent.
109
+ function notifying(op, run) {
110
+ return function (args) {
111
+ var result = run(args);
112
+ broadcastUpdate(result, op);
113
+ return result;
114
+ };
115
+ }
116
+
117
+ // --- Bindings --------------------------------------------------------
118
+
119
+ function sessionBinding(session) {
120
+ if (!service) return null;
121
+ if (isMate) {
122
+ if (!mateId) return null;
123
+ if (session && (!sm || !sm.sessions || sm.sessions.get(session.localId) !== session)) return null;
124
+ return service.bindMate({
125
+ projectSlug: slug,
126
+ projectOwnerId: getProjectOwnerId(),
127
+ isMate: true,
128
+ mateId: mateId,
129
+ session: session || null,
130
+ });
131
+ }
132
+ if (!session) return null;
133
+ return service.bindProjectSession({ projectSlug: slug, session: session });
134
+ }
135
+
136
+ function userBinding(ws) {
137
+ if (!service || isMate) return null;
138
+ return service.bindUser({ projectSlug: slug, user: (ws && ws._clayUser) || null });
139
+ }
140
+
141
+ // --- WebSocket surface -----------------------------------------------
142
+
143
+ function fail(ws, requestId, message) {
144
+ sendTo(ws, { type: "project_logs_error", requestId: requestId || null, message: message });
145
+ }
146
+
147
+ // Search already returns the ledger row shape, so a query needs no per-hit
148
+ // read and neither path dumps a record body into the list.
149
+ // Returns both the rows and this project's live category vocabulary, so the
150
+ // filter is populated solely from what this project actually uses.
151
+ function listEntries(bound, query, category) {
152
+ if (query) {
153
+ var hits = bound.searchLogs({ query: query, kind: category, limit: LIST_LIMIT });
154
+ return { entries: hits.results, categories: hits.categories || [] };
155
+ }
156
+ var listed = bound.listLogs({ kind: category, limit: LIST_LIMIT });
157
+ return { entries: listed.entries, categories: listed.categories || [] };
158
+ }
159
+
160
+ function handleLogsMessage(ws, msg) {
161
+ if (!msg || typeof msg.type !== "string") return false;
162
+ if (HANDLED_TYPES.indexOf(msg.type) === -1) return false;
163
+
164
+ var requestId = typeof msg.requestId === "string" ? msg.requestId : null;
165
+ if (isMate) {
166
+ fail(ws, requestId, "Project Logs are available in projects, not in Mate conversations.");
167
+ return true;
168
+ }
169
+
170
+ // Canonical authorship belongs to the project's agent sessions. A human
171
+ // asking to create or revise is refused before any binding is attempted,
172
+ // so no privilege level can reach the canonical writers.
173
+ if (RETIRED_TYPES.indexOf(msg.type) !== -1) {
174
+ fail(ws, requestId, "Project Logs are written by this project's agent sessions. Add a comment instead.");
175
+ return true;
176
+ }
177
+
178
+ var bound = userBinding(ws);
179
+ if (!bound) {
180
+ fail(ws, requestId, "Project Logs are not available for this project.");
181
+ return true;
182
+ }
183
+
184
+ try {
185
+ if (msg.type === "project_logs_list") {
186
+ var query = typeof msg.query === "string" ? msg.query.trim() : "";
187
+ // A view preference, not an authorization input: the store validates it.
188
+ var category = typeof msg.category === "string" && msg.category ? msg.category : null;
189
+ var listing = listEntries(bound, query, category);
190
+ sendTo(ws, {
191
+ type: "project_logs_state",
192
+ requestId: requestId,
193
+ entries: listing.entries,
194
+ categories: listing.categories,
195
+ });
196
+ return true;
197
+ }
198
+
199
+ if (msg.type === "project_log_read") {
200
+ sendTo(ws, { type: "project_log_entry", requestId: requestId, entry: bound.readLog({ ref: msg.ref }) });
201
+ return true;
202
+ }
203
+
204
+ if (msg.type === "project_log_comment") {
205
+ var commented = bound.commentLog({ ref: msg.ref, body: msg.body });
206
+ sendTo(ws, { type: "project_log_commented", requestId: requestId, entry: commented });
207
+ return true;
208
+ }
209
+ } catch (e) {
210
+ fail(ws, requestId, (e && e.message) || "Project Logs could not complete the request.");
211
+ return true;
212
+ }
213
+
214
+ return false;
215
+ }
216
+
217
+ // --- MCP surface ------------------------------------------------------
218
+
219
+ function toolHandlers(bound) {
220
+ if (!bound) return null;
221
+ return {
222
+ listLogs: function (args) { return bound.listLogs(args || {}); },
223
+ searchLogs: function (args) { return bound.searchLogs(args || {}); },
224
+ readLog: function (args) { return bound.readLog(args || {}); },
225
+ logHistory: function (args) { return bound.logHistory(args || {}); },
226
+ readLogRevision: function (args) { return bound.readLogRevision(args || {}); },
227
+ listLogFeedback: function (args) { return bound.listLogFeedback(args || {}); },
228
+ reviewLogComment: function (args) {
229
+ var request = coerceToolArgs(args);
230
+ var result = bound.reviewLogComment(request);
231
+ // Only an incorporation is a canonical revision; clarify and decline
232
+ // resolve a comment and change nothing.
233
+ if (request && request.action === "incorporate") broadcastUpdate(result, "incorporate");
234
+ return result;
235
+ },
236
+ revertLog: notifying("revert", function (args) { return bound.revertLog(args || {}); }),
237
+ createLog: notifying("create", function (args) { return bound.createLog(coerceToolArgs(args)); }),
238
+ updateLog: notifying("update", function (args) { return bound.updateLog(coerceToolArgs(args)); }),
239
+ linkLog: notifying("link", function (args) { return bound.linkLog(coerceToolArgs(args)); }),
240
+ };
241
+ }
242
+
243
+ // A Mate project advertises tools only when the registry confirms
244
+ // authoritative builtin Clay, and then only the read-only cross-project set.
245
+ function includeGlobal() {
246
+ if (!isMate) return false;
247
+ var projectBound = sessionBinding(null);
248
+ return !!(projectBound && projectBound.isClay === true);
249
+ }
250
+
251
+ function getToolDefs(session) {
252
+ if (!service) return [];
253
+ if (isMate && !includeGlobal()) return [];
254
+ var bound = session ? sessionBinding(session) : null;
255
+ if (session && !bound) return [];
256
+ return logsMcp.getToolDefs(toolHandlers(bound), isMate);
257
+ }
258
+
259
+ function createMcpServer(adapter, session) {
260
+ if (!service) return null;
261
+ if (isMate && !includeGlobal()) return null;
262
+ if (!isMate && session && !sessionBinding(session)) {
263
+ // A session that cannot bind still gets the fail-closed descriptor
264
+ // rather than a silently missing server.
265
+ return logsMcp.createMcpServer(adapter, null, false);
266
+ }
267
+ var bound = session ? sessionBinding(session) : null;
268
+ return logsMcp.createMcpServer(adapter, toolHandlers(bound), isMate);
269
+ }
270
+
271
+ function getDynamicToolDefs(session) {
272
+ return getToolDefs(session).map(function (tool) {
273
+ tool.permissionName = "mcp__clay-logs__" + tool.name;
274
+ return tool;
275
+ });
276
+ }
277
+
278
+ function getBridgeTools(session, normalizeSchema) {
279
+ if (!session) return [];
280
+ var defs = getToolDefs(session);
281
+ var tools = [];
282
+ for (var i = 0; i < defs.length; i++) {
283
+ tools.push({
284
+ server: "clay-logs",
285
+ name: defs[i].name,
286
+ description: defs[i].description || defs[i].name,
287
+ inputSchema: normalizeSchema(defs[i].inputSchema),
288
+ });
289
+ }
290
+ return tools;
291
+ }
292
+
293
+ function callBridgeTool(session, toolName, args) {
294
+ if (!session) return Promise.reject(new Error("Project Logs require a valid session."));
295
+ var defs = getToolDefs(session);
296
+ for (var i = 0; i < defs.length; i++) {
297
+ if (defs[i].name === toolName) return Promise.resolve(defs[i].handler(args || {}));
298
+ }
299
+ return Promise.reject(new Error("Project Logs tool not found: " + toolName));
300
+ }
301
+
302
+ // A count only. The comment bodies stay in the tool, so a pending queue is
303
+ // discoverable on the next turn without dumping the discussion into context
304
+ // and without starting a turn of its own.
305
+ function pendingFeedbackSignal(session) {
306
+ if (!session) return "";
307
+ var bound = sessionBinding(session);
308
+ if (!bound || typeof bound.listLogFeedback !== "function") return "";
309
+ var total = 0;
310
+ try {
311
+ total = bound.listLogFeedback({ limit: 1 }).total || 0;
312
+ } catch (e) {
313
+ return "";
314
+ }
315
+ if (!total) return "";
316
+ return "\n" + total + (total === 1 ? " comment awaits" : " comments await") +
317
+ " your review. Call list_log_feedback to see them.";
318
+ }
319
+
320
+ function getSystemPrompt(session) {
321
+ if (isMate || !service) return "";
322
+ if (session && !sessionBinding(session)) return "";
323
+ return SYSTEM_PROMPT_LABEL + "\n" + logsMcp.LOGS_CONTRACT +
324
+ "\n" + logsMcp.LEARNING_CONTRACT +
325
+ "\n" + logsMcp.REVIEW_CONTRACT + pendingFeedbackSignal(session);
326
+ }
327
+
328
+ return {
329
+ handleLogsMessage: handleLogsMessage,
330
+ createMcpServer: createMcpServer,
331
+ getToolDefs: getToolDefs,
332
+ getDynamicToolDefs: getDynamicToolDefs,
333
+ getBridgeTools: getBridgeTools,
334
+ callBridgeTool: callBridgeTool,
335
+ getSystemPrompt: getSystemPrompt,
336
+ };
337
+ }
338
+
339
+ module.exports = {
340
+ SYSTEM_PROMPT_LABEL: SYSTEM_PROMPT_LABEL,
341
+ NOTIFYING_OPS: NOTIFYING_OPS,
342
+ updateNotice: updateNotice,
343
+ HANDLED_TYPES: HANDLED_TYPES,
344
+ RETIRED_TYPES: RETIRED_TYPES,
345
+ attachProjectLogs: attachProjectLogs,
346
+ coerceToolArgs: coerceToolArgs,
347
+ };
@@ -0,0 +1,120 @@
1
+ var mateCreationMcp = require("./mate-creation-mcp-server");
2
+ var selectCatalogModel = require("./project-models").selectCatalogModel;
3
+
4
+ function attachMateCreationProposal(ctx) {
5
+ var pending = {};
6
+
7
+ function clean(value, maxLength) {
8
+ if (typeof value !== "string") return "";
9
+ return value.replace(/\u0000/g, "").trim().slice(0, maxLength);
10
+ }
11
+
12
+ function publicProposal(source) {
13
+ return {
14
+ proposalId: source.proposalId,
15
+ name: clean(source.name, 80),
16
+ bio: clean(source.bio, 280),
17
+ relationship: clean(source.relationship, 120),
18
+ activities: Array.isArray(source.activities) ? source.activities.slice(0, 12) : [],
19
+ communicationStyle: Array.isArray(source.communicationStyle) ? source.communicationStyle.slice(0, 12) : [],
20
+ autonomy: clean(source.autonomy, 500),
21
+ identityMarkdown: clean(source.identityMarkdown, 30000),
22
+ };
23
+ }
24
+
25
+ function record(session, event) {
26
+ if (typeof ctx.recordSessionEvent === "function") ctx.recordSessionEvent(session, event);
27
+ }
28
+
29
+ function validateBoundSession(session) {
30
+ var projectOwnerId = typeof ctx.getProjectOwnerId === "function" ? ctx.getProjectOwnerId() : null;
31
+ var ownerValid = typeof ctx.isMultiUser === "function" && ctx.isMultiUser() ? !!(session && session.ownerId && (!projectOwnerId || session.ownerId === projectOwnerId)) : !!(session && !session.ownerId);
32
+ return !!(session && ctx.isMate === true && ctx.isHostAgent === true && session.mateCreationMode === true
33
+ && ownerValid);
34
+ }
35
+
36
+ function hasInterviewAnswer(session) {
37
+ var history = session && Array.isArray(session.history) ? session.history : [];
38
+ for (var i = history.length - 1; i >= 0; i--) if (history[i] && history[i].type === "ask_user_answered") return true;
39
+ return false;
40
+ }
41
+
42
+ function getToolDefs(boundSession) {
43
+ if (!boundSession || boundSession.mateCreationMode !== true) return [];
44
+ return mateCreationMcp.getToolDefs(function (definition) {
45
+ if (!validateBoundSession(boundSession)) return Promise.resolve({ action: "error", error: "Mate proposals require an owned Clay creation interview." });
46
+ if (!hasInterviewAnswer(boundSession)) return Promise.resolve({ action: "error", error: "Interview the user before proposing a Mate." });
47
+ return new Promise(function (resolve) {
48
+ var proposalId = "mp_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
49
+ definition.proposalId = proposalId;
50
+ pending[proposalId] = { resolve: resolve, definition: definition, session: boundSession };
51
+ record(boundSession, { type: "mate_creation_proposal", proposal: publicProposal(definition) });
52
+ });
53
+ });
54
+ }
55
+
56
+ function createMcpServer(adapter, boundSession) {
57
+ return adapter.createToolServer({ name: "clay-mate-creation", version: "1.0.0", tools: getToolDefs(boundSession) });
58
+ }
59
+
60
+ function getBridgeTools(boundSession, normalizeSchema) {
61
+ if (!boundSession) return [];
62
+ return getToolDefs(boundSession).map(function (tool) {
63
+ return { server: "clay-mate-creation", name: tool.name, description: tool.description || tool.name, inputSchema: normalizeSchema(tool.inputSchema) };
64
+ });
65
+ }
66
+
67
+ function callBridgeTool(boundSession, toolName, args) {
68
+ var tools = getToolDefs(boundSession);
69
+ for (var i = 0; i < tools.length; i++) if (tools[i].name === toolName) return Promise.resolve(tools[i].handler(args || {}));
70
+ return Promise.reject(new Error("Session tool not found: clay-mate-creation/" + toolName));
71
+ }
72
+
73
+ function handleHomeMessage(ws, msg, session) {
74
+ var entry = msg && msg.proposalId ? pending[msg.proposalId] : null;
75
+ if (!entry || entry.session !== session || !validateBoundSession(session)) return false;
76
+ delete pending[msg.proposalId];
77
+ if (msg.action !== "create") {
78
+ record(session, { type: "mate_creation_proposal_resolved", proposalId: msg.proposalId, action: "cancel" });
79
+ entry.resolve({ action: "cancel" });
80
+ return true;
81
+ }
82
+ if (typeof ctx.createReadyMate !== "function") {
83
+ var unavailable = "Mate creation is unavailable.";
84
+ record(session, { type: "mate_creation_proposal_resolved", proposalId: msg.proposalId, action: "error", error: unavailable });
85
+ entry.resolve({ action: "error", error: unavailable });
86
+ return true;
87
+ }
88
+ var vendor = session.vendor || "claude";
89
+ if (typeof ctx.getVendorModelCatalog !== "function") {
90
+ var catalogUnavailable = "The Mate model catalog is unavailable.";
91
+ record(session, { type: "mate_creation_proposal_resolved", proposalId: msg.proposalId, action: "error", error: catalogUnavailable });
92
+ entry.resolve({ action: "error", error: catalogUnavailable });
93
+ return true;
94
+ }
95
+ Promise.resolve(ctx.getVendorModelCatalog(ws, vendor)).then(function (catalog) {
96
+ var selected = catalog && catalog.status === "ready" ? selectCatalogModel(catalog.models || [], catalog.defaultModel, "deep") : null;
97
+ if (!selected) throw new Error((catalog && catalog.error) || "No high-capability Mate model is available for " + vendor + ".");
98
+ return ctx.createReadyMate(ws, session.ownerId || null, Object.assign({}, entry.definition, { vendor: vendor, model: selected.value }));
99
+ }).then(function (mate) {
100
+ session.homeMateCreationPhase = "created";
101
+ record(session, { type: "mate_creation_proposal_resolved", proposalId: msg.proposalId, action: "create", mateId: mate.id, mateName: (mate.profile && mate.profile.displayName) || mate.name || "Mate" });
102
+ entry.resolve({ action: "create", mateId: mate.id });
103
+ }).catch(function (error) {
104
+ var text = error && error.message ? error.message : "The Mate could not be created.";
105
+ record(session, { type: "mate_creation_proposal_resolved", proposalId: msg.proposalId, action: "error", error: text });
106
+ entry.resolve({ action: "error", error: text });
107
+ });
108
+ return true;
109
+ }
110
+
111
+ return {
112
+ createMcpServer: createMcpServer,
113
+ getToolDefs: getToolDefs,
114
+ getBridgeTools: getBridgeTools,
115
+ callBridgeTool: callBridgeTool,
116
+ handleHomeMessage: handleHomeMessage,
117
+ };
118
+ }
119
+
120
+ module.exports = { attachMateCreationProposal: attachMateCreationProposal };
@@ -1,6 +1,18 @@
1
1
  var fs = require("fs");
2
2
  var path = require("path");
3
3
  var crypto = require("crypto");
4
+ var mateSync = require("./mate-knowledge-sync");
5
+
6
+ // Mirror a Mate-owned journal into the Clay-wide Knowledge backend after the
7
+ // legacy append has already succeeded. Reconciling the whole journal keeps the
8
+ // new scope current whether the write added, rewrote, or removed lines.
9
+ function mirrorMateJournal(mateDir, fileName, job) {
10
+ mateSync.syncQuietly({
11
+ mateDir: mateDir,
12
+ fileName: fileName,
13
+ actor: { type: "agent", vendor: (job && job.vendor) || null, sessionKey: (job && job.sessionKey) || null },
14
+ });
15
+ }
4
16
 
5
17
  function attachMateInteraction(ctx) {
6
18
  var cwd = ctx.cwd;
@@ -278,6 +290,7 @@ function attachMateInteraction(ctx) {
278
290
  fs.mkdirSync(knowledgeDir, { recursive: true });
279
291
  var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
280
292
  fs.appendFileSync(digestFile, JSON.stringify(digestObj) + "\n");
293
+ mirrorMateJournal(mateDir, "session-digests.jsonl", job);
281
294
  } catch (e) {
282
295
  console.error("[digest-worker] Write failed for " + job.mateId + ":", e.message);
283
296
  }
@@ -302,6 +315,7 @@ function attachMateInteraction(ctx) {
302
315
  }));
303
316
  }
304
317
  fs.appendFileSync(obsFile, obsLines.join("\n") + "\n");
318
+ mirrorMateJournal(mateDir, "user-observations.jsonl", job);
305
319
  } catch (e) {
306
320
  console.error("[digest-worker] Observations write failed for " + job.mateId + ":", e.message);
307
321
  }