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
@@ -1,313 +1,78 @@
1
- // Clay-history MCP Server
2
- // ------------------------
3
- // Tools that let the Clay host agent search and read across the user's
4
- // entire workspace (sessions, project memory, decision history). Scoped
5
- // strictly to the active user's data via the projectSessions accessor;
6
- // the Clay session never reads other users' files.
7
- //
8
- // Mounted only on host-agent mate projects (def.hostAgent === true), so
9
- // regular Mates and project sessions never see these tools.
10
- //
11
- // Usage:
12
- // var clayHistoryMcp = require("./clay-history-mcp-server");
13
- // var tools = clayHistoryMcp.getToolDefs({ getAllProjectsWithSessions: ..., readSessionRange: ... });
14
- // var mcpConfig = adapter.createToolServer({ name: "clay-history", version: "1.0.0", tools: tools });
15
-
16
- var fs = require("fs");
17
- var path = require("path");
18
- var sessionSearch = require("./session-search");
1
+ // Compatibility MCP surface for existing builtin Clay prompts. All reads
2
+ // delegate to the same exact-owner workspace query binding as clay-workspace.
19
3
 
20
4
  var z;
21
5
  try { z = require("zod"); } catch (e) { z = null; }
22
6
 
23
- function buildShape(props, required) {
7
+ function field(type, description, optional) {
24
8
  if (!z) return {};
25
- var shape = {};
26
- var keys = Object.keys(props);
27
- for (var i = 0; i < keys.length; i++) {
28
- var k = keys[i];
29
- var p = props[k];
30
- var field;
31
- if (p.type === "number") field = z.number();
32
- else if (p.type === "boolean") field = z.boolean();
33
- else if (p.enum) field = z.enum(p.enum);
34
- else field = z.string();
35
- if (p.description) field = field.describe(p.description);
36
- if (!required || required.indexOf(k) === -1) field = field.optional();
37
- shape[k] = field;
38
- }
39
- return shape;
9
+ var value = type === "number" ? z.number() : z.string();
10
+ if (description) value = value.describe(description);
11
+ return optional ? value.optional() : value;
40
12
  }
41
13
 
42
- // Heuristic patterns that suggest a "decision" was made. Kept conservative
43
- // so the result set stays small and useful. Matches case-insensitively.
44
- var DECISION_PATTERNS = [
45
- /\bdecided\s+to\b/i,
46
- /\bdecision\b/i,
47
- /\bgoing\s+with\b/i,
48
- /\bsettled\s+on\b/i,
49
- /\bchose\s+to\b/i,
50
- /\bwill\s+go\s+with\b/i,
51
- /\blet'?s\s+go\s+with\b/i,
52
- /\b결정\b/, // Korean "decision"
53
- /\b정했\b/, // Korean "settled/chose"
54
- /\b이걸로\s+가/, // Korean "going with this"
55
- ];
14
+ function result(value) {
15
+ return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
16
+ }
56
17
 
57
- function getToolDefs(deps) {
58
- var getAllProjectsWithSessions = deps.getAllProjectsWithSessions;
59
- if (typeof getAllProjectsWithSessions !== "function") {
60
- throw new Error("clay-history-mcp-server requires getAllProjectsWithSessions");
61
- }
18
+ function error(value) {
19
+ return Promise.resolve({ content: [{ type: "text", text: "Error: " + value }], isError: true });
20
+ }
62
21
 
63
- var tools = [];
22
+ function guarded(workspace, method, mapArgs) {
23
+ return function (args) {
24
+ if (!workspace || workspace.isClay !== true || typeof workspace[method] !== "function") {
25
+ return error("Clay history requires an exact session-bound builtin Clay query.");
26
+ }
27
+ try { return result(workspace[method](mapArgs ? mapArgs(args || {}) : (args || {}))); }
28
+ catch (e) { return error(e && e.message ? e.message : String(e)); }
29
+ };
30
+ }
64
31
 
65
- // --- search_clay_history ---
66
- // BM25 search across every session the user can see. Returns ranked
67
- // hits with snippet + project/session attribution. Optionally scoped
68
- // to a single project slug or a date window (since/until in ISO date
69
- // or unix-millis form).
70
- tools.push({
71
- name: "search_clay_history",
72
- description: "Search the user's entire workspace history for past conversations and decisions using BM25 ranking. Returns up to 30 hits with project, session ID, and a short snippet. Use this first for any 'what did I say about X' or 'when did I decide Y' question. Scope can be narrowed by projectSlug, since, or until.",
73
- inputSchema: buildShape({
74
- query: { type: "string", description: "Free-text search query. Multiple terms are AND-ish via BM25." },
75
- projectSlug: { type: "string", description: "Optional. Restrict to one project's sessions (matches the slug shown in the Clay sidebar)." },
76
- since: { type: "string", description: "Optional. Earliest activity date. Accepts ISO 8601 (2026-04-01) or unix milliseconds." },
77
- until: { type: "string", description: "Optional. Latest activity date. Same formats as 'since'." },
78
- maxResults: { type: "number", description: "Optional. Default 20, max 50." },
79
- }, ["query"]),
80
- handler: function (args) {
81
- try {
82
- var query = (args.query || "").trim();
83
- if (!query) {
84
- return Promise.resolve({
85
- content: [{ type: "text", text: "Empty query." }],
86
- isError: true,
87
- });
88
- }
89
- var maxResults = Math.min(50, Math.max(1, args.maxResults || 20));
90
- var projectSessions = getAllProjectsWithSessions();
91
- if (args.projectSlug) {
92
- projectSessions = projectSessions.filter(function (p) {
93
- return p.projectSlug === args.projectSlug;
94
- });
95
- }
96
- var sinceMs = parseTime(args.since);
97
- var untilMs = parseTime(args.until);
98
- if (sinceMs != null || untilMs != null) {
99
- projectSessions = projectSessions.map(function (p) {
100
- var filtered = (p.sessions || []).filter(function (s) {
101
- var t = s.lastActivity || s.createdAt || 0;
102
- if (sinceMs != null && t < sinceMs) return false;
103
- if (untilMs != null && t > untilMs) return false;
104
- return true;
105
- });
106
- return Object.assign({}, p, { sessions: filtered });
107
- }).filter(function (p) { return p.sessions.length > 0; });
108
- }
109
- var results = sessionSearch.searchPalette(projectSessions, query, { maxResults: maxResults });
110
- if (results.length === 0) {
111
- return Promise.resolve({
112
- content: [{ type: "text", text: "No matches for: " + query }],
113
- });
114
- }
115
- var lines = results.map(function (r) {
116
- var when = r.lastActivity ? new Date(r.lastActivity).toISOString().slice(0, 10) : "";
117
- var ref = "[" + r.projectSlug + "/" + r.sessionId + " — " + when + "]";
118
- var head = r.sessionTitle || "(untitled)";
119
- var body = r.snippet ? r.snippet.replace(/\s+/g, " ").trim() : "";
120
- if (body.length > 220) body = body.substring(0, 220) + "...";
121
- return ref + " " + head + (body ? " — " + body : "");
122
- });
123
- return Promise.resolve({
124
- content: [{ type: "text", text: lines.join("\n") }],
125
- });
126
- } catch (e) {
127
- return Promise.resolve({
128
- content: [{ type: "text", text: "Search failed: " + (e.message || String(e)) }],
129
- isError: true,
130
- });
131
- }
32
+ function getToolDefs(deps) {
33
+ var workspace = deps && deps.workspace;
34
+ return [
35
+ {
36
+ name: "search_clay_history",
37
+ description: "Search canonical user and assistant text across projects owned by the current user. Returns sanitized snippets and opaque session references.",
38
+ inputSchema: z ? {
39
+ query: field("string", "Search query."),
40
+ projectSlug: field("string", "Optional project slug.", true),
41
+ maxResults: field("number", "Maximum results, from 1 to 50.", true),
42
+ } : {},
43
+ handler: guarded(workspace, "searchWorkspaceHistory", function (args) {
44
+ if (args.projectSlug) return { projectSlug: args.projectSlug, query: args.query, limit: args.maxResults };
45
+ return { query: args.query, limit: args.maxResults };
46
+ }),
132
47
  },
133
- });
134
-
135
- // --- read_session ---
136
- // Pull a window of turns from a specific session. Use this after
137
- // search_clay_history identifies a session worth reading more of.
138
- tools.push({
139
- name: "read_session",
140
- description: "Read a window of turns from a specific session. Call this after search_clay_history identifies an interesting hit — the snippet there is short, this gives you the surrounding context. Returns user_message and assistant text turns; tool calls are summarized.",
141
- inputSchema: buildShape({
142
- projectSlug: { type: "string", description: "Project slug, e.g. 'clay' or 'mate-abc123'." },
143
- sessionId: { type: "string", description: "Session local ID (e.g. 'sess_abc123')." },
144
- offset: { type: "number", description: "Optional. Skip the first N turns. Default 0." },
145
- limit: { type: "number", description: "Optional. Max turns to return. Default 30, max 100." },
146
- }, ["projectSlug", "sessionId"]),
147
- handler: function (args) {
148
- try {
149
- var projectSlug = args.projectSlug;
150
- var sessionId = args.sessionId;
151
- var offset = Math.max(0, args.offset || 0);
152
- var limit = Math.min(100, Math.max(1, args.limit || 30));
153
-
154
- var projectSessions = getAllProjectsWithSessions();
155
- var found = null;
156
- for (var p = 0; p < projectSessions.length; p++) {
157
- if (projectSessions[p].projectSlug !== projectSlug) continue;
158
- var sessions = projectSessions[p].sessions || [];
159
- for (var s = 0; s < sessions.length; s++) {
160
- if (sessions[s].localId === sessionId) {
161
- found = { project: projectSessions[p], session: sessions[s] };
162
- break;
163
- }
164
- }
165
- if (found) break;
166
- }
167
- if (!found) {
168
- return Promise.resolve({
169
- content: [{ type: "text", text: "Session not found: " + projectSlug + "/" + sessionId }],
170
- isError: true,
171
- });
172
- }
173
- var history = found.session.history || [];
174
- var slice = history.slice(offset, offset + limit);
175
- if (slice.length === 0) {
176
- return Promise.resolve({
177
- content: [{ type: "text", text: "No turns in window (offset=" + offset + ", limit=" + limit + ", total=" + history.length + ")." }],
178
- });
179
- }
180
- var out = [];
181
- out.push("# " + (found.session.title || "untitled") + " — " + projectSlug + "/" + sessionId);
182
- out.push("Showing turns " + (offset + 1) + "-" + (offset + slice.length) + " of " + history.length + "\n");
183
- for (var i = 0; i < slice.length; i++) {
184
- var entry = slice[i];
185
- var label;
186
- var text = "";
187
- if (entry.type === "user_message") {
188
- label = "USER";
189
- text = entry.text || "";
190
- } else if (entry.type === "delta") {
191
- label = "ASSISTANT";
192
- text = entry.text || "";
193
- } else if (entry.type === "tool_executing" || entry.type === "tool_result") {
194
- label = "TOOL";
195
- text = (entry.name || "") + (entry.input ? " " + JSON.stringify(entry.input).substring(0, 120) : "");
196
- } else {
197
- continue;
198
- }
199
- if (text.length > 800) text = text.substring(0, 800) + "...";
200
- out.push("[" + label + "] " + text);
201
- }
202
- return Promise.resolve({
203
- content: [{ type: "text", text: out.join("\n") }],
204
- });
205
- } catch (e) {
206
- return Promise.resolve({
207
- content: [{ type: "text", text: "read_session failed: " + (e.message || String(e)) }],
208
- isError: true,
209
- });
210
- }
48
+ {
49
+ name: "read_session",
50
+ description: "Read canonical user and assistant turns from an owned session. Raw tool payloads and internal prompts are excluded.",
51
+ inputSchema: z ? {
52
+ projectSlug: field("string", "Exact project slug."),
53
+ sessionId: field("string", "Opaque session reference returned by search_clay_history."),
54
+ offset: field("number", "Legacy numeric turn offset.", true),
55
+ limit: field("number", "Maximum turns, from 1 to 50.", true),
56
+ } : {},
57
+ handler: guarded(workspace, "readProjectSession", function (args) {
58
+ var cursor = args.offset > 0 ? Buffer.from(String(Math.floor(args.offset)), "utf8").toString("base64url") : null;
59
+ return { projectSlug: args.projectSlug, sessionRef: args.sessionId, cursor: cursor, limit: args.limit };
60
+ }),
211
61
  },
212
- });
213
-
214
- // --- list_recent_decisions ---
215
- // Heuristic. Scans user_message and assistant text turns for phrases
216
- // that suggest a decision was made. Useful when the user asks
217
- // "what did I decide about X recently". Returns ranked-by-recency.
218
- tools.push({
219
- name: "list_recent_decisions",
220
- description: "Find turns that mention an explicit decision (decided to / going with / 결정 / 정했 etc.). Use when the user asks about recent decisions. Returns chronologically with the project and session the decision was made in. Scope by projectSlug or since/until.",
221
- inputSchema: buildShape({
222
- projectSlug: { type: "string", description: "Optional. Restrict to one project." },
223
- since: { type: "string", description: "Optional. Earliest activity date (ISO or unix-ms)." },
224
- until: { type: "string", description: "Optional. Latest activity date." },
225
- maxResults: { type: "number", description: "Optional. Default 15, max 30." },
226
- }),
227
- handler: function (args) {
228
- try {
229
- var maxResults = Math.min(30, Math.max(1, args.maxResults || 15));
230
- var sinceMs = parseTime(args.since);
231
- var untilMs = parseTime(args.until);
232
- var projectSessions = getAllProjectsWithSessions();
233
- if (args.projectSlug) {
234
- projectSessions = projectSessions.filter(function (p) {
235
- return p.projectSlug === args.projectSlug;
236
- });
237
- }
238
- var hits = [];
239
- for (var p = 0; p < projectSessions.length; p++) {
240
- var proj = projectSessions[p];
241
- var sessions = proj.sessions || [];
242
- for (var s = 0; s < sessions.length; s++) {
243
- var session = sessions[s];
244
- var t = session.lastActivity || session.createdAt || 0;
245
- if (sinceMs != null && t < sinceMs) continue;
246
- if (untilMs != null && t > untilMs) continue;
247
- var history = session.history || [];
248
- for (var h = 0; h < history.length; h++) {
249
- var entry = history[h];
250
- if (entry.type !== "user_message" && entry.type !== "delta") continue;
251
- var text = entry.text || "";
252
- if (!text) continue;
253
- if (!matchesDecisionPattern(text)) continue;
254
- hits.push({
255
- projectSlug: proj.projectSlug,
256
- projectTitle: proj.projectTitle,
257
- sessionId: session.localId,
258
- sessionTitle: session.title || "(untitled)",
259
- lastActivity: t,
260
- turnIdx: h,
261
- turnType: entry.type === "user_message" ? "user" : "assistant",
262
- text: text,
263
- });
264
- }
265
- }
266
- }
267
- hits.sort(function (a, b) { return b.lastActivity - a.lastActivity; });
268
- if (hits.length > maxResults) hits = hits.slice(0, maxResults);
269
- if (hits.length === 0) {
270
- return Promise.resolve({
271
- content: [{ type: "text", text: "No decision-pattern matches in scope." }],
272
- });
273
- }
274
- var lines = hits.map(function (h) {
275
- var when = h.lastActivity ? new Date(h.lastActivity).toISOString().slice(0, 10) : "";
276
- var ref = "[" + h.projectSlug + "/" + h.sessionId + " — " + when + "]";
277
- var snippet = h.text.replace(/\s+/g, " ").trim();
278
- if (snippet.length > 220) snippet = snippet.substring(0, 220) + "...";
279
- return ref + " (" + h.turnType + ") " + snippet;
280
- });
281
- return Promise.resolve({
282
- content: [{ type: "text", text: lines.join("\n") }],
283
- });
284
- } catch (e) {
285
- return Promise.resolve({
286
- content: [{ type: "text", text: "list_recent_decisions failed: " + (e.message || String(e)) }],
287
- isError: true,
288
- });
289
- }
62
+ {
63
+ name: "list_recent_decisions",
64
+ description: "List recent canonical user or assistant turns containing explicit decision language in the owned workspace.",
65
+ inputSchema: z ? {
66
+ projectSlug: field("string", "Optional project slug.", true),
67
+ since: field("string", "Optional earliest ISO date.", true),
68
+ until: field("string", "Optional latest ISO date.", true),
69
+ maxResults: field("number", "Maximum results, from 1 to 50.", true),
70
+ } : {},
71
+ handler: guarded(workspace, "listRecentDecisions", function (args) {
72
+ return { projectSlug: args.projectSlug, since: args.since, until: args.until, limit: args.maxResults };
73
+ }),
290
74
  },
291
- });
292
-
293
- return tools;
294
- }
295
-
296
- function parseTime(input) {
297
- if (input == null || input === "") return null;
298
- if (typeof input === "number") return input;
299
- var s = String(input).trim();
300
- if (/^\d{10,}$/.test(s)) return parseInt(s, 10); // unix ms
301
- var d = new Date(s);
302
- var n = d.getTime();
303
- return isNaN(n) ? null : n;
304
- }
305
-
306
- function matchesDecisionPattern(text) {
307
- for (var i = 0; i < DECISION_PATTERNS.length; i++) {
308
- if (DECISION_PATTERNS[i].test(text)) return true;
309
- }
310
- return false;
75
+ ];
311
76
  }
312
77
 
313
78
  module.exports = { getToolDefs: getToolDefs };
package/lib/daemon.js CHANGED
@@ -34,6 +34,7 @@ var { checkAclSupport, grantProjectAccess, revokeProjectAccess, provisionAllUser
34
34
  var usersModule = require("./users");
35
35
  var { createWorktree, removeWorktree, isWorktree } = require("./worktree");
36
36
  var mates = require("./mates");
37
+ var mateKnowledgeMigration = require("./mate-knowledge-migration");
37
38
  var { isWorktreeSlug, scanAndRegisterWorktrees, rescanWorktrees, cleanupWorktreesForParent, getFilteredRemovedProjects, registerWorktreeSlug, unregisterWorktreeSlug } = require("./daemon-projects");
38
39
 
39
40
  var daemonVersion = require("../package.json").version;
@@ -814,7 +815,7 @@ var relay = createServer({
814
815
  var want = !!value;
815
816
  config.matesEnabled = want;
816
817
  saveConfig(config);
817
- console.log("[daemon] Mates UI:", want ? "on" : "off", "(web)");
818
+ console.log("[daemon] Project Mate DMs:", want ? "shown" : "hidden", "(web)");
818
819
  return { ok: true, matesEnabled: want };
819
820
  },
820
821
  onGetToolPalettes: function () {
@@ -1294,6 +1295,27 @@ for (var i = 0; i < projects.length; i++) {
1294
1295
  // Migrate legacy mates storage before registration
1295
1296
  mates.migrateLegacyMates();
1296
1297
 
1298
+ // Import durable Mate knowledge into the Clay-wide Knowledge backend. Runs on
1299
+ // every start, imports nothing when there is nothing new, and never touches
1300
+ // the legacy files that remain the live source for existing read paths.
1301
+ mateKnowledgeMigration.runAtStartup({
1302
+ extraRoots: (function () {
1303
+ var roots = [];
1304
+ try {
1305
+ if (!usersModule.isMultiUser()) return roots;
1306
+ var all = usersModule.getAllUsers() || [];
1307
+ for (var mi = 0; mi < all.length; mi++) {
1308
+ if (!all[mi] || !all[mi].linuxUser) continue;
1309
+ roots.push({
1310
+ userId: all[mi].id,
1311
+ matesRoot: mates.resolveMatesRoot({ userId: all[mi].id, multiUser: true, linuxUser: all[mi].linuxUser }),
1312
+ });
1313
+ }
1314
+ } catch (e) {}
1315
+ return roots;
1316
+ })(),
1317
+ });
1318
+
1297
1319
  // Register existing mates as projects
1298
1320
  if (usersModule.isMultiUser()) {
1299
1321
  // Multi-user mode: iterate over all users and load each user's mates
@@ -0,0 +1,119 @@
1
+ var projectModels = require("./project-models");
2
+
3
+ var MAX_MODELS = 100;
4
+
5
+ function modelValue(entry) {
6
+ return projectModels.modelEntryValue(entry);
7
+ }
8
+
9
+ function modelLabel(entry) {
10
+ if (typeof entry === "string") return entry;
11
+ return entry && (entry.displayName || entry.label || entry.name || modelValue(entry)) || "Model";
12
+ }
13
+
14
+ function findModel(models, candidate) {
15
+ if (!candidate) return "";
16
+ for (var i = 0; i < models.length; i++) {
17
+ if (projectModels.modelEntryMatches(models[i], candidate)) return modelValue(models[i]);
18
+ }
19
+ return "";
20
+ }
21
+
22
+ function participantIds(moderatorId, panelists) {
23
+ var ids = [moderatorId];
24
+ for (var i = 0; i < panelists.length; i++) ids.push(panelists[i].mateId);
25
+ return ids;
26
+ }
27
+
28
+ async function loadParticipant(ctx, ws, mateCtx, mateId, role, sourceSession) {
29
+ var mate = ctx.getMate(mateCtx, mateId);
30
+ if (!mate) return null;
31
+ var vendor = mate.vendor || "claude";
32
+ var catalog;
33
+ try {
34
+ if (sourceSession && typeof ctx.getVendorModelCatalogForSession === "function") catalog = await ctx.getVendorModelCatalogForSession(sourceSession, vendor);
35
+ else catalog = await ctx.getVendorModelCatalog(ws, vendor);
36
+ } catch (error) {
37
+ catalog = { models: [], error: error && error.message ? error.message : String(error) };
38
+ }
39
+ var sourceModels = Array.isArray(catalog.models) ? catalog.models.slice(0, MAX_MODELS) : [];
40
+ var models = [];
41
+ var seen = {};
42
+ for (var i = 0; i < sourceModels.length; i++) {
43
+ var value = modelValue(sourceModels[i]);
44
+ if (typeof value !== "string" || !value || seen[value]) continue;
45
+ seen[value] = true;
46
+ models.push({ value: value.slice(0, 300), label: modelLabel(sourceModels[i]).slice(0, 160) });
47
+ }
48
+ var selected = findModel(sourceModels, mate.model) || findModel(sourceModels, catalog.defaultModel);
49
+ if (!selected && models.length) selected = models[0].value;
50
+ if (!selected && typeof mate.model === "string" && mate.model) {
51
+ selected = mate.model;
52
+ models.push({ value: mate.model, label: mate.model });
53
+ }
54
+ return {
55
+ mateId: mateId,
56
+ role: role,
57
+ vendor: vendor,
58
+ selectedModel: selected || "",
59
+ models: models,
60
+ status: models.length ? "ready" : "unavailable",
61
+ error: models.length ? "" : (catalog.error || "No models are available for this provider."),
62
+ };
63
+ }
64
+
65
+ async function loadSelections(ctx, ws, mateCtx, moderatorId, panelists, sourceSession) {
66
+ var ids = participantIds(moderatorId, panelists);
67
+ var selections = [];
68
+ for (var i = 0; i < ids.length; i++) {
69
+ var selection = await loadParticipant(ctx, ws, mateCtx, ids[i], i === 0 ? "moderator" : "panelist", sourceSession);
70
+ if (selection) selections.push(selection);
71
+ }
72
+ return selections;
73
+ }
74
+
75
+ function requestedByMate(overrides) {
76
+ if (overrides == null) return { values: {} };
77
+ if (!Array.isArray(overrides) || overrides.length > 12) return { error: "The debate model selection is invalid." };
78
+ var result = {};
79
+ for (var i = 0; i < overrides.length; i++) {
80
+ var item = overrides[i];
81
+ if (!item || typeof item.mateId !== "string" || typeof item.model !== "string" || !item.mateId || !item.model || result[item.mateId]) {
82
+ return { error: "The debate model selection is invalid." };
83
+ }
84
+ result[item.mateId] = item.model.slice(0, 300);
85
+ }
86
+ return { values: result };
87
+ }
88
+
89
+ async function validateSelections(ctx, ws, mateCtx, moderatorId, panelists, overrides, sourceSession) {
90
+ var selections = await loadSelections(ctx, ws, mateCtx, moderatorId, panelists, sourceSession);
91
+ var requestResult = requestedByMate(overrides);
92
+ if (requestResult.error) return { error: requestResult.error };
93
+ var requested = requestResult.values;
94
+ var allowed = {};
95
+ var resolved = [];
96
+ for (var i = 0; i < selections.length; i++) {
97
+ var selection = selections[i];
98
+ allowed[selection.mateId] = true;
99
+ var requestedModel = requested[selection.mateId] || selection.selectedModel;
100
+ var selected = "";
101
+ for (var j = 0; j < selection.models.length; j++) {
102
+ if (selection.models[j].value === requestedModel) selected = requestedModel;
103
+ }
104
+ if (requested[selection.mateId] && !selected) {
105
+ return { error: "A selected debate model is no longer available. Review the panel models and try again." };
106
+ }
107
+ if (selected) resolved.push({ mateId: selection.mateId, vendor: selection.vendor, model: selected });
108
+ }
109
+ var requestedIds = Object.keys(requested);
110
+ for (var k = 0; k < requestedIds.length; k++) {
111
+ if (!allowed[requestedIds[k]]) return { error: "A model override does not belong to this debate panel." };
112
+ }
113
+ return { selections: resolved };
114
+ }
115
+
116
+ module.exports = {
117
+ loadSelections: loadSelections,
118
+ validateSelections: validateSelections,
119
+ };