clay-server 3.8.1 → 4.0.0-beta.2

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 (189) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/builtin-mates.js +6 -6
  3. package/lib/claude-hook-installer.js +15 -0
  4. package/lib/clay-history-mcp-server.js +62 -297
  5. package/lib/daemon.js +1 -1
  6. package/lib/debate-model-selection.js +119 -0
  7. package/lib/home-debate-tool-policy.js +164 -0
  8. package/lib/home-response-notifications.js +26 -0
  9. package/lib/mate-creation-mcp-server.js +55 -0
  10. package/lib/mate-ready-creation.js +95 -0
  11. package/lib/mates.js +2 -0
  12. package/lib/notes.js +2 -1
  13. package/lib/project-ask-user.js +64 -0
  14. package/lib/project-capsule-catalog.js +101 -0
  15. package/lib/project-connection.js +4 -0
  16. package/lib/project-debate-proposal.js +133 -17
  17. package/lib/project-debate.js +214 -110
  18. package/lib/project-delegated-follow-up.js +84 -0
  19. package/lib/project-delegated-session.js +53 -0
  20. package/lib/project-filesystem.js +7 -9
  21. package/lib/project-knowledge.js +1 -0
  22. package/lib/project-mate-creation-proposal.js +120 -0
  23. package/lib/project-memory.js +14 -7
  24. package/lib/project-models.js +157 -32
  25. package/lib/project-session-notes.js +2 -2
  26. package/lib/project-session-pair.js +8 -8
  27. package/lib/project-sessions.js +66 -51
  28. package/lib/project-vendor-login.js +397 -0
  29. package/lib/project-worker-proposal.js +25 -25
  30. package/lib/project-workspace-query.js +144 -0
  31. package/lib/project.js +349 -102
  32. package/lib/public/app.js +85 -42
  33. package/lib/public/css/avatar-imprints.css +0 -1
  34. package/lib/public/css/base.css +4 -0
  35. package/lib/public/css/capsule-ui.css +389 -0
  36. package/lib/public/css/command-palette.css +306 -6
  37. package/lib/public/css/home-capsule-library.css +243 -0
  38. package/lib/public/css/home-capsule-source.css +192 -0
  39. package/lib/public/css/home-debate-live.css +146 -0
  40. package/lib/public/css/home-debate-planning.css +125 -0
  41. package/lib/public/css/home-debates-archive.css +281 -0
  42. package/lib/public/css/home-hub.css +896 -405
  43. package/lib/public/css/home-mate-creation.css +34 -0
  44. package/lib/public/css/home-mate-model-picker.css +131 -0
  45. package/lib/public/css/home-mate-settings.css +344 -0
  46. package/lib/public/css/home-session-actions.css +265 -0
  47. package/lib/public/css/home-sidebar.css +667 -0
  48. package/lib/public/css/icon-strip.css +1 -2
  49. package/lib/public/css/mates.css +7 -34
  50. package/lib/public/css/pane.css +1 -1
  51. package/lib/public/css/playbook.css +0 -80
  52. package/lib/public/css/sticky-notes.css +53 -117
  53. package/lib/public/css/tui-attention.css +0 -44
  54. package/lib/public/css/workspace-assignment.css +45 -0
  55. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  56. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  57. package/lib/public/index.html +93 -74
  58. package/lib/public/modules/app-connection.js +10 -1
  59. package/lib/public/modules/app-dm.js +43 -160
  60. package/lib/public/modules/app-home-hub.js +328 -585
  61. package/lib/public/modules/app-message-cards.js +242 -0
  62. package/lib/public/modules/app-message-router.js +84 -0
  63. package/lib/public/modules/app-messages.js +102 -37
  64. package/lib/public/modules/app-notifications.js +8 -119
  65. package/lib/public/modules/app-projects.js +9 -1
  66. package/lib/public/modules/app-rendering.js +21 -602
  67. package/lib/public/modules/avatar-imprint.js +11 -8
  68. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  69. package/lib/public/modules/chat-render-runtime.js +198 -0
  70. package/lib/public/modules/command-palette.js +225 -436
  71. package/lib/public/modules/dm-render.js +82 -0
  72. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  73. package/lib/public/modules/home-capsule-library.js +146 -0
  74. package/lib/public/modules/home-capsule-source.js +383 -0
  75. package/lib/public/modules/home-chat-identity.js +35 -0
  76. package/lib/public/modules/home-chat-scroll.js +77 -0
  77. package/lib/public/modules/home-chat-stream-state.js +34 -0
  78. package/lib/public/modules/home-composer-focus.js +25 -0
  79. package/lib/public/modules/home-conversations-sheet.js +179 -0
  80. package/lib/public/modules/home-debate-activity.js +48 -0
  81. package/lib/public/modules/home-debate-controls.js +295 -0
  82. package/lib/public/modules/home-debate-launch.js +41 -0
  83. package/lib/public/modules/home-debate-live.js +284 -0
  84. package/lib/public/modules/home-debate-models.js +73 -0
  85. package/lib/public/modules/home-debate-planning.js +335 -0
  86. package/lib/public/modules/home-debates-archive.js +279 -0
  87. package/lib/public/modules/home-dock-resize.js +74 -0
  88. package/lib/public/modules/home-dock.js +446 -0
  89. package/lib/public/modules/home-mate-chat.js +496 -0
  90. package/lib/public/modules/home-mate-creation.js +109 -0
  91. package/lib/public/modules/home-mate-model-picker.js +281 -0
  92. package/lib/public/modules/home-mate-selection.js +38 -0
  93. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  94. package/lib/public/modules/home-mate-settings.js +383 -0
  95. package/lib/public/modules/home-session-actions.js +373 -0
  96. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  97. package/lib/public/modules/home-shell.js +9 -0
  98. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  99. package/lib/public/modules/home-sidebar.js +158 -0
  100. package/lib/public/modules/home-sub-surface.js +19 -0
  101. package/lib/public/modules/home-surface-boot.js +68 -0
  102. package/lib/public/modules/home-surface.js +141 -0
  103. package/lib/public/modules/home-tools.js +271 -0
  104. package/lib/public/modules/markdown.js +50 -2
  105. package/lib/public/modules/mate-management.js +86 -0
  106. package/lib/public/modules/mate-wizard.js +0 -7
  107. package/lib/public/modules/mention.js +17 -19
  108. package/lib/public/modules/pane-bridge.js +9 -0
  109. package/lib/public/modules/profile.js +4 -5
  110. package/lib/public/modules/project-settings.js +34 -28
  111. package/lib/public/modules/project-switcher.js +11 -5
  112. package/lib/public/modules/project-workspace-assignment.js +23 -0
  113. package/lib/public/modules/search-clay-chat.js +402 -0
  114. package/lib/public/modules/server-settings.js +14 -12
  115. package/lib/public/modules/session-actions.js +1 -1
  116. package/lib/public/modules/sidebar-mates.js +56 -552
  117. package/lib/public/modules/sidebar-mobile.js +7 -64
  118. package/lib/public/modules/sidebar-presence.js +37 -0
  119. package/lib/public/modules/split-pair-ui.js +13 -13
  120. package/lib/public/modules/split-view.js +7 -0
  121. package/lib/public/modules/sticky-notes.js +7 -6
  122. package/lib/public/modules/tool-input-composition.js +48 -0
  123. package/lib/public/modules/tool-llm-status.js +167 -0
  124. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  125. package/lib/public/modules/tool-renderer-chart.js +158 -0
  126. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  127. package/lib/public/modules/tool-renderer.js +464 -0
  128. package/lib/public/modules/tool-runtime.js +309 -0
  129. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  130. package/lib/public/modules/tool-ui-tree.js +21 -0
  131. package/lib/public/modules/tui-attention.js +10 -0
  132. package/lib/public/modules/user-settings.js +16 -23
  133. package/lib/public/modules/vendor-login.js +287 -0
  134. package/lib/public/modules/worker-proposal.js +2 -2
  135. package/lib/public/modules/workspace-assignment-card.js +149 -0
  136. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  137. package/lib/public/style.css +13 -2
  138. package/lib/runtime-env.js +65 -0
  139. package/lib/sdk-bridge.js +120 -32
  140. package/lib/sdk-message-processor.js +10 -6
  141. package/lib/server-home-capsule-creation.js +36 -0
  142. package/lib/server-home-chat-events.js +287 -0
  143. package/lib/server-home-chat.js +518 -0
  144. package/lib/server-home-clay-entry.js +65 -0
  145. package/lib/server-home-clay-session-links.js +26 -0
  146. package/lib/server-home-debate-planning.js +214 -0
  147. package/lib/server-home-debates.js +137 -0
  148. package/lib/server-home-mate-creation.js +261 -0
  149. package/lib/server-home-models.js +241 -0
  150. package/lib/server-home-preferences.js +61 -0
  151. package/lib/server-mates.js +37 -0
  152. package/lib/server-tools.js +377 -0
  153. package/lib/server.js +111 -59
  154. package/lib/session-pair-mcp-server.js +6 -6
  155. package/lib/sessions.js +18 -3
  156. package/lib/tool-capsule-source.js +142 -0
  157. package/lib/tool-control-mcp-server.js +126 -0
  158. package/lib/tool-llm.js +48 -0
  159. package/lib/tool-storage.js +77 -0
  160. package/lib/tool-ui-spec-advanced.js +82 -0
  161. package/lib/tool-ui-spec.js +261 -0
  162. package/lib/tools-registry.js +286 -0
  163. package/lib/users-home-dock-preferences.js +77 -0
  164. package/lib/users-home-surface-preferences.js +99 -0
  165. package/lib/users-preferences.js +4 -7
  166. package/lib/users.js +12 -0
  167. package/lib/whats-new-content.js +3 -3
  168. package/lib/workspace-assignment-service.js +420 -0
  169. package/lib/workspace-query-mcp-server.js +154 -0
  170. package/lib/workspace-query-service.js +492 -0
  171. package/lib/ws-schema.js +93 -12
  172. package/lib/yoke/adapters/acp.js +2 -1
  173. package/lib/yoke/adapters/antigravity.js +2 -1
  174. package/lib/yoke/adapters/claude-worker.js +11 -4
  175. package/lib/yoke/adapters/claude.js +73 -43
  176. package/lib/yoke/adapters/codex.js +40 -10
  177. package/lib/yoke/adapters/kiro.js +2 -1
  178. package/lib/yoke/claude-user-input.js +38 -0
  179. package/lib/yoke/codex-app-server.js +20 -2
  180. package/lib/yoke/codex-user-input.js +64 -0
  181. package/lib/yoke/complete-once.js +164 -0
  182. package/lib/yoke/index.js +5 -1
  183. package/lib/yoke/interface.js +19 -0
  184. package/lib/yoke/mcp-bridge-server.js +2 -6
  185. package/lib/yoke/user-input.js +254 -0
  186. package/package.json +3 -2
  187. package/lib/public/css/home-chat.css +0 -380
  188. package/lib/public/modules/home-chat.js +0 -438
  189. package/lib/server-clay-home.js +0 -245
@@ -0,0 +1,154 @@
1
+ var z;
2
+ try { z = require("zod"); } catch (e) { z = null; }
3
+
4
+ function field(type, description, optional) {
5
+ if (!z) return {};
6
+ var value = type === "number" ? z.number() : z.string();
7
+ if (description) value = value.describe(description);
8
+ return optional ? value.optional() : value;
9
+ }
10
+
11
+ function shape(fields) {
12
+ if (!z) return {};
13
+ return fields;
14
+ }
15
+
16
+ function textResult(value) {
17
+ return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
18
+ }
19
+
20
+ function errorResult(error) {
21
+ return Promise.resolve({
22
+ content: [{ type: "text", text: "Error: " + (error && error.message ? error.message : String(error)) }],
23
+ isError: true,
24
+ });
25
+ }
26
+
27
+ function handler(bound, method) {
28
+ return function (args) {
29
+ if (!bound || typeof bound[method] !== "function") return errorResult(new Error("Workspace tools require an exact session-bound Mate query."));
30
+ try { return textResult(bound[method](args || {})); }
31
+ catch (e) { return errorResult(e); }
32
+ };
33
+ }
34
+
35
+ var SESSION_CITATION = " When citing a returned session in user-visible text, write [clayos/<sessionRef> — short label] so Clay Studio renders an owner-validated session control.";
36
+
37
+ function commonTools(bound) {
38
+ return [
39
+ {
40
+ name: "list_projects",
41
+ description: "List projects owned by the current user. Returns sanitized summaries only; use a project-specific tool to inspect sessions or history.",
42
+ inputSchema: shape({
43
+ query: field("string", "Optional project title or slug filter.", true),
44
+ cursor: field("string", "Opaque pagination cursor from a previous response.", true),
45
+ limit: field("number", "Page size, from 1 to 50.", true),
46
+ }),
47
+ handler: handler(bound, "listProjects"),
48
+ },
49
+ {
50
+ name: "list_project_sessions",
51
+ description: "List sanitized session summaries for one owned project, newest activity first." + SESSION_CITATION,
52
+ inputSchema: shape({
53
+ projectSlug: field("string", "Exact project slug."),
54
+ cursor: field("string", "Opaque pagination cursor from a previous response.", true),
55
+ limit: field("number", "Page size, from 1 to 50.", true),
56
+ }),
57
+ handler: handler(bound, "listProjectSessions"),
58
+ },
59
+ {
60
+ name: "search_project_history",
61
+ description: "Search canonical user and assistant text in one owned project. Tool inputs, tool results, hidden prompts, and internal paths are excluded." + SESSION_CITATION,
62
+ inputSchema: shape({
63
+ projectSlug: field("string", "Exact project slug."),
64
+ query: field("string", "Search query."),
65
+ cursor: field("string", "Opaque pagination cursor from a previous response.", true),
66
+ limit: field("number", "Page size, from 1 to 50.", true),
67
+ }),
68
+ handler: handler(bound, "searchProjectHistory"),
69
+ },
70
+ {
71
+ name: "read_project_session",
72
+ description: "Read a bounded page of canonical user and assistant turns from one owned session using the opaque session reference returned by list or search." + SESSION_CITATION,
73
+ inputSchema: shape({
74
+ projectSlug: field("string", "Exact project slug."),
75
+ sessionRef: field("string", "Opaque session reference returned by a workspace list or search tool."),
76
+ cursor: field("string", "Opaque pagination cursor from a previous response.", true),
77
+ limit: field("number", "Page size, from 1 to 50.", true),
78
+ }),
79
+ handler: handler(bound, "readProjectSession"),
80
+ },
81
+ {
82
+ name: "propose_project_assignment",
83
+ description: "Propose a new delegated session in an owned project. This posts a user-visible approval card and performs no target work until explicit approval.",
84
+ inputSchema: shape({
85
+ projectSlug: field("string", "Exact owned target project slug."),
86
+ title: field("string", "Short delegated session title."),
87
+ task: field("string", "Standalone bounded task without hidden source transcript."),
88
+ }),
89
+ handler: handler(bound, "proposeProjectAssignment"),
90
+ },
91
+ {
92
+ name: "propose_project_follow_up",
93
+ description: "Propose a bounded follow-up in an existing durable private session. This posts a user-visible approval card and performs no target work until explicit approval.",
94
+ inputSchema: shape({
95
+ projectSlug: field("string", "Exact owned target project slug."),
96
+ targetSessionRef: field("string", "Durable opaque session reference returned by list_project_sessions."),
97
+ title: field("string", "Short follow-up title."),
98
+ task: field("string", "Standalone bounded follow-up without hidden source transcript."),
99
+ }),
100
+ handler: handler(bound, "proposeProjectFollowUp"),
101
+ },
102
+ {
103
+ name: "get_assignment_status",
104
+ description: "Read a new-session or follow-up assignment status for this exact Mate conversation.",
105
+ inputSchema: shape({ assignmentId: field("string", "Assignment ID returned by propose_project_assignment.") }),
106
+ handler: handler(bound, "getAssignmentStatus"),
107
+ },
108
+ ];
109
+ }
110
+
111
+ function globalTools(bound) {
112
+ return [
113
+ {
114
+ name: "search_workspace_history",
115
+ description: "Search canonical user and assistant text across every project owned by the current user. Available only to authoritative builtin Clay." + SESSION_CITATION,
116
+ inputSchema: shape({
117
+ query: field("string", "Search query."),
118
+ cursor: field("string", "Opaque pagination cursor from a previous response.", true),
119
+ limit: field("number", "Page size, from 1 to 50.", true),
120
+ }),
121
+ handler: handler(bound, "searchWorkspaceHistory"),
122
+ },
123
+ {
124
+ name: "list_workspace_activity",
125
+ description: "List recent session activity across every project owned by the current user. Available only to authoritative builtin Clay." + SESSION_CITATION,
126
+ inputSchema: shape({
127
+ status: field("string", "Optional status: idle, processing, or waiting_for_approval.", true),
128
+ cursor: field("string", "Opaque pagination cursor from a previous response.", true),
129
+ limit: field("number", "Page size, from 1 to 50.", true),
130
+ }),
131
+ handler: handler(bound, "listWorkspaceActivity"),
132
+ },
133
+ ];
134
+ }
135
+
136
+ function getToolDefs(bound, includeGlobal) {
137
+ var tools = commonTools(bound);
138
+ if (includeGlobal === true) tools = tools.concat(globalTools(bound));
139
+ return tools;
140
+ }
141
+
142
+ function createMcpServer(adapter, bound, includeGlobal) {
143
+ if (!adapter || typeof adapter.createToolServer !== "function") return null;
144
+ return adapter.createToolServer({
145
+ name: "clay-workspace",
146
+ version: "1.0.0",
147
+ tools: getToolDefs(bound, includeGlobal),
148
+ });
149
+ }
150
+
151
+ module.exports = {
152
+ createMcpServer: createMcpServer,
153
+ getToolDefs: getToolDefs,
154
+ };
@@ -0,0 +1,492 @@
1
+ var sessionSearch = require("./session-search");
2
+ var builtinMates = require("./builtin-mates");
3
+ var crypto = require("crypto");
4
+
5
+ var MAX_PAGE = 50;
6
+ var MAX_TURN_CHARS = 12000;
7
+ var MAX_SNIPPET_CHARS = 320;
8
+ var DECISION_PATTERNS = [
9
+ /\bdecided\s+to\b/i, /\bdecision\b/i, /\bgoing\s+with\b/i,
10
+ /\bsettled\s+on\b/i, /\bchose\s+to\b/i, /\bwill\s+go\s+with\b/i,
11
+ /\blet'?s\s+go\s+with\b/i, /\b결정\b/, /\b정했\b/, /\b이걸로\s+가/,
12
+ ];
13
+
14
+ function cleanText(value, max) {
15
+ if (typeof value !== "string") return "";
16
+ var text = value.replace(/\u0000/g, "").trim();
17
+ if (text.length > max) text = text.substring(0, max) + "...";
18
+ return text;
19
+ }
20
+
21
+ function cleanMetadata(value, max) {
22
+ return cleanText(value, max).replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
23
+ }
24
+
25
+ function parseLimit(value, fallback) {
26
+ var limit = Number(value);
27
+ if (!Number.isFinite(limit)) limit = fallback;
28
+ return Math.max(1, Math.min(MAX_PAGE, Math.floor(limit)));
29
+ }
30
+
31
+ function encodeCursor(offset) {
32
+ return offset > 0 ? Buffer.from(String(offset), "utf8").toString("base64url") : null;
33
+ }
34
+
35
+ function decodeCursor(value) {
36
+ if (!value) return 0;
37
+ try {
38
+ var parsed = Number(Buffer.from(String(value), "base64url").toString("utf8"));
39
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0;
40
+ } catch (e) {
41
+ return 0;
42
+ }
43
+ }
44
+
45
+ function sessionRef(projectSlug, session) {
46
+ var localId = session && session.localId != null ? String(session.localId) : "";
47
+ var identity = session && session.cliSessionId
48
+ ? "cli\u0000" + String(session.vendor || "") + "\u0000" + String(session.cliSessionId)
49
+ : "local\u0000" + localId;
50
+ var digest = crypto.createHash("sha256").update(String(projectSlug || "") + "\u0000" + identity).digest("base64url");
51
+ return "session:" + digest.substring(0, 24);
52
+ }
53
+
54
+ function canonicalTurns(history) {
55
+ var turns = [];
56
+ var assistant = "";
57
+
58
+ function flushAssistant() {
59
+ var text = cleanText(assistant, MAX_TURN_CHARS);
60
+ if (text) turns.push({ role: "assistant", text: text });
61
+ assistant = "";
62
+ }
63
+
64
+ for (var i = 0; i < (history || []).length; i++) {
65
+ var event = history[i];
66
+ if (!event || event.hidden === true || event._internal === true || event.internal === true) continue;
67
+ if (event.type === "delegated_work" || event.type === "delegated_follow_up") {
68
+ flushAssistant();
69
+ } else if (event.type === "user_message") {
70
+ flushAssistant();
71
+ var userText = cleanText(event.text, MAX_TURN_CHARS);
72
+ if (userText) turns.push({ role: "user", text: userText });
73
+ } else if (event.type === "delta" && typeof event.text === "string") {
74
+ assistant += event.text;
75
+ if (assistant.length > MAX_TURN_CHARS * 2) assistant = assistant.substring(0, MAX_TURN_CHARS * 2);
76
+ } else if (event.type === "result" || event.type === "done" || event.type === "error") {
77
+ flushAssistant();
78
+ }
79
+ }
80
+ flushAssistant();
81
+ return turns;
82
+ }
83
+
84
+ function sanitizedHistory(turns) {
85
+ var history = [];
86
+ for (var i = 0; i < turns.length; i++) {
87
+ history.push({ type: turns[i].role === "user" ? "user_message" : "delta", text: turns[i].text });
88
+ if (turns[i].role === "assistant") history.push({ type: "result" });
89
+ }
90
+ return history;
91
+ }
92
+
93
+ function sessionActivity(session) {
94
+ if (session.isProcessing) return "processing";
95
+ if (session.pendingPermissions && Object.keys(session.pendingPermissions).length > 0) return "waiting_for_approval";
96
+ return "idle";
97
+ }
98
+
99
+ function attachWorkspaceQueryService(ctx) {
100
+ var getProjects = ctx.getProjects;
101
+ var isMultiUser = ctx.isMultiUser;
102
+ var resolveMate = ctx.resolveMate;
103
+ var canAccessProject = ctx.canAccessProject;
104
+
105
+ function authoritativeMate(source) {
106
+ if (!source || source.isMate !== true || !source.mateId || typeof resolveMate !== "function") return null;
107
+ var sourceProject = getProjects().get(source.projectSlug);
108
+ if (!sourceProject) return null;
109
+ var sourceStatus = sourceProject.getStatus();
110
+ if (!sourceStatus || sourceStatus.isMate !== true || sourceStatus.mateId !== source.mateId || sourceStatus.projectOwnerId !== source.projectOwnerId) return null;
111
+ if (source.session) {
112
+ var manager = sourceProject.getSessionManager ? sourceProject.getSessionManager() : sourceProject.sm;
113
+ if (!manager || !manager.sessions || manager.sessions.get(source.session.localId) !== source.session) return null;
114
+ }
115
+ var mate = resolveMate(source.projectOwnerId || null, source.mateId);
116
+ if (!mate || mate.id !== source.mateId) return null;
117
+ if (isMultiUser() && (!source.projectOwnerId || mate.createdBy !== source.projectOwnerId)) return null;
118
+ return mate;
119
+ }
120
+
121
+ function bindSource(source) {
122
+ var mate = authoritativeMate(source);
123
+ if (!mate) return null;
124
+ if (isMultiUser()) {
125
+ if (!source.projectOwnerId) return null;
126
+ if (source.session && source.session.ownerId !== source.projectOwnerId) return null;
127
+ } else if (source.session && source.session.ownerId && source.session.ownerId !== source.projectOwnerId) {
128
+ return null;
129
+ }
130
+ var def = mate.builtinKey ? builtinMates.getBuiltinByKey(mate.builtinKey) : null;
131
+ var principal = {
132
+ userId: isMultiUser() ? source.projectOwnerId : null,
133
+ singleUserOwnerId: isMultiUser() ? null : (source.projectOwnerId || null),
134
+ sourceProjectSlug: source.projectSlug,
135
+ sourceSessionId: source.session ? source.session.localId : null,
136
+ sourceSessionRef: source.session ? sessionRef(source.projectSlug, source.session) : null,
137
+ sourceRequestId: source.session ? source.session._homeRequestId || null : null,
138
+ mateId: mate.id,
139
+ isClay: !!(mate.builtinKey === "clay" && def && def.hostAgent === true),
140
+ };
141
+ return createBound(principal);
142
+ }
143
+
144
+ function bindProjectSession(source) {
145
+ if (!source || !source.projectSlug || !source.session) return null;
146
+ var project = getProjects().get(source.projectSlug);
147
+ if (!project) return null;
148
+ var manager = project.getSessionManager ? project.getSessionManager() : project.sm;
149
+ if (!manager || !manager.sessions || manager.sessions.get(source.session.localId) !== source.session) return null;
150
+ if (isMultiUser()) {
151
+ if (!source.session.ownerId) return null;
152
+ } else if (source.session.ownerId) {
153
+ return null;
154
+ }
155
+ return createBound({
156
+ userId: isMultiUser() ? source.session.ownerId : null,
157
+ sourceProjectSlug: source.projectSlug,
158
+ sourceSessionId: source.session.localId,
159
+ sourceSessionRef: sessionRef(source.projectSlug, source.session),
160
+ singleUserOwnerId: null,
161
+ sourceRequestId: source.session._homeRequestId || null,
162
+ mateId: null,
163
+ isClay: false,
164
+ });
165
+ }
166
+
167
+ function ownsProject(principal, status) {
168
+ if (!status || status.isWorktree) return false;
169
+ if (isMultiUser()) return !!principal.userId && status.projectOwnerId === principal.userId;
170
+ return !status.projectOwnerId || (!!principal.singleUserOwnerId && status.projectOwnerId === principal.singleUserOwnerId);
171
+ }
172
+
173
+ function ownsSession(principal, session) {
174
+ if (!session || session.hidden) return false;
175
+ if (isMultiUser()) return !!principal.userId && session.ownerId === principal.userId;
176
+ return !session.ownerId || (!!principal.singleUserOwnerId && session.ownerId === principal.singleUserOwnerId);
177
+ }
178
+
179
+ function collect(principal) {
180
+ var projects = [];
181
+ getProjects().forEach(function (project, slug) {
182
+ var status = project.getStatus();
183
+ var projectOwned = ownsProject(principal, status);
184
+ var projectAccessible = projectOwned;
185
+ if (!projectAccessible && isMultiUser() && typeof canAccessProject === "function") {
186
+ projectAccessible = canAccessProject(principal.userId, status) === true;
187
+ }
188
+ if (!projectAccessible || (status && status.isWorktree)) return;
189
+ var manager = project.getSessionManager ? project.getSessionManager() : project.sm;
190
+ if (!manager || !manager.sessions) return;
191
+ var sessions = [];
192
+ manager.sessions.forEach(function (session) {
193
+ if (ownsSession(principal, session)) sessions.push(session);
194
+ });
195
+ // An accessible shared/public container grants no history visibility by
196
+ // itself. Include it only when the exact principal owns a session there.
197
+ if (!projectOwned && sessions.length === 0) return;
198
+ projects.push({
199
+ projectSlug: slug,
200
+ projectTitle: cleanMetadata(status.title || status.project || slug, 160),
201
+ projectIcon: cleanMetadata(status.icon || "", 80) || null,
202
+ isMate: !!status.isMate,
203
+ mateId: status.isMate ? (status.mateId || null) : null,
204
+ sessions: sessions,
205
+ });
206
+ });
207
+ return projects;
208
+ }
209
+
210
+ function findProject(principal, slug) {
211
+ var projects = collect(principal);
212
+ for (var i = 0; i < projects.length; i++) if (projects[i].projectSlug === slug) return projects[i];
213
+ return null;
214
+ }
215
+
216
+ function findSession(project, ref) {
217
+ if (!project || typeof ref !== "string") return null;
218
+ for (var i = 0; i < project.sessions.length; i++) {
219
+ var session = project.sessions[i];
220
+ if (sessionRef(project.projectSlug, session) === ref) return session;
221
+ }
222
+ return null;
223
+ }
224
+
225
+ function projectSummary(project) {
226
+ var lastActivity = 0;
227
+ var processing = 0;
228
+ for (var i = 0; i < project.sessions.length; i++) {
229
+ var session = project.sessions[i];
230
+ lastActivity = Math.max(lastActivity, session.lastActivity || session.createdAt || 0);
231
+ if (session.isProcessing) processing++;
232
+ }
233
+ return {
234
+ projectSlug: project.projectSlug,
235
+ title: project.projectTitle,
236
+ icon: project.projectIcon,
237
+ isMate: project.isMate,
238
+ mateId: project.mateId,
239
+ sessionCount: project.sessions.length,
240
+ processingCount: processing,
241
+ lastActivity: lastActivity,
242
+ };
243
+ }
244
+
245
+ function sessionSummary(project, session) {
246
+ var ref = sessionRef(project.projectSlug, session);
247
+ return {
248
+ projectSlug: project.projectSlug,
249
+ sessionRef: ref,
250
+ durable: !!session.cliSessionId,
251
+ title: cleanMetadata(session.title || "New Session", 160),
252
+ status: sessionActivity(session),
253
+ createdAt: session.createdAt || 0,
254
+ lastActivity: session.lastActivity || session.createdAt || 0,
255
+ vendor: cleanMetadata(session.vendor || "", 40) || null,
256
+ model: cleanMetadata(session.model || "", 160) || null,
257
+ };
258
+ }
259
+
260
+ function page(items, args) {
261
+ var offset = decodeCursor(args && args.cursor);
262
+ var limit = parseLimit(args && args.limit, 20);
263
+ var sliced = items.slice(offset, offset + limit);
264
+ return { items: sliced, nextCursor: offset + limit < items.length ? encodeCursor(offset + limit) : null, total: items.length };
265
+ }
266
+
267
+ function searchProjects(principal, args) {
268
+ var query = cleanText(args && args.query || "", 200).toLowerCase();
269
+ var summaries = collect(principal).map(projectSummary).filter(function (project) {
270
+ return !query || project.projectSlug.toLowerCase().indexOf(query) !== -1 || project.title.toLowerCase().indexOf(query) !== -1;
271
+ });
272
+ summaries.sort(function (a, b) { return b.lastActivity - a.lastActivity || a.title.localeCompare(b.title); });
273
+ var result = page(summaries, args || {});
274
+ return { projects: result.items, nextCursor: result.nextCursor, total: result.total };
275
+ }
276
+
277
+ function listSessions(principal, args) {
278
+ var project = findProject(principal, args && args.projectSlug);
279
+ if (!project) throw new Error("Project not found in the owned workspace.");
280
+ var sessions = project.sessions.map(function (session) { return sessionSummary(project, session); });
281
+ sessions.sort(function (a, b) { return b.lastActivity - a.lastActivity || a.title.localeCompare(b.title); });
282
+ var result = page(sessions, args || {});
283
+ return { project: { projectSlug: project.projectSlug, title: project.projectTitle }, sessions: result.items, nextCursor: result.nextCursor, total: result.total };
284
+ }
285
+
286
+ function sanitizedProject(project) {
287
+ return {
288
+ projectSlug: project.projectSlug,
289
+ projectTitle: project.projectTitle,
290
+ projectIcon: project.projectIcon,
291
+ isMate: project.isMate,
292
+ mateId: project.mateId,
293
+ sessions: project.sessions.map(function (session) {
294
+ return {
295
+ localId: sessionRef(project.projectSlug, session),
296
+ title: cleanMetadata(session.title || "New Session", 160),
297
+ createdAt: session.createdAt || 0,
298
+ lastActivity: session.lastActivity || session.createdAt || 0,
299
+ history: sanitizedHistory(canonicalTurns(session.history)),
300
+ };
301
+ }),
302
+ };
303
+ }
304
+
305
+ function searchHistory(principal, projectSlug, args) {
306
+ var query = cleanText(args && args.query || "", 500);
307
+ if (!query) throw new Error("A search query is required.");
308
+ var projects;
309
+ if (projectSlug) {
310
+ var project = findProject(principal, projectSlug);
311
+ if (!project) throw new Error("Project not found in the owned workspace.");
312
+ projects = [sanitizedProject(project)];
313
+ } else {
314
+ projects = collect(principal).map(sanitizedProject);
315
+ }
316
+ var max = Math.min(100, decodeCursor(args && args.cursor) + parseLimit(args && args.limit, 20) + 20);
317
+ var ranked = sessionSearch.searchPalette(projects, query, { maxResults: max });
318
+ var offset = decodeCursor(args && args.cursor);
319
+ var limit = parseLimit(args && args.limit, 20);
320
+ var selected = ranked.slice(offset, offset + limit);
321
+ var results = [];
322
+ for (var i = 0; i < selected.length; i++) {
323
+ var hit = selected[i];
324
+ var projectForHit = findProject(principal, hit.projectSlug);
325
+ var session = projectForHit ? findSession(projectForHit, String(hit.sessionId)) : null;
326
+ if (!session) continue;
327
+ results.push({
328
+ projectSlug: hit.projectSlug,
329
+ projectTitle: hit.projectTitle,
330
+ sessionRef: sessionRef(projectForHit.projectSlug, session),
331
+ durable: !!session.cliSessionId,
332
+ sessionTitle: cleanMetadata(hit.sessionTitle || "New Session", 160),
333
+ lastActivity: hit.lastActivity || 0,
334
+ matchType: hit.matchType || "content",
335
+ snippet: cleanMetadata(hit.snippet || "", MAX_SNIPPET_CHARS) || null,
336
+ score: hit.score || 0,
337
+ });
338
+ }
339
+ return { results: results, nextCursor: offset + limit < ranked.length ? encodeCursor(offset + limit) : null };
340
+ }
341
+
342
+ function readSession(principal, args) {
343
+ var project = findProject(principal, args && args.projectSlug);
344
+ if (!project) throw new Error("Project not found in the owned workspace.");
345
+ var session = findSession(project, args && (args.sessionRef || args.sessionId));
346
+ if (!session) throw new Error("Session not found in the owned workspace.");
347
+ var turns = canonicalTurns(session.history);
348
+ var result = page(turns, args || {});
349
+ return {
350
+ project: { projectSlug: project.projectSlug, title: project.projectTitle },
351
+ session: sessionSummary(project, session),
352
+ turns: result.items,
353
+ nextCursor: result.nextCursor,
354
+ total: result.total,
355
+ };
356
+ }
357
+
358
+ function workspaceActivity(principal, args) {
359
+ var entries = [];
360
+ var projects = collect(principal);
361
+ for (var p = 0; p < projects.length; p++) {
362
+ for (var s = 0; s < projects[p].sessions.length; s++) entries.push(sessionSummary(projects[p], projects[p].sessions[s]));
363
+ }
364
+ if (args && args.status) entries = entries.filter(function (entry) { return entry.status === args.status; });
365
+ entries.sort(function (a, b) { return b.lastActivity - a.lastActivity || a.title.localeCompare(b.title); });
366
+ var result = page(entries, args || {});
367
+ return { sessions: result.items, nextCursor: result.nextCursor, total: result.total };
368
+ }
369
+
370
+ function resolveSessionNavigation(principal, ref) {
371
+ if (typeof ref !== "string" || !/^session:[A-Za-z0-9_-]{24}$/.test(ref)) throw new Error("Invalid session reference.");
372
+ var projects = collect(principal);
373
+ for (var p = 0; p < projects.length; p++) {
374
+ var session = findSession(projects[p], ref);
375
+ if (!session) continue;
376
+ return {
377
+ projectSlug: projects[p].projectSlug,
378
+ projectTitle: projects[p].projectTitle,
379
+ isMate: projects[p].isMate,
380
+ mateId: projects[p].mateId,
381
+ sessionId: session.localId,
382
+ homeSessionId: session.cliSessionId || "local:" + session.localId,
383
+ title: cleanMetadata(session.title || "New Session", 160),
384
+ };
385
+ }
386
+ throw new Error("Session not found in the owned workspace.");
387
+ }
388
+
389
+ function recentDecisions(principal, args) {
390
+ var hits = [];
391
+ var projects = collect(principal);
392
+ var projectSlug = args && args.projectSlug;
393
+ var since = args && args.since ? new Date(args.since).getTime() : null;
394
+ var until = args && args.until ? new Date(args.until).getTime() : null;
395
+ for (var p = 0; p < projects.length; p++) {
396
+ if (projectSlug && projects[p].projectSlug !== projectSlug) continue;
397
+ for (var s = 0; s < projects[p].sessions.length; s++) {
398
+ var session = projects[p].sessions[s];
399
+ var activity = session.lastActivity || session.createdAt || 0;
400
+ if (Number.isFinite(since) && activity < since) continue;
401
+ if (Number.isFinite(until) && activity > until) continue;
402
+ var turns = canonicalTurns(session.history);
403
+ for (var t = 0; t < turns.length; t++) {
404
+ var matched = false;
405
+ for (var d = 0; d < DECISION_PATTERNS.length; d++) {
406
+ if (DECISION_PATTERNS[d].test(turns[t].text)) { matched = true; break; }
407
+ }
408
+ if (!matched) continue;
409
+ hits.push({
410
+ projectSlug: projects[p].projectSlug,
411
+ projectTitle: projects[p].projectTitle,
412
+ sessionRef: sessionRef(projects[p].projectSlug, session),
413
+ sessionTitle: cleanMetadata(session.title || "New Session", 160),
414
+ lastActivity: activity,
415
+ role: turns[t].role,
416
+ text: cleanText(turns[t].text, MAX_SNIPPET_CHARS),
417
+ });
418
+ }
419
+ }
420
+ }
421
+ hits.sort(function (a, b) { return b.lastActivity - a.lastActivity; });
422
+ var result = page(hits, args || {});
423
+ return { results: result.items, nextCursor: result.nextCursor, total: result.total };
424
+ }
425
+
426
+ function memorySessions(principal, excludeSource) {
427
+ var out = [];
428
+ var projects = collect(principal);
429
+ for (var p = 0; p < projects.length; p++) {
430
+ if (excludeSource && projects[p].projectSlug === principal.sourceProjectSlug) continue;
431
+ var sanitized = sanitizedProject(projects[p]);
432
+ for (var s = 0; s < sanitized.sessions.length; s++) {
433
+ sanitized.sessions[s]._projectTitle = sanitized.projectTitle;
434
+ sanitized.sessions[s]._projectSlug = sanitized.projectSlug;
435
+ out.push(sanitized.sessions[s]);
436
+ }
437
+ }
438
+ return out;
439
+ }
440
+
441
+ function createBound(principal) {
442
+ return {
443
+ isClay: principal.isClay,
444
+ listProjects: function (args) { return searchProjects(principal, args || {}); },
445
+ listProjectSessions: function (args) { return listSessions(principal, args || {}); },
446
+ searchProjectHistory: function (args) { return searchHistory(principal, args && args.projectSlug, args || {}); },
447
+ readProjectSession: function (args) { return readSession(principal, args || {}); },
448
+ searchWorkspaceHistory: function (args) {
449
+ if (!principal.isClay) throw new Error("Workspace-wide search is available only to builtin Clay.");
450
+ return searchHistory(principal, args && args.projectSlug ? args.projectSlug : null, args || {});
451
+ },
452
+ listWorkspaceActivity: function (args) {
453
+ if (!principal.isClay) throw new Error("Workspace-wide activity is available only to builtin Clay.");
454
+ return workspaceActivity(principal, args || {});
455
+ },
456
+ resolveSessionNavigation: function (args) { return resolveSessionNavigation(principal, args && args.sessionRef); },
457
+ listRecentDecisions: function (args) {
458
+ if (!principal.isClay) throw new Error("Workspace decisions are available only to builtin Clay.");
459
+ return recentDecisions(principal, args || {});
460
+ },
461
+ getMemorySessions: function (excludeSource) {
462
+ if (excludeSource && !principal.isClay) return [];
463
+ return memorySessions(principal, excludeSource);
464
+ },
465
+ proposeProjectAssignment: function (args) {
466
+ if (!ctx.assignmentService) throw new Error("Project assignments are unavailable.");
467
+ return ctx.assignmentService.propose(principal, args || {});
468
+ },
469
+ proposeProjectFollowUp: function (args) {
470
+ if (!ctx.assignmentService) throw new Error("Project follow-ups are unavailable.");
471
+ return ctx.assignmentService.proposeFollowUp(principal, args || {});
472
+ },
473
+ getAssignmentStatus: function (args) {
474
+ if (!ctx.assignmentService) throw new Error("Project assignments are unavailable.");
475
+ return ctx.assignmentService.getStatus(principal, args && args.assignmentId);
476
+ },
477
+ };
478
+ }
479
+
480
+ return {
481
+ bindSource: bindSource,
482
+ bindProjectSession: bindProjectSession,
483
+ canonicalTurns: canonicalTurns,
484
+ sessionRef: sessionRef,
485
+ };
486
+ }
487
+
488
+ module.exports = {
489
+ attachWorkspaceQueryService: attachWorkspaceQueryService,
490
+ canonicalTurns: canonicalTurns,
491
+ sessionRef: sessionRef,
492
+ };