clay-server 4.0.0-beta.9 → 4.0.0

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 (185) hide show
  1. package/lib/capsule-display-floor.js +51 -0
  2. package/lib/capsule-frame-server.js +255 -0
  3. package/lib/capsule-mate-turn.js +105 -0
  4. package/lib/capsule-pig-logic.js +300 -0
  5. package/lib/capsule-server-runtimes.js +39 -0
  6. package/lib/capsules/pig/display.js +198 -0
  7. package/lib/capsules/pig/manifest.json +9 -0
  8. package/lib/capsules/pig/ui.json +84 -0
  9. package/lib/config.js +25 -1
  10. package/lib/daemon-projects.js +13 -8
  11. package/lib/daemon.js +127 -14
  12. package/lib/git-cli.js +95 -1
  13. package/lib/knowledge-import.js +436 -0
  14. package/lib/knowledge-record-store.js +188 -0
  15. package/lib/knowledge-search.js +240 -0
  16. package/lib/mate-knowledge-mcp-server.js +151 -0
  17. package/lib/mate-knowledge-migration.js +283 -0
  18. package/lib/mate-knowledge-service.js +488 -0
  19. package/lib/mate-knowledge-sync.js +482 -0
  20. package/lib/migrate-single-user.js +2 -0
  21. package/lib/notes-lifecycle.js +149 -0
  22. package/lib/notes.js +60 -3
  23. package/lib/os-users.js +3 -1
  24. package/lib/project-capsule-catalog.js +9 -1
  25. package/lib/project-capsule-turn.js +96 -0
  26. package/lib/project-connection.js +15 -1
  27. package/lib/project-file-history.js +223 -0
  28. package/lib/project-file-watch.js +77 -49
  29. package/lib/project-filesystem.js +31 -220
  30. package/lib/project-http.js +15 -1
  31. package/lib/project-knowledge.js +22 -0
  32. package/lib/project-log-context.js +172 -0
  33. package/lib/project-log-feedback-delivery.js +118 -0
  34. package/lib/project-logs-comments.js +137 -0
  35. package/lib/project-logs-context-state.js +42 -0
  36. package/lib/project-logs-mcp-server.js +328 -0
  37. package/lib/project-logs-query.js +185 -0
  38. package/lib/project-logs-root.js +75 -0
  39. package/lib/project-logs-schema.js +259 -0
  40. package/lib/project-logs-service.js +463 -0
  41. package/lib/project-logs-snapshot.js +205 -0
  42. package/lib/project-logs-store.js +495 -0
  43. package/lib/project-logs-versioning.js +132 -0
  44. package/lib/project-logs.js +400 -0
  45. package/lib/project-mate-interaction.js +14 -0
  46. package/lib/project-mate-knowledge.js +110 -0
  47. package/lib/project-memory.js +22 -0
  48. package/lib/project-message-delivery.js +70 -0
  49. package/lib/project-pair-lifecycle.js +460 -0
  50. package/lib/project-request-access.js +66 -0
  51. package/lib/project-session-handoff.js +1 -1
  52. package/lib/project-session-notes.js +64 -13
  53. package/lib/project-session-pair.js +159 -146
  54. package/lib/project-session-spawn.js +1 -1
  55. package/lib/project-sessions.js +58 -13
  56. package/lib/project-user-message.js +53 -4
  57. package/lib/project-worker-permission.js +430 -0
  58. package/lib/project-worker-proposal.js +208 -62
  59. package/lib/project.js +185 -6
  60. package/lib/public/app.js +63 -35
  61. package/lib/public/css/admin.css +2 -2
  62. package/lib/public/css/capsule-ui.css +38 -0
  63. package/lib/public/css/command-palette.css +19 -9
  64. package/lib/public/css/git-placard.css +154 -0
  65. package/lib/public/css/home-hub.css +4 -0
  66. package/lib/public/css/home-session-actions.css +66 -0
  67. package/lib/public/css/home-sidebar.css +66 -0
  68. package/lib/public/css/input.css +84 -3
  69. package/lib/public/css/messages.css +40 -1
  70. package/lib/public/css/mobile-nav.css +80 -0
  71. package/lib/public/css/notifications-center.css +67 -0
  72. package/lib/public/css/pane.css +97 -10
  73. package/lib/public/css/project-logs.css +198 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/session-actions.css +16 -101
  76. package/lib/public/css/sidebar.css +103 -0
  77. package/lib/public/css/sticky-notes.css +237 -229
  78. package/lib/public/css/worker-proposal.css +36 -3
  79. package/lib/public/index.html +43 -11
  80. package/lib/public/modules/admin.js +8 -8
  81. package/lib/public/modules/app-connection.js +124 -6
  82. package/lib/public/modules/app-home-hub.js +12 -8
  83. package/lib/public/modules/app-message-cards.js +2 -1
  84. package/lib/public/modules/app-messages.js +92 -11
  85. package/lib/public/modules/app-misc.js +2 -14
  86. package/lib/public/modules/app-notifications.js +11 -7
  87. package/lib/public/modules/app-panels.js +3 -2
  88. package/lib/public/modules/app-projects.js +56 -18
  89. package/lib/public/modules/app-rendering.js +1 -0
  90. package/lib/public/modules/capsule-preference.js +44 -0
  91. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  92. package/lib/public/modules/clay-log-links.js +70 -0
  93. package/lib/public/modules/command-palette.js +52 -15
  94. package/lib/public/modules/context-sources.js +0 -3
  95. package/lib/public/modules/git-agent-sessions.js +85 -0
  96. package/lib/public/modules/git-panel.js +111 -84
  97. package/lib/public/modules/git-placard.js +161 -0
  98. package/lib/public/modules/home-chat-empty-state.js +39 -0
  99. package/lib/public/modules/home-chat-identity.js +9 -0
  100. package/lib/public/modules/home-conversations-sheet.js +102 -48
  101. package/lib/public/modules/home-mate-chat.js +15 -35
  102. package/lib/public/modules/home-session-actions.js +6 -0
  103. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  104. package/lib/public/modules/home-sidebar.js +7 -0
  105. package/lib/public/modules/home-surface-boot.js +7 -3
  106. package/lib/public/modules/home-tool-frame.js +182 -0
  107. package/lib/public/modules/home-tools.js +138 -1
  108. package/lib/public/modules/input.js +38 -7
  109. package/lib/public/modules/markdown.js +2 -0
  110. package/lib/public/modules/mate-sidebar.js +0 -8
  111. package/lib/public/modules/message-delivery.js +97 -0
  112. package/lib/public/modules/paste-modal.js +84 -0
  113. package/lib/public/modules/project-activation.js +45 -0
  114. package/lib/public/modules/project-logs-render.js +405 -0
  115. package/lib/public/modules/project-logs.js +461 -0
  116. package/lib/public/modules/project-removal-target.js +26 -0
  117. package/lib/public/modules/scheduler.js +17 -3
  118. package/lib/public/modules/session-actions.js +29 -105
  119. package/lib/public/modules/session-hierarchy.js +54 -0
  120. package/lib/public/modules/sidebar-mobile.js +40 -15
  121. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  122. package/lib/public/modules/sidebar-sessions.js +77 -11
  123. package/lib/public/modules/sidebar.js +17 -5
  124. package/lib/public/modules/split-group-helpers.js +8 -0
  125. package/lib/public/modules/split-pair-ui.js +15 -4
  126. package/lib/public/modules/split-view.js +5 -2
  127. package/lib/public/modules/split-worker-runtime.js +24 -0
  128. package/lib/public/modules/sticky-note-markdown.js +5 -0
  129. package/lib/public/modules/sticky-notes-browser.js +326 -0
  130. package/lib/public/modules/sticky-notes-card.js +360 -0
  131. package/lib/public/modules/sticky-notes-editor.js +291 -0
  132. package/lib/public/modules/sticky-notes-shared.js +90 -0
  133. package/lib/public/modules/sticky-notes.js +170 -946
  134. package/lib/public/modules/thinking-lifecycle.js +175 -0
  135. package/lib/public/modules/thinking-summary.js +28 -0
  136. package/lib/public/modules/thinking-view.js +75 -0
  137. package/lib/public/modules/tool-palette-order.js +143 -0
  138. package/lib/public/modules/tool-palette-overlays.js +159 -0
  139. package/lib/public/modules/tool-palette.js +21 -188
  140. package/lib/public/modules/tools.js +31 -123
  141. package/lib/public/modules/update-snooze.js +169 -0
  142. package/lib/public/modules/user-settings.js +4 -0
  143. package/lib/public/modules/worker-pane-lock.js +176 -0
  144. package/lib/public/modules/worker-proposal-state.js +16 -0
  145. package/lib/public/modules/worker-proposal.js +49 -20
  146. package/lib/public/style.css +3 -1
  147. package/lib/sdk-bridge.js +135 -46
  148. package/lib/sdk-message-processor.js +36 -7
  149. package/lib/server-experimental-settings.js +49 -0
  150. package/lib/server-global-ws.js +11 -0
  151. package/lib/server-home-chat-events.js +31 -1
  152. package/lib/server-home-chat.js +27 -4
  153. package/lib/server-home-clay-entry.js +1 -1
  154. package/lib/server-home-clay-session-links.js +34 -5
  155. package/lib/server-settings.js +5 -0
  156. package/lib/server-tools.js +127 -9
  157. package/lib/server.js +137 -22
  158. package/lib/session-driver-eligibility.js +38 -0
  159. package/lib/session-driver-orchestration.js +31 -0
  160. package/lib/session-hygiene.js +3 -0
  161. package/lib/session-notes-mcp-server.js +25 -5
  162. package/lib/session-pair-factory.js +247 -0
  163. package/lib/session-pair-mcp-server.js +46 -8
  164. package/lib/session-pair-prompts.js +79 -0
  165. package/lib/session-pair-turn-control.js +153 -0
  166. package/lib/session-provenance.js +119 -0
  167. package/lib/session-spawn-mcp-server.js +1 -1
  168. package/lib/session-split-groups.js +4 -1
  169. package/lib/session-title-policy.js +60 -0
  170. package/lib/session-visibility.js +41 -0
  171. package/lib/sessions.js +57 -10
  172. package/lib/tools-registry.js +89 -10
  173. package/lib/update-snooze.js +437 -0
  174. package/lib/users-experimental-preferences.js +30 -0
  175. package/lib/users-permissions.js +6 -6
  176. package/lib/users.js +22 -16
  177. package/lib/workspace-query-access.js +102 -0
  178. package/lib/workspace-query-service.js +24 -17
  179. package/lib/worktree.js +25 -38
  180. package/lib/ws-schema.js +38 -7
  181. package/lib/yoke/adapters/claude-worker.js +16 -6
  182. package/lib/yoke/adapters/claude.js +12 -0
  183. package/lib/yoke/adapters/codex.js +24 -14
  184. package/lib/yoke/vendor-registry.js +1 -1
  185. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  var sessionSearch = require("./session-search");
2
2
  var builtinMates = require("./builtin-mates");
3
+ var workspaceQueryAccess = require("./workspace-query-access");
3
4
  var crypto = require("crypto");
4
5
 
5
6
  var MAX_PAGE = 50;
@@ -100,7 +101,13 @@ function attachWorkspaceQueryService(ctx) {
100
101
  var getProjects = ctx.getProjects;
101
102
  var isMultiUser = ctx.isMultiUser;
102
103
  var resolveMate = ctx.resolveMate;
103
- var canAccessProject = ctx.canAccessProject;
104
+ // Ownership and access both come from here, never from getStatus().
105
+ var projectAccess = workspaceQueryAccess.attachWorkspaceQueryAccess({
106
+ isMultiUser: isMultiUser,
107
+ resolveMate: resolveMate,
108
+ onGetProjectAccess: ctx.onGetProjectAccess,
109
+ canAccessProject: ctx.canAccessProject,
110
+ });
104
111
 
105
112
  function authoritativeMate(source) {
106
113
  if (!source || source.isMate !== true || !source.mateId || typeof resolveMate !== "function") return null;
@@ -164,12 +171,6 @@ function attachWorkspaceQueryService(ctx) {
164
171
  });
165
172
  }
166
173
 
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
174
  function ownsSession(principal, session) {
174
175
  if (!session || session.hidden) return false;
175
176
  if (isMultiUser()) return !!principal.userId && session.ownerId === principal.userId;
@@ -180,12 +181,12 @@ function attachWorkspaceQueryService(ctx) {
180
181
  var projects = [];
181
182
  getProjects().forEach(function (project, slug) {
182
183
  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;
184
+ if (!status || status.isWorktree) return;
185
+ // Authorized here rather than at bind time, so an already-created tool
186
+ // handler loses a project the moment its access record changes.
187
+ var verdict = projectAccess.evaluate(principal, status);
188
+ if (!verdict.accessible) return;
189
+ var projectOwned = verdict.owned;
189
190
  var manager = project.getSessionManager ? project.getSessionManager() : project.sm;
190
191
  if (!manager || !manager.sessions) return;
191
192
  var sessions = [];
@@ -305,14 +306,20 @@ function attachWorkspaceQueryService(ctx) {
305
306
  function searchHistory(principal, projectSlug, args) {
306
307
  var query = cleanText(args && args.query || "", 500);
307
308
  if (!query) throw new Error("A search query is required.");
308
- var projects;
309
+ // One authorization snapshot for the whole call. Resolving hits back to
310
+ // their project through findProject() would re-authorize every project once
311
+ // per hit, so the visible set is captured once and indexed.
312
+ var visible;
309
313
  if (projectSlug) {
310
314
  var project = findProject(principal, projectSlug);
311
315
  if (!project) throw new Error("Project not found in the owned workspace.");
312
- projects = [sanitizedProject(project)];
316
+ visible = [project];
313
317
  } else {
314
- projects = collect(principal).map(sanitizedProject);
318
+ visible = collect(principal);
315
319
  }
320
+ var bySlug = {};
321
+ for (var v = 0; v < visible.length; v++) bySlug[visible[v].projectSlug] = visible[v];
322
+ var projects = visible.map(sanitizedProject);
316
323
  var max = Math.min(100, decodeCursor(args && args.cursor) + parseLimit(args && args.limit, 20) + 20);
317
324
  var ranked = sessionSearch.searchPalette(projects, query, { maxResults: max });
318
325
  var offset = decodeCursor(args && args.cursor);
@@ -321,7 +328,7 @@ function attachWorkspaceQueryService(ctx) {
321
328
  var results = [];
322
329
  for (var i = 0; i < selected.length; i++) {
323
330
  var hit = selected[i];
324
- var projectForHit = findProject(principal, hit.projectSlug);
331
+ var projectForHit = bySlug[hit.projectSlug] || null;
325
332
  var session = projectForHit ? findSession(projectForHit, String(hit.sessionId)) : null;
326
333
  if (!session) continue;
327
334
  results.push({
package/lib/worktree.js CHANGED
@@ -1,6 +1,6 @@
1
- var { execFileSync } = require("child_process");
2
1
  var fs = require("fs");
3
2
  var path = require("path");
3
+ var { runGitSync } = require("./git-cli");
4
4
 
5
5
  // Parse `git worktree list --porcelain` output into structured objects
6
6
  function parseWorktreeOutput(output) {
@@ -28,14 +28,10 @@ function parseWorktreeOutput(output) {
28
28
  }
29
29
 
30
30
  // Check if a given path is itself a worktree (not the main working tree)
31
- function isWorktree(projectPath) {
31
+ function isWorktree(projectPath, osUserInfo) {
32
32
  try {
33
- var gitDir = execFileSync("git", ["rev-parse", "--git-dir"], {
34
- cwd: projectPath, encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
35
- }).trim();
36
- var commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
37
- cwd: projectPath, encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
38
- }).trim();
33
+ var gitDir = runGitSync(projectPath, ["rev-parse", "--git-dir"], null, osUserInfo).trim();
34
+ var commonDir = runGitSync(projectPath, ["rev-parse", "--git-common-dir"], null, osUserInfo).trim();
39
35
  var absGit = path.resolve(projectPath, gitDir);
40
36
  var absCommon = path.resolve(projectPath, commonDir);
41
37
  return absGit !== absCommon;
@@ -49,18 +45,21 @@ function isPathInside(parentPath, candidatePath) {
49
45
  return relative !== "" && relative !== ".." && relative.indexOf(".." + path.sep) !== 0 && !path.isAbsolute(relative);
50
46
  }
51
47
 
48
+ function worktreePath(projectPath, dirName) {
49
+ if (typeof dirName !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(dirName)) return null;
50
+ var resolvedParent = path.resolve(projectPath);
51
+ var resolvedWorktree = path.resolve(resolvedParent, dirName);
52
+ return isPathInside(resolvedParent, resolvedWorktree) ? resolvedWorktree : null;
53
+ }
54
+
52
55
  // Scan worktrees for a given project path
53
56
  // Returns array of { path, branch, bare, detached, external }
54
57
  // external = true when Git registered the worktree outside the main project folder
55
- function scanWorktrees(projectPath) {
58
+ function scanWorktrees(projectPath, osUserInfo) {
56
59
  var resolvedParent = path.resolve(projectPath);
60
+ try { resolvedParent = fs.realpathSync(resolvedParent); } catch (e) {}
57
61
  try {
58
- var output = execFileSync("git", ["worktree", "list", "--porcelain"], {
59
- cwd: resolvedParent,
60
- encoding: "utf8",
61
- timeout: 5000,
62
- stdio: ["pipe", "pipe", "pipe"],
63
- });
62
+ var output = runGitSync(resolvedParent, ["worktree", "list", "--porcelain"], null, osUserInfo);
64
63
  var all = parseWorktreeOutput(output);
65
64
  // Filter out bare worktrees and the main worktree itself
66
65
  var results = [];
@@ -68,10 +67,11 @@ function scanWorktrees(projectPath) {
68
67
  var wt = all[i];
69
68
  if (wt.bare) continue;
70
69
  var resolvedWt = path.resolve(wt.path);
71
- if (resolvedWt === resolvedParent) continue;
72
70
  // Git retains removed worktrees until `git worktree prune` runs. Do not
73
71
  // register those stale paths as Clay projects.
74
72
  if (!fs.existsSync(resolvedWt)) continue;
73
+ try { resolvedWt = fs.realpathSync(resolvedWt); } catch (e) {}
74
+ if (resolvedWt === resolvedParent) continue;
75
75
  wt.external = !isPathInside(resolvedParent, resolvedWt);
76
76
  wt.dirName = path.basename(wt.path);
77
77
  results.push(wt);
@@ -84,28 +84,19 @@ function scanWorktrees(projectPath) {
84
84
 
85
85
  // Create a new worktree inside the parent project directory
86
86
  // Returns { ok, path, error }
87
- function createWorktree(projectPath, branchName, dirName, baseBranch) {
87
+ function createWorktree(projectPath, branchName, dirName, baseBranch, osUserInfo) {
88
88
  var resolvedParent = path.resolve(projectPath);
89
- var wtPath = path.join(resolvedParent, dirName || branchName);
89
+ var wtPath = worktreePath(resolvedParent, dirName || branchName);
90
+ if (!wtPath) return { ok: false, error: "Invalid worktree directory name" };
90
91
  var base = baseBranch || "main";
91
92
  // Try creating with -b (new branch)
92
93
  try {
93
- execFileSync("git", ["worktree", "add", wtPath, "-b", branchName, base], {
94
- cwd: resolvedParent,
95
- encoding: "utf8",
96
- timeout: 15000,
97
- stdio: ["pipe", "pipe", "pipe"],
98
- });
94
+ runGitSync(resolvedParent, ["worktree", "add", wtPath, "-b", branchName, base], { timeout: 15000 }, osUserInfo);
99
95
  return { ok: true, path: wtPath };
100
96
  } catch (e) {
101
97
  // Branch may already exist, try without -b
102
98
  try {
103
- execFileSync("git", ["worktree", "add", wtPath, branchName], {
104
- cwd: resolvedParent,
105
- encoding: "utf8",
106
- timeout: 15000,
107
- stdio: ["pipe", "pipe", "pipe"],
108
- });
99
+ runGitSync(resolvedParent, ["worktree", "add", wtPath, branchName], { timeout: 15000 }, osUserInfo);
109
100
  return { ok: true, path: wtPath };
110
101
  } catch (e2) {
111
102
  return { ok: false, error: e2.message || "Failed to create worktree" };
@@ -115,17 +106,13 @@ function createWorktree(projectPath, branchName, dirName, baseBranch) {
115
106
 
116
107
  // Remove a worktree
117
108
  // Returns { ok, error }
118
- function removeWorktree(projectPath, worktreeDirName) {
109
+ function removeWorktree(projectPath, worktreeDirName, osUserInfo) {
119
110
  var resolvedParent = path.resolve(projectPath);
120
- var wtPath = path.join(resolvedParent, worktreeDirName);
111
+ var wtPath = worktreePath(resolvedParent, worktreeDirName);
112
+ if (!wtPath) return { ok: false, error: "Invalid worktree directory name" };
121
113
  // Try normal remove first
122
114
  try {
123
- execFileSync("git", ["worktree", "remove", wtPath], {
124
- cwd: resolvedParent,
125
- encoding: "utf8",
126
- timeout: 15000,
127
- stdio: ["pipe", "pipe", "pipe"],
128
- });
115
+ runGitSync(resolvedParent, ["worktree", "remove", wtPath], { timeout: 15000 }, osUserInfo);
129
116
  return { ok: true };
130
117
  } catch (e) {
131
118
  var errMsg = (e.stderr || e.message || "").toString();
package/lib/ws-schema.js CHANGED
@@ -40,10 +40,12 @@ var schema = {
40
40
  "split_group_set_pair": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Assign Driver/Worker roles on a split group (driverId null clears them)" },
41
41
  "pair_session_options": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Request vendors/models for the pair-session dialog (response reuses the same type)" },
42
42
  "pair_session_create": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Create a Driver/Split Worker session pair as a split group" },
43
- "worker_proposal_response": { direction: "c2s", handler: "lib/project-worker-proposal.js", description: "Accept or decline a Fable Worker suggestion with the selected runtime" },
43
+ "worker_proposal_response": { direction: "c2s", handler: "lib/project-worker-proposal.js", description: "Accept or decline an exact Driver Split Worker configuration with the selected runtime" },
44
44
  "project_assignment_response": { direction: "c2s", handler: "lib/workspace-assignment-service.js", description: "Approve or cancel an exact-session cross-project assignment proposal" },
45
45
  "home_clay_session_resolve": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Resolve an opaque Mate session reference through the exact bound source session" },
46
46
  "home_clay_session_target": { direction: "s2c", handler: "lib/public/modules/command-palette.js", description: "Return an owner-validated navigation target for a rendered Mate session reference" },
47
+ "home_clay_log_resolve": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Resolve an opaque Project Log reference through the exact bound Clay session" },
48
+ "home_clay_log_target": { direction: "s2c", handler: "lib/public/modules/command-palette.js", description: "Return an owner-validated project target for a rendered Log reference" },
47
49
 
48
50
  "session_list": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full list of sessions for the sidebar" },
49
51
  "session_switched": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Confirmation that active session changed" },
@@ -62,8 +64,8 @@ var schema = {
62
64
  "split_groups": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full persistent split-group list for the current user" },
63
65
  "split_group_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of a split-group mutation" },
64
66
  "pair_session_created": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of pair_session_create; carries the new group" },
65
- "worker_proposal": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Inline Fable suggestion to delegate execution to a Worker" },
66
- "worker_proposal_update": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Worker suggestion lifecycle update" },
67
+ "worker_proposal": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Recorded Split Worker creation/replacement runtime card with Driver rationale" },
68
+ "worker_proposal_update": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Worker card lifecycle and manual/auto decision audit update" },
67
69
  "project_assignment_proposal": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Approval card for a new owner-bound project assignment" },
68
70
  "project_assignment_status": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Durable project assignment lifecycle update" },
69
71
  "project_assignment_error": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Rejected or stale project assignment response" },
@@ -81,7 +83,8 @@ var schema = {
81
83
  // -----------------------------------------------------------------------
82
84
  // Chat / streaming
83
85
  // -----------------------------------------------------------------------
84
- "message": { direction: "c2s", handler: "lib/project-user-message.js", description: "Send a user chat message" },
86
+ "message": { direction: "c2s", handler: "lib/project-user-message.js", description: "Send an idempotent user chat message; message_ack confirms persistence" },
87
+ "message_ack": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Confirm that an idempotent user chat message was persisted" },
85
88
  "user_message": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Echo of the user message in the timeline" },
86
89
  "delta": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Incremental text delta from the assistant" },
87
90
  "result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Final assistant result text" },
@@ -247,9 +250,11 @@ var schema = {
247
250
  "set_update_channel": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the update channel (stable/beta)" },
248
251
  "check_update": { direction: "c2s", handler: "lib/project-sessions.js", description: "Check if an update is available" },
249
252
  "update_now": { direction: "c2s", handler: "lib/project-sessions.js", description: "Start the update process" },
253
+ "update_snooze": { direction: "c2s", handler: "lib/project-sessions.js", description: "Snooze the current update notification for an allowlisted duration" },
254
+ "update_snoozed": { direction: "s2c", handler: "lib/public/modules/update-snooze.js", description: "Result of an update snooze, fanned out to every device of that user" },
250
255
  "update_available": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "A new version is available" },
251
256
  "up_to_date": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Server is already on the latest version" },
252
- "update_started": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Update download/install has started" },
257
+ "update_started": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Update download/install has started; the version field is included only for admin-grade recipients" },
253
258
 
254
259
  // -----------------------------------------------------------------------
255
260
  // What's New
@@ -396,12 +401,31 @@ var schema = {
396
401
  "note_written": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Agent created or updated a sticky note" },
397
402
  "note_update": { direction: "c2s", handler: "lib/project-user-message.js", description: "Update a sticky note" },
398
403
  "note_updated": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Sticky note was updated" },
399
- "note_delete": { direction: "c2s", handler: "lib/project-user-message.js", description: "Delete a sticky note" },
400
- "note_deleted": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Sticky note was deleted" },
404
+ "note_close": { direction: "c2s", handler: "lib/project-user-message.js", description: "Close a sticky note (reversible; never deletes)" },
405
+ "note_reopen": { direction: "c2s", handler: "lib/project-user-message.js", description: "Reopen a closed sticky note" },
406
+ "note_delete": { direction: "c2s", handler: "lib/project-user-message.js", description: "Retired: older clients still send this; handled as a non-destructive close" },
407
+ "note_deleted": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Retired: never sent. Closing broadcasts note_updated instead" },
401
408
  "note_list_request": { direction: "c2s", handler: "lib/project-user-message.js", description: "Request the full notes list" },
402
409
  "note_bring_front": { direction: "c2s", handler: "lib/project-user-message.js", description: "Bring a note to front (update z-order)" },
403
410
  "notes_list": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full list of sticky notes" },
404
411
 
412
+ // -----------------------------------------------------------------------
413
+ // Project Logs
414
+ // -----------------------------------------------------------------------
415
+ "project_logs_list": { direction: "c2s", handler: "lib/project-logs.js", description: "List or search this project's logs (project scope is bound server-side)" },
416
+ "project_log_read": { direction: "c2s", handler: "lib/project-logs.js", description: "Read one project log entry by opaque ref" },
417
+ "project_log_comment": { direction: "c2s", handler: "lib/project-logs.js", description: "Append an attributed comment to a project log entry (append-only; author comes from the connection)" },
418
+ "project_log_delete": { direction: "c2s", handler: "lib/project-logs.js", description: "Delete a project log entry (project owner only; scope and identity are bound server-side)" },
419
+ "project_log_create": { direction: "c2s", handler: "lib/project-logs.js", description: "Retired. Canonical entries are written only by project agent sessions; this is always refused" },
420
+ "project_log_update": { direction: "c2s", handler: "lib/project-logs.js", description: "Retired. Canonical revisions are written only by project agent sessions; this is always refused" },
421
+ "project_logs_state": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "Correlated project log index for a list or search request" },
422
+ "project_log_entry": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "Correlated full project log entry" },
423
+ "project_log_commented": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "Correlated project log entry after a comment was appended" },
424
+ "project_log_comment_reviewed": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "A Project Driver resolved a log comment; carries identifiers and action only" },
425
+ "project_log_deleted": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "Confirm that the project owner deleted a project log entry" },
426
+ "project_log_updated": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "A project log entry was created, revised, or deleted (bounded ledger metadata only)" },
427
+ "project_logs_error": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "Correlated project log request failure" },
428
+
405
429
  // -----------------------------------------------------------------------
406
430
  // Browser extension
407
431
  // -----------------------------------------------------------------------
@@ -471,6 +495,8 @@ var schema = {
471
495
  "tool_llm_op": { direction: "c2s", handler: "lib/server-tools.js", description: "Run a permission-gated vendor-neutral capsule LLM completion" },
472
496
  "tool_llm_config_get": { direction: "c2s", handler: "lib/server-tools.js", description: "Get the concrete configured provider and model used by Capsule LLM calls" },
473
497
  "tool_control_response": { direction: "c2s", handler: "lib/server-tools.js", description: "Resolve a mate tool-control request with browser runtime state or an error" },
498
+ "tool_server_control": { direction: "c2s", handler: "lib/server-tools.js", description: "Human snapshot or action against a server-runtime Capsule, through the same Logic a Mate calls" },
499
+ "tool_frame_url": { direction: "c2s", handler: "lib/server-tools.js", description: "Mint a one-time separate-origin URL for a Capsule's sandboxed rich Display frame" },
474
500
  "tools_state": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full installed tool definitions" },
475
501
  "tool_installed": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "An installed tool changed" },
476
502
  "tool_source_state": { direction: "s2c", handler: "lib/public/modules/home-capsule-source.js", description: "Correlated authored Capsule source or read error" },
@@ -480,6 +506,10 @@ var schema = {
480
506
  "tool_llm_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of a capsule LLM completion" },
481
507
  "tool_llm_config_state": { direction: "s2c", handler: "lib/public/modules/tool-llm-status.js", description: "Concrete provider and model state for Capsule LLM calls" },
482
508
  "tool_control_request": { direction: "s2c", handler: "lib/public/modules/home-tools.js", description: "Ask the connected home client to snapshot or drive a sandboxed tool" },
509
+ "tool_server_state": { direction: "s2c", handler: "lib/public/modules/home-tools.js", description: "Correlated server-runtime Capsule state for the human who requested it, or the refusal that blocked it" },
510
+ "tool_server_event": { direction: "s2c", handler: "lib/public/modules/home-tools.js", description: "Ordered causal act event {seq, actor, action, previous, next} pushed to the user's Displays whenever server-runtime Capsule Logic changes state" },
511
+ "tool_frame_url_state": { direction: "s2c", handler: "lib/public/modules/home-tool-frame.js", description: "Correlated one-time rich Display frame URL, or the refusal that blocked it" },
512
+ "capsule_game_session": { direction: "s2c", handler: "lib/public/modules/home-tools.js", description: "A Capsule game session opened or advanced for this user; an explicit game start navigates the home board into it" },
483
513
  "tools_error": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Tool registry, runtime, or storage error" },
484
514
 
485
515
  // -----------------------------------------------------------------------
@@ -632,6 +662,7 @@ var schema = {
632
662
  "home_mate_session_identity": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Promote an active Home session from its local reference to its durable runtime identity" },
633
663
  "home_mate_sessions_state": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Owned visible Mate conversations with sanitized runtime identity, model, status, and timestamps" },
634
664
  "home_mate_delta": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Selected mate's streaming assistant text delta" },
665
+ "home_mate_segment": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "A tool call ended the mate's current spoken burst; the client seals the streamed bubble and later text starts a fresh one" },
635
666
  "home_mate_done": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Selected mate's turn finished" },
636
667
  "home_debate_proposal": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Session-bound debate proposal for the active Home transcript" },
637
668
  "home_debate_proposal_resolved": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Server-confirmed result for a Home debate proposal" },
@@ -424,22 +424,32 @@ async function handleQueryStart(msg) {
424
424
  function perf(label) { console.log("[PERF] sdk-worker: " + label + " +" + (Date.now() - t0) + "ms (local +" + (Date.now() - localT0) + "ms)"); }
425
425
  perf("handleQueryStart entered");
426
426
 
427
+ // Socket messages keep arriving while the SDK import awaits. Accept input
428
+ // and cancellation synchronously, before yielding to the next IPC message.
429
+ messageQueue = createMessageQueue();
430
+ abortController = new AbortController();
431
+ if (msg.prompt) messageQueue.push(msg.prompt);
432
+
427
433
  var sdk;
428
434
  try {
429
435
  perf("loading SDK");
430
436
  sdk = await getSDK();
431
437
  perf("SDK loaded");
432
438
  } catch (e) {
439
+ messageQueue.end();
440
+ messageQueue = null;
441
+ abortController = null;
442
+ sdkModule = null;
433
443
  sendToDaemon({ type: "query_error", error: "Failed to load SDK: " + (e.message || e), exitCode: null, stderr: null });
434
444
  return;
435
445
  }
436
446
 
437
- messageQueue = createMessageQueue();
438
- abortController = new AbortController();
439
-
440
- // Push the initial user message
441
- if (msg.prompt) {
442
- messageQueue.push(msg.prompt);
447
+ if (abortController.signal.aborted) {
448
+ messageQueue.end();
449
+ messageQueue = null;
450
+ abortController = null;
451
+ sendToDaemon({ type: "query_done" });
452
+ return;
443
453
  }
444
454
 
445
455
  // Build query options (callbacks are local, everything else from daemon)
@@ -260,6 +260,18 @@ function flattenEvent(raw) {
260
260
  base.status = raw.status;
261
261
  return base;
262
262
  }
263
+ // API retry notices are transient transport state, not conversation
264
+ // errors. Preserve the structured counters so the host can bound overload
265
+ // churn without recording one red error row per SDK retry.
266
+ if (raw.subtype === "api_retry") {
267
+ base.yokeType = "api_retry";
268
+ base.attempt = raw.attempt || 0;
269
+ base.maxRetries = raw.max_retries || 0;
270
+ base.retryDelayMs = raw.retry_delay_ms || 0;
271
+ base.error = raw.error || "unknown";
272
+ base.errorStatus = raw.error_status == null ? null : raw.error_status;
273
+ return base;
274
+ }
263
275
  if (raw.subtype === "task_started") {
264
276
  base.yokeType = "task_started";
265
277
  base.parentToolId = raw.tool_use_id;
@@ -28,6 +28,21 @@ var CODEX_REASONING_DEFAULTS = {
28
28
  model_reasoning_summary: "detailed",
29
29
  model_supports_reasoning_summaries: true,
30
30
  };
31
+ var DEFAULT_CODEX_MODEL = "gpt-6-astra";
32
+ var CODEX_NOTE_TOOLS = {
33
+ list_notes: true,
34
+ write_note: true,
35
+ close_note: true,
36
+ reopen_note: true,
37
+ remove_note: true,
38
+ };
39
+
40
+ function canonicalDynamicPermissionToolName(toolName) {
41
+ if (CODEX_NOTE_TOOLS[toolName]) return "mcp__clay-notes__" + toolName;
42
+ if (toolName === "send_to_partner" || toolName === "read_partner") return "mcp__clay-sessions__" + toolName;
43
+ if (toolName === "present_markdown_edit") return "mcp__clay-documents__" + toolName;
44
+ return toolName;
45
+ }
31
46
 
32
47
  // --- Event flattening ---
33
48
  // Converts app-server JSON-RPC notifications into flat objects with a yokeType field.
@@ -723,7 +738,7 @@ function createEventState(model) {
723
738
  done: false,
724
739
  aborted: false,
725
740
  loopStarted: false,
726
- model: model || "gpt-5.6-terra",
741
+ model: model || DEFAULT_CODEX_MODEL,
727
742
  textBlocks: {},
728
743
  textLengths: {},
729
744
  thinkingBlocks: {},
@@ -912,14 +927,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
912
927
  appServer.respond(msg.id, { contentItems: contentItems, success: !(result && result.isError) });
913
928
  });
914
929
  }
915
- var permissionToolName = dynamicParams.tool;
916
- if (permissionToolName === "list_notes" || permissionToolName === "write_note" || permissionToolName === "remove_note") {
917
- permissionToolName = "mcp__clay-notes__" + permissionToolName;
918
- } else if (permissionToolName === "send_to_partner" || permissionToolName === "read_partner") {
919
- permissionToolName = "mcp__clay-sessions__" + permissionToolName;
920
- } else if (permissionToolName === "present_markdown_edit") {
921
- permissionToolName = "mcp__clay-documents__" + permissionToolName;
922
- }
930
+ var permissionToolName = canonicalDynamicPermissionToolName(dynamicParams.tool);
923
931
  var permission = canUseTool
924
932
  ? Promise.resolve(canUseTool(permissionToolName, dynamicParams.arguments || {}, {
925
933
  toolUseID: dynamicParams.callId || String(msg.id),
@@ -1073,7 +1081,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
1073
1081
 
1074
1082
  // Start or resume thread
1075
1083
  var threadParams = {
1076
- model: queryOpts.model || "gpt-5.6-terra",
1084
+ model: queryOpts.model || DEFAULT_CODEX_MODEL,
1077
1085
  sandbox: queryOpts.sandboxMode || "workspace-write",
1078
1086
  approvalPolicy: queryOpts.approvalPolicy || "on-request",
1079
1087
  cwd: queryOpts.cwd,
@@ -1241,7 +1249,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
1241
1249
  },
1242
1250
 
1243
1251
  setModel: function(model) {
1244
- state.model = model || "gpt-5.6-terra";
1252
+ state.model = model || DEFAULT_CODEX_MODEL;
1245
1253
  return Promise.resolve();
1246
1254
  },
1247
1255
 
@@ -1330,9 +1338,10 @@ function createCodexAdapter(opts) {
1330
1338
  // slow/failed `initialize` leaves the picker empty and the chip shows the
1331
1339
  // previous vendor's model.
1332
1340
  // Kept in sync with what `model/list` reports for the bundled codex build
1333
- // (0.152.1). Models the CLI no longer offers are rejected at turn/start, so
1341
+ // (0.153.4). Models the CLI no longer offers are rejected at turn/start, so
1334
1342
  // listing them would only surface a runtime failure in the model picker.
1335
1343
  var CODEX_MODELS = [
1344
+ "gpt-6-astra",
1336
1345
  "gpt-5.6-terra",
1337
1346
  "gpt-5.6-sol",
1338
1347
  "gpt-5.6-luna",
@@ -1430,7 +1439,7 @@ function createCodexAdapter(opts) {
1430
1439
  function buildReadyResponse(skillNames) {
1431
1440
  return {
1432
1441
  models: _cachedModels,
1433
- defaultModel: "gpt-5.6-terra",
1442
+ defaultModel: DEFAULT_CODEX_MODEL,
1434
1443
  skills: skillNames || [],
1435
1444
  slashCommands: skillNames || [],
1436
1445
  fastModeState: null,
@@ -1767,7 +1776,7 @@ function createCodexAdapter(opts) {
1767
1776
  throw new Error("[yoke/codex] Adapter not initialized. Call init() first.");
1768
1777
  }
1769
1778
 
1770
- var model = queryOpts.model || "gpt-5.6-terra";
1779
+ var model = queryOpts.model || DEFAULT_CODEX_MODEL;
1771
1780
  var ac = queryOpts.abortController || new AbortController();
1772
1781
  var activeEntry = {
1773
1782
  abort: function() {
@@ -1984,6 +1993,7 @@ function createCodexAdapter(opts) {
1984
1993
  module.exports = {
1985
1994
  createCodexAdapter: createCodexAdapter,
1986
1995
  contractTestKit: {
1996
+ canonicalDynamicPermissionToolName: canonicalDynamicPermissionToolName,
1987
1997
  createEventState: createEventState,
1988
1998
  createQueryHandle: createCodexQueryHandle,
1989
1999
  normalizeEvent: flattenEvent,
@@ -32,7 +32,7 @@ var VENDOR_REGISTRY = {
32
32
  binaryName: "codex",
33
33
  avatar: "/codex-avatar.png",
34
34
  sessionModes: ["gui"],
35
- effortLevels: ["minimal", "low", "medium", "high", "xhigh"],
35
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
36
36
  osUserIsolation: true,
37
37
  sessionBoundTools: false,
38
38
  usageDashboard: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "4.0.0-beta.9",
3
+ "version": "4.0.0",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",
@@ -51,7 +51,7 @@
51
51
  "dependencies": {
52
52
  "@anthropic-ai/claude-agent-sdk": "^0.3.258",
53
53
  "@lydell/node-pty": "^1.2.0-beta.3",
54
- "@openai/codex": "^0.152.1",
54
+ "@openai/codex": "^0.153.4",
55
55
  "@seald-io/nedb": "^4.1.2",
56
56
  "imapflow": "^1.3.1",
57
57
  "nodemailer": "^9.0.5",