clay-server 5.1.0-beta.1 → 5.1.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 (157) hide show
  1. package/LICENSES/README.md +4 -0
  2. package/LICENSES/dicebear-avatars.md +16 -0
  3. package/lib/daemon-project-access.js +7 -1
  4. package/lib/daemon.js +2 -0
  5. package/lib/dicebear-avatar.js +62 -0
  6. package/lib/knowledge-db-service.js +175 -0
  7. package/lib/knowledge-db-validation.js +0 -0
  8. package/lib/knowledge-document-index.js +49 -0
  9. package/lib/knowledge-draft-store.js +56 -0
  10. package/lib/knowledge-file-store.js +199 -0
  11. package/lib/knowledge-markdown.js +128 -0
  12. package/lib/mate-knowledge-mcp-server.js +29 -6
  13. package/lib/mate-knowledge-service.js +23 -2
  14. package/lib/mate-knowledge-sync.js +36 -21
  15. package/lib/mate-workspace-color.js +4 -0
  16. package/lib/mates.js +18 -0
  17. package/lib/project-connection.js +36 -2
  18. package/lib/project-debate-proposal.js +6 -0
  19. package/lib/project-debate-workspace.js +43 -0
  20. package/lib/project-debate.js +1 -0
  21. package/lib/project-knowledge.js +315 -136
  22. package/lib/project-mate-creation-proposal.js +1 -0
  23. package/lib/project-sessions.js +39 -2
  24. package/lib/project-user-knowledge.js +60 -0
  25. package/lib/project.js +59 -18
  26. package/lib/public/app.js +73 -34
  27. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  28. package/lib/public/css/clay-workspace.css +105 -0
  29. package/lib/public/css/debates-workbench.css +47 -0
  30. package/lib/public/css/home-debate-live.css +26 -0
  31. package/lib/public/css/home-hub.css +3 -1
  32. package/lib/public/css/home-mate-model-picker.css +15 -131
  33. package/lib/public/css/home-mate-settings.css +168 -16
  34. package/lib/public/css/icon-strip.css +249 -32
  35. package/lib/public/css/mate-conversation.css +176 -0
  36. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  37. package/lib/public/css/mate-workspace-color.css +16 -0
  38. package/lib/public/css/mates.css +28 -28
  39. package/lib/public/css/messages.css +1 -114
  40. package/lib/public/css/mobile-nav.css +16 -16
  41. package/lib/public/css/overlays.css +8 -0
  42. package/lib/public/css/project-logs.css +7 -0
  43. package/lib/public/css/project-mate-creation.css +105 -0
  44. package/lib/public/css/right-workbench.css +3 -3
  45. package/lib/public/css/session-folders.css +80 -21
  46. package/lib/public/css/sidebar.css +183 -107
  47. package/lib/public/css/title-bar.css +151 -0
  48. package/lib/public/css/worker-delegation.css +47 -0
  49. package/lib/public/css/worker-proposal.css +72 -150
  50. package/lib/public/css/you.css +74 -0
  51. package/lib/public/index.html +33 -101
  52. package/lib/public/modules/account-island.js +230 -0
  53. package/lib/public/modules/app-cursors.js +19 -43
  54. package/lib/public/modules/app-debate-ui.js +4 -3
  55. package/lib/public/modules/app-header.js +106 -0
  56. package/lib/public/modules/app-message-router.js +12 -3
  57. package/lib/public/modules/app-messages.js +34 -9
  58. package/lib/public/modules/app-notifications.js +4 -7
  59. package/lib/public/modules/app-projects.js +5 -38
  60. package/lib/public/modules/app-rendering.js +12 -13
  61. package/lib/public/modules/avatar.js +25 -9
  62. package/lib/public/modules/chat-render-runtime.js +16 -4
  63. package/lib/public/modules/command-palette.js +2 -15
  64. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  65. package/lib/public/modules/debate-controls.js +299 -0
  66. package/lib/public/modules/debate.js +2 -0
  67. package/lib/public/modules/debates-workbench.js +339 -0
  68. package/lib/public/modules/default-ai.js +81 -65
  69. package/lib/public/modules/home-debate-controls.js +18 -289
  70. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  71. package/lib/public/modules/home-mate-color-picker.js +66 -0
  72. package/lib/public/modules/home-mate-creation.js +1 -1
  73. package/lib/public/modules/home-mate-instructions.js +90 -0
  74. package/lib/public/modules/home-mate-model-picker.js +80 -271
  75. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  76. package/lib/public/modules/home-mate-settings.js +118 -125
  77. package/lib/public/modules/home-shell.js +3 -0
  78. package/lib/public/modules/home-surface-boot.js +1 -9
  79. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  80. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  81. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  82. package/lib/public/modules/knowledge-history-state.js +55 -0
  83. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  84. package/lib/public/modules/knowledge-preview-links.js +70 -0
  85. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  86. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  87. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  88. package/lib/public/modules/mate-knowledge.js +11 -3
  89. package/lib/public/modules/mate-management.js +54 -10
  90. package/lib/public/modules/mate-sidebar.js +11 -1
  91. package/lib/public/modules/mate-workspace-color.js +33 -0
  92. package/lib/public/modules/mention.js +5 -13
  93. package/lib/public/modules/profile.js +51 -18
  94. package/lib/public/modules/project-chat-identity.js +79 -0
  95. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  96. package/lib/public/modules/project-debate-workspace.js +185 -0
  97. package/lib/public/modules/project-header-identity.js +127 -0
  98. package/lib/public/modules/project-logs-render.js +9 -4
  99. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  100. package/lib/public/modules/project-mate-navigation.js +238 -0
  101. package/lib/public/modules/project-session-navigation.js +29 -0
  102. package/lib/public/modules/project-switcher.js +6 -8
  103. package/lib/public/modules/projects-hub-view.js +5 -6
  104. package/lib/public/modules/projects-hub.js +2 -6
  105. package/lib/public/modules/qrcode.js +27 -8
  106. package/lib/public/modules/search-clay-chat.js +8 -11
  107. package/lib/public/modules/session-activity-tooltip.js +12 -0
  108. package/lib/public/modules/session-create-form.js +86 -14
  109. package/lib/public/modules/session-create.js +50 -7
  110. package/lib/public/modules/session-list-visibility.js +41 -0
  111. package/lib/public/modules/session-presence-ui.js +4 -1
  112. package/lib/public/modules/session-title-marquee.js +51 -0
  113. package/lib/public/modules/sidebar-mates.js +3 -50
  114. package/lib/public/modules/sidebar-mobile.js +63 -60
  115. package/lib/public/modules/sidebar-projects.js +84 -97
  116. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  117. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  118. package/lib/public/modules/sidebar-sessions.js +49 -50
  119. package/lib/public/modules/sidebar.js +11 -13
  120. package/lib/public/modules/thinking-lifecycle.js +7 -6
  121. package/lib/public/modules/tool-palette-order.js +4 -3
  122. package/lib/public/modules/tool-palette.js +13 -3
  123. package/lib/public/modules/tools.js +22 -22
  124. package/lib/public/modules/worker-proposal.js +42 -8
  125. package/lib/public/modules/worker-runtime-picker.js +115 -0
  126. package/lib/public/modules/you-activity.js +121 -0
  127. package/lib/public/modules/you.js +251 -0
  128. package/lib/public/style.css +16 -8
  129. package/lib/public/vendor/codemirror/LICENSE +21 -0
  130. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  131. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  132. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  133. package/lib/public/vendor/codemirror/markdown.js +886 -0
  134. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  135. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  136. package/lib/sdk-bridge.js +15 -3
  137. package/lib/server-admin.js +11 -0
  138. package/lib/server-home-chat-events.js +1 -1
  139. package/lib/server-home-chat.js +25 -3
  140. package/lib/server-home-debate-planning.js +4 -1
  141. package/lib/server-home-debates.js +2 -0
  142. package/lib/server-home-mate-creation.js +4 -1
  143. package/lib/server-mate-instructions.js +44 -0
  144. package/lib/server-mates.js +12 -3
  145. package/lib/server-palette.js +1 -1
  146. package/lib/server-settings.js +65 -1
  147. package/lib/server-user-knowledge.js +119 -0
  148. package/lib/server.js +21 -1
  149. package/lib/session-visibility.js +7 -2
  150. package/lib/sessions.js +32 -6
  151. package/lib/user-knowledge-service.js +167 -0
  152. package/lib/users-permissions.js +7 -0
  153. package/lib/users-project-mate-filter-preferences.js +39 -0
  154. package/lib/users.js +6 -0
  155. package/lib/workspace-query-access.js +4 -1
  156. package/lib/ws-schema.js +50 -0
  157. package/package.json +10 -1
@@ -1,184 +1,363 @@
1
- var fs = require("fs");
2
1
  var path = require("path");
2
+ var crypto = require("crypto");
3
3
  var mateSync = require("./mate-knowledge-sync");
4
+ var knowledgeFiles = require("./knowledge-file-store");
5
+ var markdown = require("./knowledge-markdown");
6
+ var draftStore = require("./knowledge-draft-store");
7
+ var documentIndexModule = require("./knowledge-document-index");
8
+ var knowledgeDbModule = require("./knowledge-db-service");
9
+ var knowledgeImport = require("./knowledge-import");
4
10
 
5
- /**
6
- * Attach knowledge file management to a project context.
7
- *
8
- * ctx fields:
9
- * cwd, isMate, sendTo, matesModule, projectOwnerId
10
- */
11
11
  function attachKnowledge(ctx) {
12
12
  var cwd = ctx.cwd;
13
13
  var isMate = ctx.isMate;
14
14
  var sendTo = ctx.sendTo;
15
15
  var matesModule = ctx.matesModule;
16
16
  var getProjectOwnerId = ctx.getProjectOwnerId;
17
+ var metadataKey = crypto.createHash("sha256").update(path.resolve(cwd)).digest("hex").slice(0, 24);
18
+ var metadataRoot = path.join(ctx.knowledgeBaseDir || path.join(cwd, ".clay"), "knowledge-documents", metadataKey);
19
+ var files = knowledgeFiles.createKnowledgeFileStore(cwd, { metaDir: metadataRoot });
20
+ var drafts = draftStore.createKnowledgeDraftStore(ctx.knowledgeBaseDir || path.join(cwd, ".clay"), path.resolve(cwd));
21
+ var indexer = documentIndexModule.createKnowledgeDocumentIndex({ list: listKnowledgeFiles, read: readEntry, maxDocumentBytes: knowledgeFiles.MAX_CONTENT_BYTES });
22
+ var dbScope = isMate ? mateSync.scopeForMateDir(cwd) : null;
23
+ var dbImporter = dbScope ? knowledgeImport.createKnowledgeImporter({ scopeId: dbScope.scopeId, baseDir: ctx.knowledgeBaseDir }) : null;
24
+ var databases = isMate ? knowledgeDbModule.createKnowledgeDbService({
25
+ projectDir: cwd,
26
+ authorizeTextReference: function (id) { return files.list().some(function (file) { return file.id === id && file.itemType === "text"; }); },
27
+ projectSummary: dbImporter ? function (summary) {
28
+ return dbImporter.importRecord({ importKey: "knowledge-database:" + summary.databaseId, sourceHash: String(summary.revision) + ":" + String(summary.archived),
29
+ kind: "knowledge-database-summary", name: summary.name, content: summary.content,
30
+ source: { namespace: "knowledge-database", databaseId: summary.databaseId, relPath: "knowledge/.clay-db/databases.db" }, actor: summary.actor });
31
+ } : null,
32
+ }) : null;
33
+ var databaseReady = databases && databases.hasStore() ? databases.initialize() : null;
34
+ if (databaseReady) databaseReady.catch(function (error) { console.error("[knowledge-db] Failed to initialize:", error.message); });
35
+
36
+ function canWrite(ws) {
37
+ if (typeof ctx.canManageKnowledge === "function") return ctx.canManageKnowledge(ws) === true;
38
+ var ownerId = getProjectOwnerId();
39
+ return !ownerId || !!(ws && ws._clayUser && ws._clayUser.id === ownerId);
40
+ }
41
+
42
+ function actor(ws) {
43
+ var user = (ws && ws._clayUser) || null;
44
+ return { type: user ? "user" : "system", userId: user ? user.id : null, displayName: user ? (user.displayName || user.username) : null };
45
+ }
46
+
47
+ function mirror(ws, name) {
48
+ if (!isMate) return { ok: true, names: [] };
49
+ var summary = mateSync.syncMateSource({ mateDir: cwd, fileName: name, baseDir: ctx.knowledgeBaseDir, actor: actor(ws) });
50
+ return { ok: !!summary && summary.failed === 0, names: [name], error: summary && summary.errors[0] ? summary.errors[0].message : "Knowledge index synchronization failed." };
51
+ }
52
+
53
+ function mirrorMany(ws, names) {
54
+ var failures = [];
55
+ for (var i = 0; i < names.length; i++) {
56
+ var mirrored = mirror(ws, names[i]);
57
+ if (!mirrored.ok) failures.push(mirrored.error);
58
+ }
59
+ return { ok: failures.length === 0, names: names.slice(), error: failures[0] || null };
60
+ }
61
+
62
+ function addSyncState(payload, sync) {
63
+ if (sync.ok) return payload;
64
+ return Object.assign(payload, { indexSyncPending: true, syncNames: sync.names, syncError: sync.error });
65
+ }
17
66
 
18
67
  function listKnowledgeFiles() {
19
- var knowledgeDir = path.join(cwd, "knowledge");
20
- var files = [];
21
- try {
22
- var entries = fs.readdirSync(knowledgeDir);
23
- for (var ki = 0; ki < entries.length; ki++) {
24
- if (entries[ki] === "session-digests.jsonl") continue;
25
- if (entries[ki] === "sticky-notes.md") continue;
26
- if (entries[ki] === "memory-summary.md") continue;
27
- if (entries[ki].endsWith(".md") || entries[ki].endsWith(".jsonl")) {
28
- var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
29
- files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs, common: false });
30
- }
31
- }
32
- } catch (e) { /* dir may not exist */ }
33
- files.sort(function (a, b) { return b.mtime - a.mtime; });
68
+ var listed = files.list();
69
+ for (var i = 0; i < listed.length; i++) listed[i].common = false;
70
+ if (!isMate) return listed;
71
+ var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
72
+ var thisMateId = path.basename(cwd);
73
+ for (var p = 0; p < listed.length; p++) listed[p].promoted = matesModule.isPromoted(mateCtx, thisMateId, listed[p].name);
74
+ var common = matesModule.getCommonKnowledgeForMate(mateCtx, thisMateId);
75
+ for (var c = 0; c < common.length; c++) {
76
+ if (common[c].ownMateId !== thisMateId) listed.push(Object.assign({}, common[c], { id: "shared:" + common[c].ownMateId + ":" + common[c].name,
77
+ itemType: /\.jsonl$/i.test(common[c].name) ? "db" : "text", writable: false, revision: null }));
78
+ }
79
+ return listed;
80
+ }
81
+
82
+ function listKnowledgeItems(ws) {
83
+ var textItems = listKnowledgeFiles();
84
+ if (!databases || !databases.hasStore()) return textItems;
85
+ function combine(result) { return textItems.concat(result.databases.map(function (item) { return Object.assign({}, item, { writable: canWrite(ws), common: false }); })); }
86
+ if (databases.isReady()) return combine(databases.listCached({}));
87
+ return databaseReady.then(function () { return combine(databases.listCached({})); });
88
+ }
34
89
 
35
- if (isMate) {
90
+ function response(ws, msg, payload) {
91
+ sendTo(ws, Object.assign({ requestId: msg.requestId || null }, payload));
92
+ }
93
+
94
+ function failure(ws, msg, error, extra) {
95
+ response(ws, msg, Object.assign({ type: "knowledge_error", operation: msg.type, name: msg.name || msg.from || null,
96
+ code: error.code || "FAILED", error: error.message || "Knowledge operation failed.", currentRevision: error.currentRevision || null }, extra || {}));
97
+ }
98
+
99
+ function requireWrite(ws, msg) {
100
+ if (canWrite(ws)) return true;
101
+ failure(ws, msg, Object.assign(new Error("Only the Mate owner can change Knowledge documents."), { code: "FORBIDDEN" }));
102
+ return false;
103
+ }
104
+
105
+ function readEntry(file) {
106
+ if (file.common && file.ownMateId && isMate) {
36
107
  var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
37
- var thisMateId = path.basename(cwd);
38
- for (var pi = 0; pi < files.length; pi++) {
39
- files[pi].promoted = matesModule.isPromoted(mateCtx, thisMateId, files[pi].name);
40
- }
41
- var commonFiles = matesModule.getCommonKnowledgeForMate(mateCtx, thisMateId);
42
- for (var ci = 0; ci < commonFiles.length; ci++) {
43
- if (commonFiles[ci].ownMateId !== thisMateId) {
44
- files.push(commonFiles[ci]);
45
- }
46
- }
108
+ return matesModule.readCommonKnowledgeFile(mateCtx, file.ownMateId, file.name);
47
109
  }
48
- return files;
110
+ return files.read(file.name).content;
49
111
  }
50
112
 
51
- function listKnowledgeFilesBasic() {
52
- var knowledgeDir = path.join(cwd, "knowledge");
53
- var files = [];
54
- try {
55
- var entries = fs.readdirSync(knowledgeDir);
56
- for (var ki = 0; ki < entries.length; ki++) {
57
- if (entries[ki].endsWith(".md") || entries[ki].endsWith(".jsonl")) {
58
- var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
59
- files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs });
113
+ function documentIndex() {
114
+ return indexer.reconcile();
115
+ }
116
+
117
+ function findDocument(index, name, id) {
118
+ var matches;
119
+ if (id) { matches = index.filter(function (item) { return item.id === id; }); return matches.length === 1 ? matches[0] : null; }
120
+ matches = index.filter(function (item) { return item.name === name; });
121
+ return matches.length === 1 ? matches[0] : null;
122
+ }
123
+
124
+ function linksFor(index, target) {
125
+ var outgoing = []; var backlinks = [];
126
+ for (var i = 0; i < target.parsed.links.length; i++) {
127
+ var link = target.parsed.links[i]; var resolved = markdown.resolveLink(target.name, link, index, target.id);
128
+ var headingValid = !link.heading || !!(resolved && resolved.parsed.headings.some(function (heading) { return heading.slug === markdown.slug(link.heading); }));
129
+ outgoing.push({ kind: link.kind, raw: link.raw, label: link.alias || link.target || link.heading, target: link.target, heading: link.heading,
130
+ headingValid: headingValid, documentId: resolved && resolved.id || null, documentKey: resolved && resolved.key || null,
131
+ name: resolved && resolved.name || null, external: link.external === true, self: !link.target && !!link.heading, unresolved: !resolved && !link.external });
132
+ }
133
+ for (var d = 0; d < index.length; d++) {
134
+ if (index[d] === target) continue;
135
+ for (var l = 0; l < index[d].parsed.links.length; l++) {
136
+ var resolvedTarget = markdown.resolveLink(index[d].name, index[d].parsed.links[l], index, index[d].id);
137
+ if (resolvedTarget && resolvedTarget.id === target.id) {
138
+ backlinks.push({ documentId: index[d].id, documentKey: index[d].key, name: index[d].name, heading: index[d].parsed.links[l].heading,
139
+ snippet: markdown.snippet(index[d].content, index[d].parsed.links[l].start) }); break;
60
140
  }
61
141
  }
62
- } catch (e) {}
63
- files.sort(function (a, b) { return b.mtime - a.mtime; });
64
- if (isMate) {
65
- var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
66
- var thisMateId = path.basename(cwd);
67
- for (var pi = 0; pi < files.length; pi++) {
68
- files[pi].common = false;
69
- files[pi].promoted = matesModule.isPromoted(mateCtx, thisMateId, files[pi].name);
70
- }
71
- var commonFiles = matesModule.getCommonKnowledgeForMate(mateCtx, thisMateId);
72
- for (var ci = 0; ci < commonFiles.length; ci++) {
73
- if (commonFiles[ci].ownMateId !== thisMateId) files.push(commonFiles[ci]);
142
+ }
143
+ return { outgoing: outgoing, backlinks: backlinks };
144
+ }
145
+
146
+ function relativeLink(from, to, withExtension) {
147
+ var value = path.posix.relative(path.posix.dirname(from), to) || path.posix.basename(to);
148
+ if (withExtension === false) value = value.replace(/\.md$/i, "");
149
+ return value;
150
+ }
151
+
152
+ function rewriteMoveLinks(index, target, nextName) {
153
+ var changes = [];
154
+ for (var i = 0; i < index.length; i++) {
155
+ var source = index[i];
156
+ if (source.common || source.itemType === "db" || !source.writable) continue;
157
+ var replacements = [];
158
+ for (var l = 0; l < source.parsed.links.length; l++) {
159
+ var link = source.parsed.links[l]; var resolved = markdown.resolveLink(source.name, link, index, source.id);
160
+ if (!resolved || source.id !== target.id && resolved.id !== target.id) continue;
161
+ var sourceAfter = source.id === target.id ? nextName : source.name;
162
+ var targetAfter = resolved.id === target.id ? nextName : resolved.name;
163
+ var nextTarget = relativeLink(sourceAfter, targetAfter, link.kind === "markdown" || link.kind === "image");
164
+ var suffix = link.heading ? "#" + link.heading : ""; var replacement;
165
+ if (link.kind === "wiki" || link.kind === "embed") replacement = (link.kind === "embed" ? "!" : "") + "[[" + nextTarget + suffix + (link.alias ? "|" + link.alias : "") + "]]";
166
+ else replacement = (link.kind === "image" ? "!" : "") + "[" + link.alias + "](" + nextTarget + suffix + ")";
167
+ replacements.push({ start: link.start, end: link.end, value: replacement });
74
168
  }
169
+ if (!replacements.length) continue;
170
+ var content = source.content;
171
+ for (var r = replacements.length - 1; r >= 0; r--) content = content.slice(0, replacements[r].start) + replacements[r].value + content.slice(replacements[r].end);
172
+ changes.push({ id: source.id, name: source.name, revision: source.revision, content: content, count: replacements.length });
75
173
  }
76
- return files;
174
+ return changes;
77
175
  }
78
176
 
79
- // Mirror a Mate-owned knowledge file into the Clay-wide Knowledge backend
80
- // after the legacy write has already succeeded. The legacy file stays
81
- // authoritative; this only keeps the new scope from going stale.
82
- function mirrorMateSource(ws, fileName) {
83
- if (!isMate) return;
84
- var user = (ws && ws._clayUser) || null;
85
- mateSync.syncQuietly({
86
- mateDir: cwd,
87
- fileName: fileName,
88
- actor: {
89
- type: user ? "user" : "system",
90
- userId: user ? user.id : null,
91
- displayName: user ? (user.displayName || user.username) : null,
92
- },
93
- });
177
+ function searchFiles(query, offset, limit) {
178
+ var start = Math.max(0, Number(offset) || 0); var count = Math.max(1, Math.min(100, Number(limit) || 50)); var direct = indexer.search(query, start, count); var needle = String(query || "").trim().toLowerCase();
179
+ if (!databases || !databases.hasStore()) return direct;
180
+ function combineSearch(result) {
181
+ var databaseMatches = result.databases.filter(function (item) { return needle && (item.name.toLowerCase().indexOf(needle) !== -1 || item.schema.some(function (field) { return field.name.toLowerCase().indexOf(needle) !== -1 || field.type.indexOf(needle) !== -1; })); }).map(function (item) { return Object.assign({}, item, { itemType: "db", writable: true, common: false, snippet: "Database · " + item.recordCount + " records" }); });
182
+ if (!databaseMatches.length) return direct;
183
+ var files = direct.files.slice(); var textTotal = direct.total; var databaseOffset = Math.max(0, start - textTotal); var room = count - files.length;
184
+ if (room > 0) files = files.concat(databaseMatches.slice(databaseOffset, databaseOffset + room));
185
+ return { files: files, total: textTotal + databaseMatches.length, offset: start, limit: count, complete: direct.complete, errors: direct.errors };
186
+ }
187
+ if (databases.isReady()) return combineSearch(databases.listCached({}));
188
+ return databaseReady.then(function () { return combineSearch(databases.listCached({})); });
94
189
  }
95
190
 
96
191
  function handleKnowledgeMessage(ws, msg) {
97
192
  if (msg.type === "knowledge_list") {
98
- sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
193
+ try { var listed = listKnowledgeItems(ws); if (listed && typeof listed.then === "function") listed.then(function (items) { response(ws, msg, { type: "knowledge_list", files: items, drafts: drafts.list(ws && ws._clayUser && ws._clayUser.id || "system") }); }).catch(function (error) { failure(ws, msg, error); }); else response(ws, msg, { type: "knowledge_list", files: listed, drafts: drafts.list(ws && ws._clayUser && ws._clayUser.id || "system") }); } catch (error) { failure(ws, msg, error); }
194
+ return true;
195
+ }
196
+ if (msg.type === "knowledge_search") {
197
+ try { var searched = searchFiles(msg.query, msg.offset, msg.limit); if (searched && typeof searched.then === "function") searched.then(function (result) { response(ws, msg, Object.assign({ type: "knowledge_search_results", query: String(msg.query || "") }, result)); }).catch(function (error) { failure(ws, msg, error); }); else response(ws, msg, Object.assign({ type: "knowledge_search_results", query: String(msg.query || "") }, searched)); } catch (error) { failure(ws, msg, error); }
99
198
  return true;
100
199
  }
101
-
102
200
  if (msg.type === "knowledge_read") {
103
- if (!msg.name) return true;
104
- var safeName = path.basename(msg.name);
105
- if (msg.common && msg.ownMateId && isMate) {
106
- var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
107
- try {
108
- var content = matesModule.readCommonKnowledgeFile(mateCtx, msg.ownMateId, safeName);
109
- sendTo(ws, { type: "knowledge_content", name: safeName, content: content, common: true, ownMateId: msg.ownMateId });
110
- } catch (e) {
111
- sendTo(ws, { type: "knowledge_content", name: safeName, content: "", error: "File not found", common: true });
112
- }
113
- } else {
114
- var filePath = path.join(cwd, "knowledge", safeName);
115
- try {
116
- var content = fs.readFileSync(filePath, "utf8");
117
- sendTo(ws, { type: "knowledge_content", name: safeName, content: content });
118
- } catch (e) {
119
- sendTo(ws, { type: "knowledge_content", name: safeName, content: "", error: "File not found" });
201
+ try {
202
+ if (msg.common && msg.ownMateId && isMate) {
203
+ if (!knowledgeFiles.validName(msg.name)) throw new Error("Invalid knowledge file name.");
204
+ var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
205
+ var sharedContent = matesModule.readCommonKnowledgeFile(mateCtx, msg.ownMateId, msg.name);
206
+ var sharedParsed = markdown.parse(sharedContent); var sharedIndexState = documentIndex();
207
+ var sharedIndexed = findDocument(sharedIndexState.documents, msg.name, msg.documentId); var sharedRelations = sharedIndexed ? linksFor(sharedIndexState.documents, sharedIndexed) : { outgoing: [], backlinks: [] };
208
+ response(ws, msg, { type: "knowledge_content", name: msg.name, content: sharedContent, common: true, ownMateId: msg.ownMateId, writable: false, revision: null,
209
+ outline: sharedParsed.headings, outgoing: sharedRelations.outgoing, backlinks: sharedRelations.backlinks,
210
+ properties: sharedParsed.properties, tags: sharedParsed.tags, propertyErrors: sharedParsed.propertyErrors,
211
+ indexComplete: sharedIndexState.complete, indexErrors: sharedIndexState.errors });
212
+ } else {
213
+ var item = files.read(msg.name);
214
+ var indexState = documentIndex(); var indexed = findDocument(indexState.documents, item.name, msg.documentId || item.id); var relations = indexed ? linksFor(indexState.documents, indexed) : { outgoing: [], backlinks: [] };
215
+ var parsed = indexed ? indexed.parsed : markdown.parse(item.content);
216
+ var userId = ws && ws._clayUser && ws._clayUser.id || "system"; var recovered = drafts.read(userId, item.id);
217
+ response(ws, msg, { type: "knowledge_content", id: item.id, name: item.name, content: item.content, revision: item.revision, itemType: item.itemType,
218
+ writable: item.itemType === "text" && canWrite(ws), outline: parsed.headings, outgoing: relations.outgoing, backlinks: relations.backlinks,
219
+ properties: parsed.properties, tags: parsed.tags, propertyErrors: parsed.propertyErrors, recoveredDraft: recovered,
220
+ indexComplete: indexState.complete, indexErrors: indexState.errors });
120
221
  }
121
- }
222
+ } catch (error) { failure(ws, msg, error); }
122
223
  return true;
123
224
  }
124
-
125
225
  if (msg.type === "knowledge_save") {
126
- if (!msg.name || typeof msg.content !== "string") return true;
127
- var safeName = path.basename(msg.name);
128
- if (!safeName.endsWith(".md") && !safeName.endsWith(".jsonl")) safeName += ".md";
129
- var knowledgeDir = path.join(cwd, "knowledge");
130
- fs.mkdirSync(knowledgeDir, { recursive: true });
131
- fs.writeFileSync(path.join(knowledgeDir, safeName), msg.content);
132
- mirrorMateSource(ws, safeName);
133
- sendTo(ws, { type: "knowledge_saved", name: safeName });
134
- sendTo(ws, { type: "knowledge_list", files: listKnowledgeFilesBasic() });
226
+ if (!requireWrite(ws, msg)) return true;
227
+ try {
228
+ var saved = files.save(msg.name, msg.content, msg.expectedRevision || null);
229
+ var savedSync = mirror(ws, saved.name);
230
+ if (msg.documentId) drafts.remove(ws._clayUser && ws._clayUser.id || "system", msg.documentId, msg.draftVersion);
231
+ response(ws, msg, addSyncState({ type: "knowledge_saved", id: saved.id, name: saved.name, revision: saved.revision }, savedSync));
232
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
233
+ } catch (error) { failure(ws, msg, error); }
234
+ return true;
235
+ }
236
+ if (msg.type === "knowledge_rename") {
237
+ if (!requireWrite(ws, msg)) return true;
238
+ try {
239
+ var beforeMoveState = documentIndex(); var beforeMove = beforeMoveState.documents; var moveTarget = findDocument(beforeMove, msg.from, msg.documentId); var linkChanges = moveTarget ? rewriteMoveLinks(beforeMove, moveTarget, msg.name) : [];
240
+ var renamed = files.rename(msg.from, msg.name, msg.expectedRevision || null); var linkUpdateFailures = []; var updatedLinks = 0;
241
+ var updatedDocuments = []; var mirrorNames = [msg.from, renamed.name];
242
+ for (var u = 0; u < linkChanges.length; u++) {
243
+ var change = linkChanges[u]; var writeName = change.id === renamed.id ? renamed.name : change.name; var expected = change.id === renamed.id ? renamed.revision : change.revision;
244
+ try { var rewritten = files.save(writeName, change.content, expected); updatedLinks += change.count; updatedDocuments.push(rewritten); mirrorNames.push(rewritten.name); }
245
+ catch (linkError) { linkUpdateFailures.push({ name: writeName, error: linkError.message }); }
246
+ }
247
+ renamed = files.read(renamed.name);
248
+ var uniqueMirrorNames = mirrorNames.filter(function (name, at) { return mirrorNames.indexOf(name) === at; }); var renameSync = mirrorMany(ws, uniqueMirrorNames);
249
+ response(ws, msg, addSyncState({ type: "knowledge_renamed", id: renamed.id, from: msg.from, name: renamed.name, revision: renamed.revision,
250
+ content: renamed.content, updatedLinks: updatedLinks, linkUpdateFailures: linkUpdateFailures,
251
+ updatedDocuments: updatedDocuments.map(function (item) { return { id: item.id, name: item.name, content: item.content, revision: item.revision }; }) }, renameSync));
252
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
253
+ } catch (error) { failure(ws, msg, error); }
135
254
  return true;
136
255
  }
137
-
138
256
  if (msg.type === "knowledge_delete") {
139
- if (!msg.name) return true;
140
- var safeName = path.basename(msg.name);
141
- var filePath = path.join(cwd, "knowledge", safeName);
142
- try { fs.unlinkSync(filePath); } catch (e) {}
143
- // Reconciling after the unlink appends a tombstone, so a deleted file
144
- // does not stay live in the new backend.
145
- mirrorMateSource(ws, safeName);
146
- sendTo(ws, { type: "knowledge_deleted", name: safeName });
147
- sendTo(ws, { type: "knowledge_list", files: listKnowledgeFilesBasic() });
257
+ if (!requireWrite(ws, msg)) return true;
258
+ try {
259
+ var removed = files.remove(msg.name, msg.expectedRevision || null);
260
+ var deleteSync = mirror(ws, removed.name);
261
+ response(ws, msg, addSyncState({ type: "knowledge_deleted", name: removed.name }, deleteSync));
262
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
263
+ } catch (error) { failure(ws, msg, error); }
148
264
  return true;
149
265
  }
150
-
151
- if (msg.type === "knowledge_promote") {
152
- if (!isMate || !msg.name) return true;
153
- var safeName = path.basename(msg.name);
154
- var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
155
- var thisMateId = path.basename(cwd);
156
- var mate = matesModule.getMate(mateCtx, thisMateId);
157
- var mateName = (mate && mate.name) || null;
158
- matesModule.promoteKnowledge(mateCtx, thisMateId, mateName, safeName);
159
- sendTo(ws, { type: "knowledge_promoted", name: safeName });
160
- sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
266
+ if (msg.type === "knowledge_sync") {
267
+ if (!requireWrite(ws, msg)) return true;
268
+ try {
269
+ var syncNames = Array.isArray(msg.names) ? msg.names.slice(0, 4) : [msg.name];
270
+ if (!syncNames.length) throw new Error("A Knowledge source is required.");
271
+ for (var n = 0; n < syncNames.length; n++) if (!knowledgeFiles.validName(syncNames[n])) throw new Error("Invalid knowledge file name.");
272
+ var retrySync = mirrorMany(ws, syncNames);
273
+ if (!retrySync.ok) throw Object.assign(new Error(retrySync.error), { code: "SYNC_FAILED" });
274
+ response(ws, msg, { type: "knowledge_synced", names: syncNames });
275
+ } catch (error) { failure(ws, msg, error, { diskCommitted: true, names: msg.names || [msg.name] }); }
161
276
  return true;
162
277
  }
163
-
164
- if (msg.type === "knowledge_depromote") {
165
- if (!isMate || !msg.name) return true;
166
- var safeName = path.basename(msg.name);
167
- var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
168
- var thisMateId = path.basename(cwd);
169
- matesModule.depromoteKnowledge(mateCtx, thisMateId, safeName);
170
- sendTo(ws, { type: "knowledge_depromoted", name: safeName });
171
- sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
278
+ if (msg.type === "knowledge_draft_save") {
279
+ if (!requireWrite(ws, msg)) return true;
280
+ try {
281
+ var savedDraft = drafts.save(ws._clayUser && ws._clayUser.id || "system", msg.documentId, { name: msg.name, content: msg.content, baseRevision: msg.baseRevision,
282
+ cursor: msg.cursor, clientVersion: msg.clientVersion, newDocument: msg.newDocument });
283
+ response(ws, msg, { type: "knowledge_draft_saved", documentId: msg.documentId, savedAt: savedDraft.savedAt, clientVersion: savedDraft.clientVersion });
284
+ } catch (error) { failure(ws, msg, error); }
285
+ return true;
286
+ }
287
+ if (msg.type === "knowledge_draft_delete") {
288
+ if (!requireWrite(ws, msg)) return true;
289
+ try { var removedDraft = drafts.remove(ws._clayUser && ws._clayUser.id || "system", msg.documentId, msg.throughVersion); response(ws, msg, { type: "knowledge_draft_deleted", documentId: msg.documentId, removed: removedDraft }); }
290
+ catch (error) { failure(ws, msg, error); }
291
+ return true;
292
+ }
293
+ if (msg.type === "knowledge_discard_draft") {
294
+ if (!requireWrite(ws, msg)) return true;
295
+ try {
296
+ var disk = files.read(msg.name); if (msg.expectedRevision && disk.revision !== msg.expectedRevision) throw Object.assign(new Error("The disk version changed again."), { code: "STALE", currentRevision: disk.revision });
297
+ var removed = drafts.remove(ws._clayUser && ws._clayUser.id || "system", msg.documentId, msg.throughVersion);
298
+ response(ws, msg, { type: "knowledge_draft_discarded", documentId: msg.documentId, removed: removed, id: disk.id, name: disk.name, content: disk.content, revision: disk.revision });
299
+ } catch (error) { failure(ws, msg, error); }
300
+ return true;
301
+ }
302
+ if (msg.type === "knowledge_history") {
303
+ try { response(ws, msg, { type: "knowledge_history", documentId: msg.documentId, versions: files.history(msg.documentId) }); }
304
+ catch (error) { failure(ws, msg, error); }
305
+ return true;
306
+ }
307
+ if (msg.type === "knowledge_history_read") {
308
+ try { response(ws, msg, Object.assign({ type: "knowledge_history_version", documentId: msg.documentId }, files.readHistory(msg.documentId, msg.version))); }
309
+ catch (error) { failure(ws, msg, error); }
310
+ return true;
311
+ }
312
+ if (msg.type === "knowledge_deleted_list") {
313
+ if (!databases || !databases.hasStore()) { try { response(ws, msg, { type: "knowledge_deleted_list", files: files.deleted() }); } catch (error) { failure(ws, msg, error); } return true; }
314
+ databases.list({ archived: true }).then(function (result) { var archivedDatabases = result.databases.map(function (item) { return Object.assign({}, item, { itemType: "db", writable: canWrite(ws) }); }); response(ws, msg, { type: "knowledge_deleted_list", files: files.deleted().concat(archivedDatabases) }); }).catch(function (error) { failure(ws, msg, error); });
315
+ return true;
316
+ }
317
+ if (msg.type === "knowledge_restore") {
318
+ if (!requireWrite(ws, msg)) return true;
319
+ try {
320
+ var restored = files.restore(msg.documentId, msg.version, msg.name, msg.expectedRevision || null); var restoreSync = mirror(ws, restored.name);
321
+ response(ws, msg, addSyncState({ type: "knowledge_restored", id: restored.id, name: restored.name, revision: restored.revision }, restoreSync));
322
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
323
+ } catch (error) { failure(ws, msg, error); }
324
+ return true;
325
+ }
326
+ if (msg.type === "knowledge_promote" || msg.type === "knowledge_depromote") {
327
+ if (!isMate || !requireWrite(ws, msg)) return true;
328
+ try {
329
+ if (!knowledgeFiles.validName(msg.name)) throw new Error("Invalid knowledge file name.");
330
+ var promoteCtx = matesModule.buildMateCtx(getProjectOwnerId());
331
+ var mateId = path.basename(cwd);
332
+ if (msg.type === "knowledge_promote") {
333
+ var mate = matesModule.getMate(promoteCtx, mateId);
334
+ matesModule.promoteKnowledge(promoteCtx, mateId, mate && mate.name, msg.name);
335
+ } else matesModule.depromoteKnowledge(promoteCtx, mateId, msg.name);
336
+ response(ws, msg, { type: msg.type === "knowledge_promote" ? "knowledge_promoted" : "knowledge_depromoted", name: msg.name });
337
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
338
+ } catch (error) { failure(ws, msg, error); }
339
+ return true;
340
+ }
341
+ var dbOperations = {
342
+ knowledge_db_create: { method: "create", reply: "knowledge_db_created" }, knowledge_db_read: { method: "read", reply: "knowledge_db_content", read: true },
343
+ knowledge_db_rename: { method: "rename", reply: "knowledge_db_renamed" }, knowledge_db_archive: { method: "archive", reply: "knowledge_db_archived" },
344
+ knowledge_db_recover: { method: "recover", reply: "knowledge_db_recovered" }, knowledge_db_schema_save: { method: "updateSchema", reply: "knowledge_db_schema_saved" },
345
+ knowledge_db_view_save: { method: "saveView", reply: "knowledge_db_view_saved" }, knowledge_db_record_create: { method: "createRecord", reply: "knowledge_db_record_created" },
346
+ knowledge_db_record_read: { method: "readRecord", reply: "knowledge_db_record_content", read: true }, knowledge_db_record_update: { method: "updateRecord", reply: "knowledge_db_record_updated" },
347
+ knowledge_db_record_archive: { method: "archiveRecord", reply: "knowledge_db_record_archived" }, knowledge_db_record_recover: { method: "recoverRecord", reply: "knowledge_db_record_recovered" },
348
+ knowledge_db_query: { method: "query", reply: "knowledge_db_query_results", read: true }, knowledge_db_sync: { method: "retryProjection", reply: "knowledge_db_synced" },
349
+ };
350
+ if (dbOperations[msg.type]) {
351
+ if (!isMate) { failure(ws, msg, new Error("Knowledge databases are available only inside a Mate project.")); return true; }
352
+ var operation = dbOperations[msg.type]; if (!operation.read && !requireWrite(ws, msg)) return true;
353
+ var args = Object.assign({}, msg, { actor: actor(ws) }); delete args.type; delete args.requestId;
354
+ Promise.resolve(databases[operation.method](args)).then(function (result) { response(ws, msg, Object.assign({ type: operation.reply }, result)); }).catch(function (error) { failure(ws, msg, error, { databaseId: msg.databaseId || null, recordId: msg.recordId || null }); });
172
355
  return true;
173
356
  }
174
-
175
357
  return false;
176
358
  }
177
359
 
178
- return {
179
- handleKnowledgeMessage: handleKnowledgeMessage,
180
- listKnowledgeFiles: listKnowledgeFiles,
181
- };
360
+ return { handleKnowledgeMessage: handleKnowledgeMessage, listKnowledgeFiles: listKnowledgeFiles, databases: databases };
182
361
  }
183
362
 
184
363
  module.exports = { attachKnowledge: attachKnowledge };
@@ -98,6 +98,7 @@ function attachMateCreationProposal(ctx) {
98
98
  return ctx.createReadyMate(ws, session.ownerId || null, Object.assign({}, entry.definition, { vendor: vendor, model: selected.value }));
99
99
  }).then(function (mate) {
100
100
  session.homeMateCreationPhase = "created";
101
+ if (typeof ctx.persistSession === "function") ctx.persistSession(session);
101
102
  record(session, { type: "mate_creation_proposal_resolved", proposalId: msg.proposalId, action: "create", mateId: mate.id, mateName: (mate.profile && mate.profile.displayName) || mate.name || "Mate" });
102
103
  entry.resolve({ action: "create", mateId: mate.id });
103
104
  }).catch(function (error) {
@@ -87,6 +87,7 @@ function formatAskUserAnswerAsMessage(input, answers) {
87
87
  * getLatestVersion, setLatestVersion
88
88
  */
89
89
  function attachSessions(ctx) {
90
+ var mateDefaultsToken = {};
90
91
  var contextUsage = contextUsageModule.attachContextUsage(ctx);
91
92
  var cwd = ctx.cwd;
92
93
  var slug = ctx.slug;
@@ -635,6 +636,32 @@ function attachSessions(ctx) {
635
636
  ws._scheduledInterviewSessionRequests[scheduledRequestId] = pendingScheduled;
636
637
  return true;
637
638
  }
639
+ if (msg.mateDefaults === true && msg._mateDefaultsToken !== mateDefaultsToken) {
640
+ var mateRequestId = typeof msg.requestId === "string" ? msg.requestId.substring(0, 200) : "";
641
+ if (!isMate || !mateRequestId || typeof ctx.resolveMateSessionDefaults !== "function") {
642
+ scheduledRequestError(ws, msg, "Mate defaults are unavailable.");
643
+ return true;
644
+ }
645
+ if (!ws._pendingMateSessionRequests) ws._pendingMateSessionRequests = {};
646
+ if (ws._pendingMateSessionRequests[mateRequestId]) return true;
647
+ var pendingMateSession = Promise.resolve(ctx.resolveMateSessionDefaults(ws)).then(function (resolved) {
648
+ if (!resolved || !resolved.vendor || !resolved.model) throw new Error("The Mate vendor and model are not configured.");
649
+ var resolvedMessage = Object.assign({}, msg, {
650
+ _mateDefaultsToken: mateDefaultsToken,
651
+ vendor: resolved.vendor,
652
+ model: resolved.model,
653
+ effort: resolved.effort || "",
654
+ mode: "gui",
655
+ });
656
+ handleSessionsMessage(ws, resolvedMessage);
657
+ }).catch(function (error) {
658
+ scheduledRequestError(ws, msg, error.message || String(error));
659
+ }).finally(function () {
660
+ if (ws._pendingMateSessionRequests[mateRequestId] === pendingMateSession) delete ws._pendingMateSessionRequests[mateRequestId];
661
+ });
662
+ ws._pendingMateSessionRequests[mateRequestId] = pendingMateSession;
663
+ return true;
664
+ }
638
665
  var creationRequestId = typeof msg.requestId === "string" ? msg.requestId.substring(0, 200) : "";
639
666
  var creationFailed = function (text) {
640
667
  if (creationRequestId) sendTo(ws, { type: "new_session_result", requestId: creationRequestId, ok: false, error: text });
@@ -672,6 +699,7 @@ function attachSessions(ctx) {
672
699
  }
673
700
  sessionOpts.sessionVisibility = visibilityChoice.visibility;
674
701
  if (msg.vendor) sessionOpts.vendor = msg.vendor;
702
+ if (isMate && msg._mateDefaultsToken === mateDefaultsToken) sessionOpts.model = msg.model;
675
703
  // Mode resolution: vendors without a TUI session mode are always GUI.
676
704
  // TUI-capable sessions honor the explicit msg.mode if provided, otherwise
677
705
  // fall back to the user's claudeOpenMode preference. This is what
@@ -741,7 +769,10 @@ function attachSessions(ctx) {
741
769
  sm.switchSession(newSess.localId, ws);
742
770
  } else {
743
771
  var newSessionVendor = sessionOpts.vendor || sm.defaultVendor || "claude";
744
- sessionOpts.effort = yoke.clampEffort(newSessionVendor, (sm.currentEffortByVendor && sm.currentEffortByVendor[newSessionVendor]) || sm.currentEffort || "medium") || null;
772
+ var newSessionEffort = isMate && msg._mateDefaultsToken === mateDefaultsToken
773
+ ? (msg.effort || "")
774
+ : ((sm.currentEffortByVendor && sm.currentEffortByVendor[newSessionVendor]) || sm.currentEffort || "medium");
775
+ sessionOpts.effort = yoke.clampEffort(newSessionVendor, newSessionEffort) || null;
745
776
  // Reuse an existing blank GUI session instead of stacking another
746
777
  // one. A vendor-less blank is acceptable: nothing has happened in it,
747
778
  // so stamping the requested vendor is equivalent to a fresh create.
@@ -773,7 +804,7 @@ function attachSessions(ctx) {
773
804
  // new_session without a vendor (mate sidebar, notification banner,
774
805
  // debate) falls through to the default adapter and must not clobber
775
806
  // the user's remembered pick.
776
- if (msg.vendor && sm.lastVendor !== msg.vendor) {
807
+ if (msg.vendor && msg._mateDefaultsToken !== mateDefaultsToken && sm.lastVendor !== msg.vendor) {
777
808
  sm.lastVendor = msg.vendor;
778
809
  if (typeof opts.onSetProjectLastVendor === "function") {
779
810
  opts.onSetProjectLastVendor(slug, msg.vendor);
@@ -810,6 +841,10 @@ function attachSessions(ctx) {
810
841
  }
811
842
 
812
843
  if (msg.type === "transfer_project_owner") {
844
+ if (isMate) {
845
+ sendTo(ws, { type: "error", text: "Clay and Mate ownership cannot be transferred." });
846
+ return true;
847
+ }
813
848
  // Home directory projects: ownership is permanently locked
814
849
  if (osUsers && osUsers.length > 0 && /^\/home\/[^/]+\//.test(cwd)) {
815
850
  sendTo(ws, { type: "error", text: "Cannot transfer ownership of home directory projects." });
@@ -1603,6 +1638,7 @@ function attachSessions(ctx) {
1603
1638
  var forked = sm.createSession({
1604
1639
  vendor: session.vendor,
1605
1640
  ownerId: session.ownerId || null,
1641
+ userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
1606
1642
  sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
1607
1643
  }, ws);
1608
1644
  forked.cliSessionId = result.sessionId;
@@ -1627,6 +1663,7 @@ function attachSessions(ctx) {
1627
1663
  title: forkTitle,
1628
1664
  vendor: session.vendor,
1629
1665
  ownerId: session.ownerId || null,
1666
+ userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
1630
1667
  sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
1631
1668
  sessionVisibilityExplicit: session.sessionVisibilityExplicit === true,
1632
1669
  }, ws);