clay-server 5.0.2-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.
- package/LICENSES/README.md +4 -0
- package/LICENSES/dicebear-avatars.md +16 -0
- package/lib/daemon-project-access.js +7 -1
- package/lib/daemon.js +2 -0
- package/lib/dicebear-avatar.js +62 -0
- package/lib/knowledge-db-service.js +175 -0
- package/lib/knowledge-db-validation.js +0 -0
- package/lib/knowledge-document-index.js +49 -0
- package/lib/knowledge-draft-store.js +56 -0
- package/lib/knowledge-file-store.js +199 -0
- package/lib/knowledge-markdown.js +128 -0
- package/lib/mate-knowledge-mcp-server.js +29 -6
- package/lib/mate-knowledge-service.js +23 -2
- package/lib/mate-knowledge-sync.js +36 -21
- package/lib/mate-workspace-color.js +4 -0
- package/lib/mates.js +18 -0
- package/lib/project-connection.js +36 -2
- package/lib/project-debate-proposal.js +6 -0
- package/lib/project-debate-workspace.js +43 -0
- package/lib/project-debate.js +1 -0
- package/lib/project-file-path.js +2 -3
- package/lib/project-knowledge.js +315 -136
- package/lib/project-mate-creation-proposal.js +1 -0
- package/lib/project-request-access.js +13 -1
- package/lib/project-sessions.js +39 -2
- package/lib/project-user-knowledge.js +60 -0
- package/lib/project.js +59 -18
- package/lib/public/app.js +73 -34
- package/lib/public/clay-studio-wordmark-bold.svg +20 -0
- package/lib/public/css/clay-workspace.css +105 -0
- package/lib/public/css/debates-workbench.css +47 -0
- package/lib/public/css/filebrowser.css +10 -0
- package/lib/public/css/home-debate-live.css +26 -0
- package/lib/public/css/home-hub.css +3 -1
- package/lib/public/css/home-mate-model-picker.css +15 -131
- package/lib/public/css/home-mate-settings.css +168 -16
- package/lib/public/css/icon-strip.css +249 -32
- package/lib/public/css/mate-conversation.css +176 -0
- package/lib/public/css/mate-knowledge-workbench.css +118 -0
- package/lib/public/css/mate-workspace-color.css +16 -0
- package/lib/public/css/mates.css +28 -28
- package/lib/public/css/messages.css +1 -114
- package/lib/public/css/mobile-nav.css +16 -16
- package/lib/public/css/overlays.css +8 -0
- package/lib/public/css/project-logs.css +7 -0
- package/lib/public/css/project-mate-creation.css +105 -0
- package/lib/public/css/right-workbench.css +3 -3
- package/lib/public/css/session-folders.css +80 -21
- package/lib/public/css/sidebar.css +183 -107
- package/lib/public/css/title-bar.css +151 -0
- package/lib/public/css/worker-delegation.css +47 -0
- package/lib/public/css/worker-proposal.css +72 -150
- package/lib/public/css/you.css +74 -0
- package/lib/public/index.html +33 -101
- package/lib/public/modules/account-island.js +230 -0
- package/lib/public/modules/app-cursors.js +19 -43
- package/lib/public/modules/app-debate-ui.js +4 -3
- package/lib/public/modules/app-header.js +106 -0
- package/lib/public/modules/app-message-router.js +12 -3
- package/lib/public/modules/app-messages.js +34 -9
- package/lib/public/modules/app-notifications.js +4 -7
- package/lib/public/modules/app-projects.js +5 -38
- package/lib/public/modules/app-rendering.js +12 -13
- package/lib/public/modules/avatar.js +25 -9
- package/lib/public/modules/chat-render-runtime.js +16 -4
- package/lib/public/modules/command-palette.js +2 -15
- package/lib/public/modules/cursor-sharing-preference.js +3 -3
- package/lib/public/modules/debate-controls.js +299 -0
- package/lib/public/modules/debate.js +2 -0
- package/lib/public/modules/debates-workbench.js +339 -0
- package/lib/public/modules/default-ai.js +81 -65
- package/lib/public/modules/filebrowser.js +28 -8
- package/lib/public/modules/home-debate-controls.js +18 -289
- package/lib/public/modules/home-mate-avatar-editor.js +228 -0
- package/lib/public/modules/home-mate-color-picker.js +66 -0
- package/lib/public/modules/home-mate-creation.js +1 -1
- package/lib/public/modules/home-mate-instructions.js +90 -0
- package/lib/public/modules/home-mate-model-picker.js +80 -271
- package/lib/public/modules/home-mate-profile-editor.js +72 -0
- package/lib/public/modules/home-mate-settings.js +118 -125
- package/lib/public/modules/home-shell.js +3 -0
- package/lib/public/modules/home-surface-boot.js +1 -9
- package/lib/public/modules/html-preview.js +19 -0
- package/lib/public/modules/icon-strip-tooltip.js +95 -0
- package/lib/public/modules/knowledge-db-edit-state.js +35 -0
- package/lib/public/modules/knowledge-db-workbench.js +234 -0
- package/lib/public/modules/knowledge-history-state.js +55 -0
- package/lib/public/modules/knowledge-markdown-editor.js +57 -0
- package/lib/public/modules/knowledge-preview-links.js +70 -0
- package/lib/public/modules/knowledge-workbench-shell.js +21 -0
- package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
- package/lib/public/modules/mate-knowledge-workbench.js +500 -0
- package/lib/public/modules/mate-knowledge.js +11 -3
- package/lib/public/modules/mate-management.js +54 -10
- package/lib/public/modules/mate-sidebar.js +11 -1
- package/lib/public/modules/mate-workspace-color.js +33 -0
- package/lib/public/modules/mention.js +5 -13
- package/lib/public/modules/profile.js +51 -18
- package/lib/public/modules/project-chat-identity.js +79 -0
- package/lib/public/modules/project-debate-planning-workspace.js +250 -0
- package/lib/public/modules/project-debate-workspace.js +185 -0
- package/lib/public/modules/project-header-identity.js +127 -0
- package/lib/public/modules/project-logs-render.js +9 -4
- package/lib/public/modules/project-mate-creation-workspace.js +414 -0
- package/lib/public/modules/project-mate-navigation.js +238 -0
- package/lib/public/modules/project-session-navigation.js +29 -0
- package/lib/public/modules/project-switcher.js +6 -8
- package/lib/public/modules/projects-hub-view.js +5 -6
- package/lib/public/modules/projects-hub.js +2 -6
- package/lib/public/modules/qrcode.js +27 -8
- package/lib/public/modules/search-clay-chat.js +8 -11
- package/lib/public/modules/session-activity-tooltip.js +12 -0
- package/lib/public/modules/session-create-form.js +86 -14
- package/lib/public/modules/session-create.js +50 -7
- package/lib/public/modules/session-list-visibility.js +41 -0
- package/lib/public/modules/session-presence-ui.js +4 -1
- package/lib/public/modules/session-title-marquee.js +51 -0
- package/lib/public/modules/sidebar-mates.js +3 -50
- package/lib/public/modules/sidebar-mobile.js +63 -60
- package/lib/public/modules/sidebar-projects.js +84 -97
- package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
- package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
- package/lib/public/modules/sidebar-sessions.js +49 -50
- package/lib/public/modules/sidebar.js +11 -13
- package/lib/public/modules/thinking-lifecycle.js +7 -6
- package/lib/public/modules/tool-palette-order.js +4 -3
- package/lib/public/modules/tool-palette.js +13 -3
- package/lib/public/modules/tools.js +22 -22
- package/lib/public/modules/worker-proposal.js +42 -8
- package/lib/public/modules/worker-runtime-picker.js +115 -0
- package/lib/public/modules/you-activity.js +121 -0
- package/lib/public/modules/you.js +251 -0
- package/lib/public/style.css +16 -8
- package/lib/public/vendor/codemirror/LICENSE +21 -0
- package/lib/public/vendor/codemirror/codemirror.css +344 -0
- package/lib/public/vendor/codemirror/codemirror.js +9884 -0
- package/lib/public/vendor/codemirror/continuelist.js +101 -0
- package/lib/public/vendor/codemirror/markdown.js +886 -0
- package/lib/public/vendor/codemirror/show-hint.css +37 -0
- package/lib/public/vendor/codemirror/show-hint.js +523 -0
- package/lib/sdk-bridge.js +15 -3
- package/lib/server-admin.js +11 -0
- package/lib/server-home-chat-events.js +1 -1
- package/lib/server-home-chat.js +25 -3
- package/lib/server-home-debate-planning.js +4 -1
- package/lib/server-home-debates.js +2 -0
- package/lib/server-home-mate-creation.js +4 -1
- package/lib/server-mate-instructions.js +44 -0
- package/lib/server-mates.js +12 -3
- package/lib/server-palette.js +1 -1
- package/lib/server-settings.js +65 -1
- package/lib/server-user-knowledge.js +119 -0
- package/lib/server.js +21 -1
- package/lib/session-visibility.js +7 -2
- package/lib/sessions.js +32 -6
- package/lib/user-knowledge-service.js +167 -0
- package/lib/users-permissions.js +7 -0
- package/lib/users-project-mate-filter-preferences.js +39 -0
- package/lib/users.js +6 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +50 -0
- package/package.json +10 -1
package/lib/project-knowledge.js
CHANGED
|
@@ -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
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
174
|
+
return changes;
|
|
77
175
|
}
|
|
78
176
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
var
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
var
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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 (!
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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) {
|
|
@@ -60,10 +60,22 @@ function attachRequestAccess(ctx) {
|
|
|
60
60
|
return info;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function isSoleAccount(ws) {
|
|
64
|
+
if (!users || typeof users.getAllUsers !== "function") return false;
|
|
65
|
+
try {
|
|
66
|
+
var accounts = users.getAllUsers();
|
|
67
|
+
var user = userFor(ws);
|
|
68
|
+
return !!(user && user.id && Array.isArray(accounts) && accounts.length === 1 &&
|
|
69
|
+
accounts[0] && accounts[0].id === user.id);
|
|
70
|
+
} catch (e) { return false; }
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
function fileScope(ws) {
|
|
64
74
|
if (!canUseFiles(ws)) throw fileFailure("FILE_FORBIDDEN", "File browser access is not permitted");
|
|
65
75
|
var identity = osIdentity(ws);
|
|
66
|
-
|
|
76
|
+
// Recheck the account roster so adding another user restores the boundary
|
|
77
|
+
// for shared installations without OS isolation, including active watches.
|
|
78
|
+
return { identity: identity, projectBound: !identity && isMultiUser() && !isSoleAccount(ws) };
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
return { permitMessage: permitMessage, canAccessProject: canAccessProject, hasPermission: hasPermission,
|