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,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
+ };