clay-server 5.2.0-beta.2 → 5.2.0-beta.4
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/lib/builtin-mates.js +2 -2
- package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
- package/lib/dm-digest-cursor.js +120 -0
- package/lib/knowledge-db-import-validation.js +46 -0
- package/lib/knowledge-db-import.js +98 -0
- package/lib/knowledge-db-service.js +18 -30
- package/lib/knowledge-db-storage-nedb-maintenance.js +99 -0
- package/lib/knowledge-db-storage-nedb.js +66 -0
- package/lib/knowledge-db-storage.js +72 -0
- package/lib/knowledge-db-validation.js +0 -0
- package/lib/knowledge-file-store.js +7 -1
- package/lib/mate-avatar-catalog.js +12 -0
- package/lib/mate-avatar-migration.js +41 -0
- package/lib/mate-knowledge-mate-bound.js +53 -0
- package/lib/mate-knowledge-mcp-server.js +55 -8
- package/lib/mate-knowledge-service.js +9 -30
- package/lib/mate-knowledge-text-tools.js +106 -0
- package/lib/mates.js +14 -3
- package/lib/mcp-agent-tools.js +117 -0
- package/lib/mcp-connection-store.js +119 -15
- package/lib/mcp-import-credentials.js +31 -0
- package/lib/mcp-local-transport.js +13 -0
- package/lib/mcp-oauth.js +8 -6
- package/lib/mcp-query-bridge.js +30 -0
- package/lib/mcp-remote-connections.js +74 -20
- package/lib/mcp-source-adapters.js +91 -0
- package/lib/mcp-source-context.js +42 -0
- package/lib/mcp-source-discovery.js +169 -0
- package/lib/mcp-source-plugins.js +68 -0
- package/lib/mcp-source-restrictions.js +47 -0
- package/lib/mcp-tool-policy.js +34 -0
- package/lib/project-ask-user.js +6 -4
- package/lib/project-debate.js +1 -0
- package/lib/project-mate-interaction.js +40 -62
- package/lib/project-mcp.js +54 -16
- package/lib/project-session-document.js +8 -3
- package/lib/project-session-github.js +16 -7
- package/lib/project-session-linear.js +14 -6
- package/lib/project-session-notes.js +7 -3
- package/lib/project-sessions.js +6 -2
- package/lib/project-shared-browser.js +56 -12
- package/lib/project.js +49 -34
- package/lib/public/app.js +5 -0
- package/lib/public/appearance-bootstrap.js +14 -0
- package/lib/public/css/clay-workspace.css +29 -0
- package/lib/public/css/home-debate-planning.css +2 -0
- package/lib/public/css/mate-avatar-gallery.css +12 -0
- package/lib/public/css/mate-conversation.css +10 -5
- package/lib/public/css/session-github.css +43 -1
- package/lib/public/css/shared-browser.css +35 -1
- package/lib/public/index.html +10 -4
- package/lib/public/mate-avatars/B01.png +0 -0
- package/lib/public/mate-avatars/B02.png +0 -0
- package/lib/public/mate-avatars/B03.png +0 -0
- package/lib/public/mate-avatars/B04.png +0 -0
- package/lib/public/mate-avatars/B05.png +0 -0
- package/lib/public/mate-avatars/B06.png +0 -0
- package/lib/public/mate-avatars/B07.png +0 -0
- package/lib/public/mate-avatars/B08.png +0 -0
- package/lib/public/mate-avatars/B09.png +0 -0
- package/lib/public/mate-avatars/B10.png +0 -0
- package/lib/public/mate-avatars/B11.png +0 -0
- package/lib/public/mate-avatars/B12.png +0 -0
- package/lib/public/mate-avatars/B13.png +0 -0
- package/lib/public/mate-avatars/B14.png +0 -0
- package/lib/public/mate-avatars/B15.png +0 -0
- package/lib/public/mate-avatars/B16.png +0 -0
- package/lib/public/mate-avatars/B17.png +0 -0
- package/lib/public/mate-avatars/B18.png +0 -0
- package/lib/public/mate-avatars/B19.png +0 -0
- package/lib/public/mate-avatars/B20.png +0 -0
- package/lib/public/mate-avatars/B21.png +0 -0
- package/lib/public/mate-avatars/B22.png +0 -0
- package/lib/public/mate-avatars/B23.png +0 -0
- package/lib/public/mate-avatars/B24.png +0 -0
- package/lib/public/mate-avatars/B25.png +0 -0
- package/lib/public/mate-avatars/B26.png +0 -0
- package/lib/public/mate-avatars/B27.png +0 -0
- package/lib/public/mate-avatars/B28.png +0 -0
- package/lib/public/mate-avatars/B29.png +0 -0
- package/lib/public/mate-avatars/B30.png +0 -0
- package/lib/public/mate-avatars/B31.png +0 -0
- package/lib/public/mate-avatars/B32.png +0 -0
- package/lib/public/mate-avatars/B33.png +0 -0
- package/lib/public/mate-avatars/B34.png +0 -0
- package/lib/public/mate-avatars/B35.png +0 -0
- package/lib/public/mate-avatars/B36.png +0 -0
- package/lib/public/mate-avatars/B37.png +0 -0
- package/lib/public/mate-avatars/B38.png +0 -0
- package/lib/public/mate-avatars/B39.png +0 -0
- package/lib/public/mate-avatars/B40.png +0 -0
- package/lib/public/mate-avatars/B41.png +0 -0
- package/lib/public/mate-avatars/B42.png +0 -0
- package/lib/public/mate-avatars/B43.png +0 -0
- package/lib/public/mate-avatars/B44.png +0 -0
- package/lib/public/mate-avatars/B45.png +0 -0
- package/lib/public/mate-avatars/B46.png +0 -0
- package/lib/public/mate-avatars/B47.png +0 -0
- package/lib/public/mate-avatars/B48.png +0 -0
- package/lib/public/mate-avatars/C01.png +0 -0
- package/lib/public/mate-avatars/C02.png +0 -0
- package/lib/public/mate-avatars/C03.png +0 -0
- package/lib/public/mate-avatars/C04.png +0 -0
- package/lib/public/mate-avatars/C05.png +0 -0
- package/lib/public/mate-avatars/C06.png +0 -0
- package/lib/public/mate-avatars/C07.png +0 -0
- package/lib/public/mate-avatars/C08.png +0 -0
- package/lib/public/mate-avatars/C09.png +0 -0
- package/lib/public/mate-avatars/C10.png +0 -0
- package/lib/public/mate-avatars/C11.png +0 -0
- package/lib/public/mate-avatars/C12.png +0 -0
- package/lib/public/mate-avatars/C13.png +0 -0
- package/lib/public/mate-avatars/C14.png +0 -0
- package/lib/public/mate-avatars/C15.png +0 -0
- package/lib/public/mate-avatars/C16.png +0 -0
- package/lib/public/mate-avatars/C17.png +0 -0
- package/lib/public/mate-avatars/C18.png +0 -0
- package/lib/public/mate-avatars/C19.png +0 -0
- package/lib/public/mate-avatars/C20.png +0 -0
- package/lib/public/mate-avatars/C21.png +0 -0
- package/lib/public/mate-avatars/C22.png +0 -0
- package/lib/public/mate-avatars/C23.png +0 -0
- package/lib/public/mate-avatars/C24.png +0 -0
- package/lib/public/mate-avatars/C25.png +0 -0
- package/lib/public/mate-avatars/C26.png +0 -0
- package/lib/public/mate-avatars/C27.png +0 -0
- package/lib/public/mate-avatars/C28.png +0 -0
- package/lib/public/mate-avatars/C29.png +0 -0
- package/lib/public/mate-avatars/C30.png +0 -0
- package/lib/public/mate-avatars/C31.png +0 -0
- package/lib/public/mate-avatars/C32.png +0 -0
- package/lib/public/mate-avatars/C33.png +0 -0
- package/lib/public/mate-avatars/C34.png +0 -0
- package/lib/public/mate-avatars/C35.png +0 -0
- package/lib/public/mate-avatars/C36.png +0 -0
- package/lib/public/mate-avatars/C37.png +0 -0
- package/lib/public/mate-avatars/C38.png +0 -0
- package/lib/public/mate-avatars/C39.png +0 -0
- package/lib/public/mate-avatars/C40.png +0 -0
- package/lib/public/mate-avatars/C41.png +0 -0
- package/lib/public/mate-avatars/C42.png +0 -0
- package/lib/public/mate-avatars/C43.png +0 -0
- package/lib/public/mate-avatars/C44.png +0 -0
- package/lib/public/mate-avatars/C45.png +0 -0
- package/lib/public/mate-avatars/C46.png +0 -0
- package/lib/public/mate-avatars/C47.png +0 -0
- package/lib/public/mate-avatars/C48.png +0 -0
- package/lib/public/mate-avatars/catalog.json +482 -0
- package/lib/public/mate-avatars/lead-clay.png +0 -0
- package/lib/public/modules/app-message-router.js +4 -0
- package/lib/public/modules/app-messages.js +13 -2
- package/lib/public/modules/app-rendering.js +1 -1
- package/lib/public/modules/avatar.js +15 -41
- package/lib/public/modules/chat-render-runtime.js +10 -1
- package/lib/public/modules/clay-browser-links.js +47 -0
- package/lib/public/modules/clay-log-links.js +1 -1
- package/lib/public/modules/command-palette.js +1 -1
- package/lib/public/modules/debate.js +1 -0
- package/lib/public/modules/history-replay-batch.js +5 -1
- package/lib/public/modules/home-debate-live.js +2 -0
- package/lib/public/modules/home-debate-planning.js +7 -7
- package/lib/public/modules/home-mate-avatar-editor.js +127 -208
- package/lib/public/modules/home-mate-chat.js +6 -6
- package/lib/public/modules/home-mate-settings.js +19 -12
- package/lib/public/modules/markdown.js +4 -0
- package/lib/public/modules/mate-avatar-catalog.js +97 -0
- package/lib/public/modules/mate-management.js +40 -7
- package/lib/public/modules/mcp-connection-view.js +31 -13
- package/lib/public/modules/mcp-discovery-view.js +31 -0
- package/lib/public/modules/mcp-ui.js +23 -4
- package/lib/public/modules/mention.js +3 -0
- package/lib/public/modules/pane-browser-bridge.js +20 -0
- package/lib/public/modules/profile.js +1 -238
- package/lib/public/modules/project-mate-navigation.js +99 -14
- package/lib/public/modules/search-clay-chat.js +16 -2
- package/lib/public/modules/session-github.js +23 -9
- package/lib/public/modules/session-work-primary.js +51 -0
- package/lib/public/modules/shared-browser-settings.js +112 -0
- package/lib/public/modules/shared-browser-tabs.js +13 -3
- package/lib/public/modules/shared-browser.js +27 -6
- package/lib/public/modules/split-view.js +2 -0
- package/lib/public/modules/theme.js +8 -1
- package/lib/public/modules/tools.js +2 -1
- package/lib/public/style.css +1 -0
- package/lib/public/sw.js +3 -1
- package/lib/sdk-bridge.js +9 -3
- package/lib/server-appearance-settings.js +13 -1
- package/lib/server-dm.js +1 -0
- package/lib/server-home-chat-events.js +14 -4
- package/lib/server-home-chat.js +10 -2
- package/lib/server-home-mcp-consent.js +14 -0
- package/lib/server-mates.js +6 -1
- package/lib/server-settings.js +4 -1
- package/lib/server.js +45 -12
- package/lib/session-work-primary.js +50 -0
- package/lib/sessions.js +3 -0
- package/lib/shared-browser-cookie-sync.js +32 -0
- package/lib/shared-browser-cookies.js +79 -0
- package/lib/shared-browser-runtime.js +25 -9
- package/lib/shared-browser-storage-sync.js +43 -0
- package/lib/shared-browser-storage.js +101 -0
- package/lib/shared-browser-tools.js +2 -1
- package/lib/shared-browser-worker.js +46 -10
- package/lib/yoke/acp-agent-profiles.js +2 -2
- package/package.json +5 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Version 1 persists opaque JSON aggregate snapshots with a stable _id and revision.
|
|
2
|
+
// _id is the existing Knowledge identity, not a query expression. Engines may map it
|
|
3
|
+
// to their own primary key; aggregate format/limits remain application responsibilities.
|
|
4
|
+
// Each adapter is bound to one server-authorized Mate-project store; get/list cannot
|
|
5
|
+
// broaden that scope. Facades for the same store must share locks and committed cache.
|
|
6
|
+
//
|
|
7
|
+
// initialize() -> Promise<void>; hasStore()/isReady() -> boolean.
|
|
8
|
+
// get(id) -> Promise<snapshot|null>; list() -> Promise<snapshot[]>;
|
|
9
|
+
// getCached() -> snapshot[] (committed, detached, no initialization side effect).
|
|
10
|
+
// insert(snapshot) -> Promise<void> (reject ALREADY_EXISTS; never overwrite).
|
|
11
|
+
// replaceIfRevision(id, revision, snapshot) -> Promise<boolean>: atomic compare and
|
|
12
|
+
// full replacement, false on missing/mismatch without writing. Same-revision writes
|
|
13
|
+
// are allowed for receipts/projection status. Identity must remain unchanged.
|
|
14
|
+
// serialize(scope, work) -> Promise<result>: same-scope callbacks exclude each other
|
|
15
|
+
// across all facades of one physical store. Callback receives no engine objects and
|
|
16
|
+
// may await adapter operations; this is not a multi-aggregate transaction or I/O lock.
|
|
17
|
+
// Engines separately serialize reads/writes/maintenance/recovery as needed. A failed
|
|
18
|
+
// write may have committed durably; restore readable committed state before releasing
|
|
19
|
+
// the I/O gate, or invalidate it. Never turn committed success into maintenance failure.
|
|
20
|
+
// Domain operation receipts resolve ambiguous retries; the adapter does not own them.
|
|
21
|
+
// diagnostics() -> Promise<JSON> is optional, engine-specific and never a native handle.
|
|
22
|
+
|
|
23
|
+
var METHODS = ["initialize", "hasStore", "isReady", "get", "list", "getCached", "insert", "replaceIfRevision", "serialize"];
|
|
24
|
+
function invalid(message) { return Object.assign(new Error("Invalid Knowledge storage adapter: " + message), { code: "INVALID_STORAGE_ADAPTER" }); }
|
|
25
|
+
function validateAdapter(adapter) {
|
|
26
|
+
if (!adapter || typeof adapter !== "object" || adapter.contractVersion !== 1) throw invalid("contractVersion must be 1.");
|
|
27
|
+
METHODS.forEach(function (name) { if (typeof adapter[name] !== "function") throw invalid(name + " must be a function."); });
|
|
28
|
+
if (adapter.diagnostics !== undefined && typeof adapter.diagnostics !== "function") throw invalid("diagnostics must be a function when supplied.");
|
|
29
|
+
return adapter;
|
|
30
|
+
}
|
|
31
|
+
function bindAdapter(adapter) {
|
|
32
|
+
function snapshot(value) {
|
|
33
|
+
if (!value || Object.getPrototypeOf(value) !== Object.prototype || typeof value._id !== "string" || !Number.isInteger(value.revision)) throw invalid("snapshot must be a plain aggregate with _id and integer revision.");
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function snapshots(values) { if (!Array.isArray(values)) throw invalid("list/getCached must return an array."); values.forEach(snapshot); return values; }
|
|
37
|
+
function call(name, args, check) {
|
|
38
|
+
var result;
|
|
39
|
+
try { result = adapter[name].apply(adapter, args); } catch (cause) { return Promise.reject(cause); }
|
|
40
|
+
if (!result || typeof result.then !== "function") return Promise.reject(invalid(name + " must return a Promise."));
|
|
41
|
+
return result.then(check);
|
|
42
|
+
}
|
|
43
|
+
function flag(name) { var value = adapter[name](); if (typeof value !== "boolean") throw invalid(name + " must return a boolean."); return value; }
|
|
44
|
+
var bound = {
|
|
45
|
+
contractVersion: 1,
|
|
46
|
+
initialize: function () { return call("initialize", [], function () {}); },
|
|
47
|
+
hasStore: function () { return flag("hasStore"); }, isReady: function () { return flag("isReady"); },
|
|
48
|
+
get: function (id) { return call("get", [id], function (value) { if (value === null) return null; snapshot(value); if (value._id !== id) throw invalid("get returned a different identity."); return value; }); },
|
|
49
|
+
list: function () { return call("list", [], snapshots); }, getCached: function () { return snapshots(adapter.getCached()); },
|
|
50
|
+
insert: function (value) { return call("insert", [value], function (result) { if (result !== undefined) throw invalid("insert must resolve without a native result."); }); },
|
|
51
|
+
replaceIfRevision: function (id, revision, value) { return call("replaceIfRevision", [id, revision, value], function (result) { if (typeof result !== "boolean") throw invalid("replaceIfRevision must resolve to a boolean."); return result; }); },
|
|
52
|
+
serialize: function (scope, work) { return call("serialize", [scope, function () { return work(); }], function (result) { return result; }); }
|
|
53
|
+
};
|
|
54
|
+
if (typeof adapter.diagnostics === "function") bound.diagnostics = function () {
|
|
55
|
+
return call("diagnostics", [], function (result) { try { return JSON.parse(JSON.stringify(result)); } catch (cause) { throw invalid("diagnostics must resolve to JSON data."); } });
|
|
56
|
+
};
|
|
57
|
+
return bound;
|
|
58
|
+
}
|
|
59
|
+
function createDefaultKnowledgeDbStorage(options) {
|
|
60
|
+
// Lazy engine loading: injected adapters can run domain code without NeDB installed.
|
|
61
|
+
return require("./knowledge-db-storage-nedb").createKnowledgeDbNedbStorage(options);
|
|
62
|
+
}
|
|
63
|
+
function createKnowledgeDbStorage(options) {
|
|
64
|
+
var settings = options || {}; var adapter;
|
|
65
|
+
if (settings.storageAdapter !== undefined) adapter = settings.storageAdapter;
|
|
66
|
+
else if (settings.storageFactory !== undefined) {
|
|
67
|
+
if (typeof settings.storageFactory !== "function") throw invalid("storageFactory must be a synchronous function.");
|
|
68
|
+
adapter = settings.storageFactory({ projectDir: settings.projectDir });
|
|
69
|
+
} else adapter = module.exports.createDefaultKnowledgeDbStorage({ projectDir: settings.projectDir });
|
|
70
|
+
return bindAdapter(validateAdapter(adapter));
|
|
71
|
+
}
|
|
72
|
+
module.exports = { createKnowledgeDbStorage: createKnowledgeDbStorage, createDefaultKnowledgeDbStorage: createDefaultKnowledgeDbStorage, validateAdapter: validateAdapter, invalid: invalid };
|
|
Binary file
|
|
@@ -136,7 +136,13 @@ function createKnowledgeFileStore(cwd, options) {
|
|
|
136
136
|
var listed = list(); var identity = listed.filter(function (entry) { return entry.name === item.name; })[0]; if (!identity) return; ensureMetadata(true);
|
|
137
137
|
var folder = path.join(historyRoot, identity.id); safeDirectory(folder, true);
|
|
138
138
|
var target = path.join(folder, Date.now() + "-" + item.revision + ".md");
|
|
139
|
-
fs.writeFileSync(target, item.content, { flag: "wx", mode: 0o600 });
|
|
139
|
+
try { fs.writeFileSync(target, item.content, { flag: "wx", mode: 0o600 }); }
|
|
140
|
+
catch (error) {
|
|
141
|
+
// A move and its link rewrite can snapshot the same revision in one millisecond.
|
|
142
|
+
if (error.code !== "EEXIST") throw error;
|
|
143
|
+
var stat = fs.lstatSync(target);
|
|
144
|
+
if (!stat.isFile() || stat.isSymbolicLink() || fs.readFileSync(target, "utf8") !== item.content) throw error;
|
|
145
|
+
}
|
|
140
146
|
}
|
|
141
147
|
function save(name, content, expectedRevision) {
|
|
142
148
|
var clean = cleanName(name); if (!clean || itemType(clean) !== "text") throw new Error("Only valid Markdown documents can be edited.");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Stable catalog identities are the only accepted library references.
|
|
2
|
+
var catalog = require('./public/mate-avatars/catalog.json');
|
|
3
|
+
var ids = new Set(catalog.map(function (entry) { return entry.id; }));
|
|
4
|
+
function valid(id) { return typeof id === 'string' && ids.has(id); }
|
|
5
|
+
function validate(profile, mate) {
|
|
6
|
+
if (profile !== undefined && (!profile || typeof profile !== 'object' || Array.isArray(profile))) return 'Choose a valid Mate profile.';
|
|
7
|
+
if (profile && Object.prototype.hasOwnProperty.call(profile, 'avatarCatalogId') && profile.avatarCatalogId !== '' && !valid(profile.avatarCatalogId) && !(profile.avatarCatalogId === 'lead-clay' && mate && mate.builtinKey === 'clay')) {
|
|
8
|
+
return 'Choose an avatar from the Clay catalog.';
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
module.exports = { valid: valid, validate: validate };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Stable v1 migration, run only against the caller's normal Mate registry.
|
|
2
|
+
var catalog = require('./mate-avatar-catalog');
|
|
3
|
+
var managedArtwork = { clay: ['/clay-studio-symbol.png', '/mates/ally.png', '/mate-avatars/lead-clay.png'], ally: ['/mates/ally.png'] };
|
|
4
|
+
function mappedId(mate) {
|
|
5
|
+
var p = mate.profile || mate;
|
|
6
|
+
var key = JSON.stringify([mate.id || '', p.avatarStyle || 'imprint', p.avatarSeed || mate.id || p.displayName || mate.name || 'Mate']);
|
|
7
|
+
var hash = 2166136261;
|
|
8
|
+
for (var i = 0; i < key.length; i++) hash = Math.imul(hash ^ key.charCodeAt(i), 16777619) >>> 0;
|
|
9
|
+
var index = hash % 96;
|
|
10
|
+
return (index < 48 ? 'B' : 'C') + String(index % 48 + 1).padStart(2, '0');
|
|
11
|
+
}
|
|
12
|
+
function hasCustom(mate) {
|
|
13
|
+
var p = mate.profile || {};
|
|
14
|
+
var custom = p.avatarCustom || mate.avatarCustom;
|
|
15
|
+
if (!custom) return false;
|
|
16
|
+
var managed = managedArtwork[mate.builtinKey] || [];
|
|
17
|
+
return managed.indexOf(custom) === -1;
|
|
18
|
+
}
|
|
19
|
+
function normalizeMate(mate) {
|
|
20
|
+
if (!mate || hasCustom(mate)) return false;
|
|
21
|
+
var before = JSON.stringify(mate.profile || {});
|
|
22
|
+
var p = Object.assign({}, mate.profile || {});
|
|
23
|
+
p.avatarCatalogId = mate.builtinKey === 'clay' ? 'lead-clay' : catalog.valid(p.avatarCatalogId) ? p.avatarCatalogId : mappedId(mate);
|
|
24
|
+
p.avatarStyle = 'catalog';
|
|
25
|
+
delete p.avatarSeed;
|
|
26
|
+
if (p.avatarCustom) p.avatarCustom = '';
|
|
27
|
+
if (mate.builtinKey === 'clay') p.avatarLocked = true;
|
|
28
|
+
if (before === JSON.stringify(p) && !Object.prototype.hasOwnProperty.call(mate, 'avatarStyle') && !Object.prototype.hasOwnProperty.call(mate, 'avatarSeed') && !Object.prototype.hasOwnProperty.call(mate, 'avatarCustom')) return false;
|
|
29
|
+
mate.profile = p;
|
|
30
|
+
delete mate.avatarStyle;
|
|
31
|
+
delete mate.avatarSeed;
|
|
32
|
+
delete mate.avatarCustom;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
function migrate(data) {
|
|
36
|
+
var changed = false;
|
|
37
|
+
data.mates.forEach(function (mate) { if (normalizeMate(mate)) changed = true; });
|
|
38
|
+
if (data.avatarCatalogMigrationVersion !== 1) { data.avatarCatalogMigrationVersion = 1; changed = true; }
|
|
39
|
+
return changed;
|
|
40
|
+
}
|
|
41
|
+
module.exports = { mappedId: mappedId, hasCustom: hasCustom, normalizeMate: normalizeMate, migrate: migrate };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Ordinary Mate Knowledge bindings. The service supplies live validation and
|
|
2
|
+
// the already-authorized scope; this module only exposes the Mate operations.
|
|
3
|
+
|
|
4
|
+
function createMateBound(options) {
|
|
5
|
+
var settings = options || {};
|
|
6
|
+
var revalidate = settings.revalidate;
|
|
7
|
+
var initial = settings.initial;
|
|
8
|
+
|
|
9
|
+
function ownScope() {
|
|
10
|
+
return [revalidate().own];
|
|
11
|
+
}
|
|
12
|
+
function db(method, args) {
|
|
13
|
+
var current = revalidate();
|
|
14
|
+
if (!current.databaseService || typeof current.databaseService[method] !== "function") throw new Error("Knowledge databases are unavailable for this Mate.");
|
|
15
|
+
return current.databaseService[method](Object.assign({}, args || {}, { actor: current.actor }));
|
|
16
|
+
}
|
|
17
|
+
function text(method, args) {
|
|
18
|
+
var current = revalidate();
|
|
19
|
+
return settings.textTools[method](current.own, args || {}, current.actor);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
isClay: false,
|
|
23
|
+
mateId: initial.own.mateId,
|
|
24
|
+
scopeId: initial.own.scopeId,
|
|
25
|
+
listKnowledge: function (args) { return settings.listFor(ownScope(), args || {}, false); },
|
|
26
|
+
searchKnowledge: function (args) { return settings.searchFor(ownScope(), args || {}, false); },
|
|
27
|
+
readKnowledge: function (args) { return settings.readFor(ownScope(), args || {}, false); },
|
|
28
|
+
readKnowledgeDocument: function (args) { return text("read", args); },
|
|
29
|
+
createKnowledgeDocument: function (args) { return text("create", args); },
|
|
30
|
+
updateKnowledgeDocument: function (args) { return text("update", args); },
|
|
31
|
+
listKnowledgeDocumentHistory: function (args) { return text("history", args); },
|
|
32
|
+
restoreKnowledgeDocument: function (args) { return text("restore", args); },
|
|
33
|
+
listDatabases: function (args) { return db("list", args); },
|
|
34
|
+
createDatabase: function (args) { return db("create", args); },
|
|
35
|
+
readDatabase: function (args) { return db("read", args); },
|
|
36
|
+
renameDatabase: function (args) { return db("rename", args); },
|
|
37
|
+
archiveDatabase: function (args) { return db("archive", args); },
|
|
38
|
+
recoverDatabase: function (args) { return db("recover", args); },
|
|
39
|
+
updateDatabaseSchema: function (args) { return db("updateSchema", args); },
|
|
40
|
+
saveTableView: function (args) { return db("saveView", args); },
|
|
41
|
+
queryDatabase: function (args) { return db("query", args); },
|
|
42
|
+
createDatabaseRecord: function (args) { return db("createRecord", args); },
|
|
43
|
+
readDatabaseRecord: function (args) { return db("readRecord", args); },
|
|
44
|
+
updateDatabaseRecord: function (args) { return db("updateRecord", args); },
|
|
45
|
+
archiveDatabaseRecord: function (args) { return db("archiveRecord", args); },
|
|
46
|
+
recoverDatabaseRecord: function (args) { return db("recoverRecord", args); },
|
|
47
|
+
retryDatabaseSync: function (args) { return db("retryProjection", args); },
|
|
48
|
+
importDatabaseRecords: function (args) { return db("importRecords", args); },
|
|
49
|
+
readDatabaseImportProgress: function (args) { return db("readImportProgress", args); },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { createMateBound: createMateBound };
|
|
@@ -9,11 +9,17 @@
|
|
|
9
9
|
|
|
10
10
|
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
11
11
|
var service = require("./mate-knowledge-service");
|
|
12
|
+
var zod;
|
|
13
|
+
try { zod = require("zod"); } catch (error) { zod = null; }
|
|
12
14
|
|
|
13
15
|
var MATE_CONTRACT =
|
|
14
16
|
"Your Knowledge is the durable personal and expertise context you own: what you have learned, the material you keep, and your accumulated observations. " +
|
|
15
17
|
"It is yours alone. There are no other Knowledge collections available to you here, and project Logs are a separate, unrelated surface. " +
|
|
18
|
+
"Use text documents for narrative or multiline Markdown content, and databases for structured records, relationships, filters, and numeric totals. " +
|
|
16
19
|
"Search it when recalling something specific would genuinely change your answer, and read only the records you actually need. " +
|
|
20
|
+
"Database list and search results are summaries for database discovery, not database row content: discover a database, read its schema, then query_database with stable field ids, typed filters, explicit sorts, bounded pagination, and totals; read individual rows by stable record id. Text search and text-document reads retain their own content and revision semantics. " +
|
|
21
|
+
"For text edits, use the returned document id and revision, reread after a conflict, and retry with the current revision. " +
|
|
22
|
+
"Treat errors as failures. A successful revision/id with indexSyncPending or projectionPending means canonical content was saved but indexing is pending: report that exact state, do not claim it is indexed, and do not blindly retry a mutation that could create a duplicate. " +
|
|
17
23
|
"Do not enumerate or restate your Knowledge to the user unasked, and never reproduce whole records as filler.";
|
|
18
24
|
|
|
19
25
|
var CLAY_CONTRACT =
|
|
@@ -56,24 +62,35 @@ var OPERATION = { type: "string", description: "Stable retry id formatted label_
|
|
|
56
62
|
var FIELD = { type: "object", description: "Field with stable id, name, type, required, and type-specific options or reference target." };
|
|
57
63
|
var FILTER = { type: "object", description: "Validated filter: fieldId, op, and value. Supported ops are eq, neq, contains, gt, gte, lt, lte, empty, not-empty." };
|
|
58
64
|
var SORT = { type: "object", description: "Validated sort with fieldId and asc or desc direction." };
|
|
65
|
+
var FIELD_SCHEMA = zod ? zod.object({ id: zod.string(), name: zod.string(), type: zod.string(), required: zod.boolean().optional(), options: zod.any().optional(), target: zod.string().optional(), targetDatabaseId: zod.string().optional(), archived: zod.boolean().optional() }) : null;
|
|
66
|
+
var FILTER_SCHEMA = zod ? zod.object({ fieldId: zod.string(), op: zod.string(), value: zod.any().optional() }) : null;
|
|
67
|
+
var SORT_SCHEMA = zod ? zod.object({ fieldId: zod.string(), direction: zod.enum(["asc", "desc"]) }) : null;
|
|
68
|
+
var VALUES_SCHEMA = zod ? zod.record(zod.string(), zod.any()) : null;
|
|
69
|
+
var VIEW_SCHEMA = zod ? zod.object({ id: zod.string(), name: zod.string(), columns: zod.array(zod.string()), filters: zod.array(FILTER_SCHEMA || zod.any()).optional(), sorts: zod.array(SORT_SCHEMA || zod.any()).optional(), archived: zod.boolean().optional() }) : null;
|
|
70
|
+
var IMPORT_ROW_SCHEMA = zod ? zod.object({ sourceKey: zod.string(), sourcePayload: zod.any(), values: zod.record(zod.string(), zod.any()) }) : null;
|
|
71
|
+
var IMPORT_ROW = IMPORT_ROW_SCHEMA ? { schema: IMPORT_ROW_SCHEMA, description: "Source row with sourceKey, immutable JSON sourcePayload, and typed values keyed by stable schema field id. Record references use sourceNamespace/sourceKey, including forward references within this batch." } : { type: "object", description: "Source row with sourceKey, immutable JSON sourcePayload, and typed values keyed by stable schema field id. Record references use sourceNamespace/sourceKey, including forward references within this batch." };
|
|
72
|
+
var DOCUMENT_ID = { type: "string", description: "Stable document id returned by a text-document tool." };
|
|
73
|
+
var DOCUMENT_REVISION = { type: "string", description: "Revision returned by the most recent text-document read or mutation; required for updates and restore." };
|
|
59
74
|
|
|
60
75
|
function databaseTools(bound) {
|
|
61
76
|
return [
|
|
62
|
-
{ name: "list_databases", description: "
|
|
63
|
-
{ name: "create_database", description: "Create a first-class database in this Mate's Knowledge.
|
|
64
|
-
{ name: "read_database", description: "Read one Knowledge database
|
|
77
|
+
{ name: "list_databases", description: "Discover canonical Knowledge databases, including archived databases when requested. Results are database summaries only, not row content; read a chosen database before querying it.", inputSchema: buildShape({ archived: { type: "boolean" } }, []), handler: handler(bound, "listDatabases") },
|
|
78
|
+
{ name: "create_database", description: "Create a first-class structured database in this Mate's Knowledge. Define typed fields for values, relationships, and numeric totals; use text documents for narrative or multiline material.", inputSchema: buildShape({ name: { type: "string" }, schema: FIELD_SCHEMA ? { schema: zod.array(FIELD_SCHEMA), description: "Typed field descriptors." } : { type: "array", items: FIELD }, operationId: OPERATION }, ["name", "operationId"]), handler: handler(bound, "createDatabase") },
|
|
79
|
+
{ name: "read_database", description: "Read one discovered Knowledge database by stable database id to obtain its current schema, views, revision, and record count before querying rows.", inputSchema: buildShape({ databaseId: ID, includeArchived: { type: "boolean" } }, ["databaseId"]), handler: handler(bound, "readDatabase") },
|
|
65
80
|
{ name: "rename_database", description: "Rename a Knowledge database with optimistic revision checking.", inputSchema: buildShape({ databaseId: ID, name: { type: "string" }, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "name", "expectedRevision", "operationId"]), handler: handler(bound, "renameDatabase") },
|
|
66
81
|
{ name: "archive_database", description: "Recoverably archive a Knowledge database.", inputSchema: buildShape({ databaseId: ID, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "expectedRevision", "operationId"]), handler: handler(bound, "archiveDatabase") },
|
|
67
82
|
{ name: "recover_database", description: "Recover an archived Knowledge database.", inputSchema: buildShape({ databaseId: ID, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "expectedRevision", "operationId"]), handler: handler(bound, "recoverDatabase") },
|
|
68
|
-
{ name: "update_database_schema", description: "Replace the schema using stable field ids. Exactly one active title field is required; populated fields cannot be removed or change type and should be archived instead.", inputSchema: buildShape({ databaseId: ID, schema: { type: "array", items: FIELD }, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "schema", "expectedRevision", "operationId"]), handler: handler(bound, "updateDatabaseSchema") },
|
|
69
|
-
{ name: "save_table_view", description: "Create or revise a persisted Table view with selected columns, validated filters, and deterministic sorts.", inputSchema: buildShape({ databaseId: ID, view: { type: "object", description: "View with stable id, name, columns, filters, and sorts." }, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "view", "expectedRevision", "operationId"]), handler: handler(bound, "saveTableView") },
|
|
70
|
-
{ name: "query_database", description: "Query a bounded deterministic page
|
|
71
|
-
{ name: "create_database_record", description: "Create a typed record. Values are keyed by stable field id; unknown fields and unsafe keys are rejected.", inputSchema: buildShape({ databaseId: ID, values: { type: "object" }, expectedDatabaseRevision: REVISION, operationId: OPERATION }, ["databaseId", "values", "expectedDatabaseRevision", "operationId"]), handler: handler(bound, "createDatabaseRecord") },
|
|
83
|
+
{ name: "update_database_schema", description: "Replace the schema using stable field ids. Exactly one active title field is required; populated fields cannot be removed or change type and should be archived instead.", inputSchema: buildShape({ databaseId: ID, schema: FIELD_SCHEMA ? { schema: zod.array(FIELD_SCHEMA), description: "Complete typed field descriptor array." } : { type: "array", items: FIELD }, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "schema", "expectedRevision", "operationId"]), handler: handler(bound, "updateDatabaseSchema") },
|
|
84
|
+
{ name: "save_table_view", description: "Create or revise a persisted Table view with selected columns, validated filters, and deterministic sorts.", inputSchema: buildShape({ databaseId: ID, view: VIEW_SCHEMA ? { schema: VIEW_SCHEMA, description: "View with stable id, name, columns, filters, and sorts." } : { type: "object", description: "View with stable id, name, columns, filters, and sorts." }, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "view", "expectedRevision", "operationId"]), handler: handler(bound, "saveTableView") },
|
|
85
|
+
{ name: "query_database", description: "Query a bounded deterministic page using stable field ids, typed validated filters, explicit sorts, and a cursor. Returns only the requested rows plus count and numeric sums for the same filter scope; do not infer totals from one page.", inputSchema: buildShape({ databaseId: ID, viewId: ID, filters: FILTER_SCHEMA ? { schema: zod.array(FILTER_SCHEMA) } : { type: "array", items: FILTER }, sorts: SORT_SCHEMA ? { schema: zod.array(SORT_SCHEMA) } : { type: "array", items: SORT }, cursor: { type: "string" }, limit: { type: "number" }, archived: { type: "boolean" } }, ["databaseId"]), handler: handler(bound, "queryDatabase") },
|
|
86
|
+
{ name: "create_database_record", description: "Create a typed record. Values are keyed by stable field id; unknown fields and unsafe keys are rejected.", inputSchema: buildShape({ databaseId: ID, values: VALUES_SCHEMA ? { schema: VALUES_SCHEMA } : { type: "object" }, expectedDatabaseRevision: REVISION, operationId: OPERATION }, ["databaseId", "values", "expectedDatabaseRevision", "operationId"]), handler: handler(bound, "createDatabaseRecord") },
|
|
72
87
|
{ name: "read_database_record", description: "Read one record with its values, revision, attribution, archive state, and bounded history.", inputSchema: buildShape({ databaseId: ID, recordId: ID }, ["databaseId", "recordId"]), handler: handler(bound, "readDatabaseRecord") },
|
|
73
|
-
{ name: "update_database_record", description: "Update selected typed values using the record revision. Edits to other fields made since a prior save are not overwritten.", inputSchema: buildShape({ databaseId: ID, recordId: ID, values: { type: "object" }, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "recordId", "values", "expectedRevision", "operationId"]), handler: handler(bound, "updateDatabaseRecord") },
|
|
88
|
+
{ name: "update_database_record", description: "Update selected typed values using the record revision. Edits to other fields made since a prior save are not overwritten.", inputSchema: buildShape({ databaseId: ID, recordId: ID, values: VALUES_SCHEMA ? { schema: VALUES_SCHEMA } : { type: "object" }, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "recordId", "values", "expectedRevision", "operationId"]), handler: handler(bound, "updateDatabaseRecord") },
|
|
74
89
|
{ name: "archive_database_record", description: "Recoverably archive one record using its current revision.", inputSchema: buildShape({ databaseId: ID, recordId: ID, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "recordId", "expectedRevision", "operationId"]), handler: handler(bound, "archiveDatabaseRecord") },
|
|
75
90
|
{ name: "recover_database_record", description: "Recover one archived record using its current revision.", inputSchema: buildShape({ databaseId: ID, recordId: ID, expectedRevision: REVISION, operationId: OPERATION }, ["databaseId", "recordId", "expectedRevision", "operationId"]), handler: handler(bound, "recoverDatabaseRecord") },
|
|
76
91
|
{ name: "retry_database_sync", description: "Retry the shared append-only Knowledge summary projection after a canonical database mutation reported projectionPending.", inputSchema: buildShape({ databaseId: ID }, ["databaseId"]), handler: handler(bound, "retryDatabaseSync") },
|
|
92
|
+
{ name: "import_database_records", description: "Import up to 100 source rows (256 KiB per batch) into a discovered database using immutable source identities and typed values. Use sourceNamespace/sourceKey references for same-batch or already-imported cross-database links; replay the exact operation and payload after uncertainty, and reread the current database revision before a stale retry.", inputSchema: buildShape({ databaseId: ID, expectedDatabaseRevision: REVISION, operationId: OPERATION, sourceNamespace: { type: "string", description: "Stable source namespace for this import mapping." }, rows: IMPORT_ROW_SCHEMA ? { schema: zod.array(IMPORT_ROW_SCHEMA), description: "At most 100 source rows and 256 KiB of serialized row JSON." } : { type: "array", description: "At most 100 source rows and 256 KiB of serialized row JSON.", items: IMPORT_ROW } }, ["databaseId", "expectedDatabaseRevision", "operationId", "sourceNamespace", "rows"]), handler: handler(bound, "importDatabaseRecords") },
|
|
93
|
+
{ name: "read_database_import_progress", description: "Read bounded source-to-native record mappings by database id and source namespace. Use returned stable record ids for later corrections; this does not read arbitrary filesystem paths or caller-selected owners.", inputSchema: buildShape({ databaseId: ID, sourceNamespace: { type: "string" }, offset: { type: "number" }, limit: { type: "number", description: "Mapping page size from 1 to 100." } }, ["databaseId", "sourceNamespace"]), handler: handler(bound, "readDatabaseImportProgress") },
|
|
77
94
|
].map(function (tool) { tool.description = MATE_CONTRACT + " " + tool.description; return tool; });
|
|
78
95
|
}
|
|
79
96
|
|
|
@@ -110,6 +127,36 @@ function mateTools(bound) {
|
|
|
110
127
|
}, ["ref"]),
|
|
111
128
|
handler: handler(bound, "readKnowledge"),
|
|
112
129
|
},
|
|
130
|
+
{
|
|
131
|
+
name: "read_knowledge_document",
|
|
132
|
+
description: MATE_CONTRACT + " Read the complete current Markdown document by its relative path or stable document id, including its current revision.",
|
|
133
|
+
inputSchema: buildShape({ name: { type: "string", description: "Relative Markdown path under this Mate's Knowledge directory." }, documentId: DOCUMENT_ID }, []),
|
|
134
|
+
handler: handler(bound, "readKnowledgeDocument"),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "create_knowledge_document",
|
|
138
|
+
description: MATE_CONTRACT + " Create a new Markdown document at a safe relative path. Use this for narrative or multiline content; it is synchronized into Knowledge search after the canonical save.",
|
|
139
|
+
inputSchema: buildShape({ name: { type: "string", description: "Relative Markdown path under this Mate's Knowledge directory." }, content: { type: "string", description: "Complete Markdown document content." } }, ["name", "content"]),
|
|
140
|
+
handler: handler(bound, "createKnowledgeDocument"),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "update_knowledge_document",
|
|
144
|
+
description: MATE_CONTRACT + " Update an existing Markdown document by relative path or stable document id. The current revision is mandatory, and stale writes are rejected.",
|
|
145
|
+
inputSchema: buildShape({ name: { type: "string", description: "Relative Markdown path under this Mate's Knowledge directory." }, documentId: DOCUMENT_ID, content: { type: "string", description: "Complete replacement Markdown document content." }, expectedRevision: DOCUMENT_REVISION }, ["content", "expectedRevision"]),
|
|
146
|
+
handler: handler(bound, "updateKnowledgeDocument"),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "list_knowledge_document_history",
|
|
150
|
+
description: MATE_CONTRACT + " List recoverable revisions for one Markdown document by relative path or stable document id.",
|
|
151
|
+
inputSchema: buildShape({ name: { type: "string", description: "Relative Markdown path under this Mate's Knowledge directory." }, documentId: DOCUMENT_ID }, []),
|
|
152
|
+
handler: handler(bound, "listKnowledgeDocumentHistory"),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "restore_knowledge_document",
|
|
156
|
+
description: MATE_CONTRACT + " Restore a selected historical Markdown revision. The current revision is mandatory so a concurrent edit cannot be overwritten.",
|
|
157
|
+
inputSchema: buildShape({ name: { type: "string", description: "Relative Markdown path under this Mate's Knowledge directory." }, documentId: DOCUMENT_ID, version: { type: "string", description: "Version returned by list_knowledge_document_history." }, expectedRevision: DOCUMENT_REVISION }, ["version", "expectedRevision"]),
|
|
158
|
+
handler: handler(bound, "restoreKnowledgeDocument"),
|
|
159
|
+
},
|
|
113
160
|
].concat(databaseTools(bound));
|
|
114
161
|
}
|
|
115
162
|
|
|
@@ -15,6 +15,8 @@ var builtinMates = require("./builtin-mates");
|
|
|
15
15
|
var knowledgeImport = require("./knowledge-import");
|
|
16
16
|
var knowledgeSearch = require("./knowledge-search");
|
|
17
17
|
var mateSync = require("./mate-knowledge-sync");
|
|
18
|
+
var textToolsModule = require("./mate-knowledge-text-tools");
|
|
19
|
+
var mateBoundModule = require("./mate-knowledge-mate-bound");
|
|
18
20
|
|
|
19
21
|
var MAX_PAGE = 50;
|
|
20
22
|
var DEFAULT_PAGE = 20;
|
|
@@ -97,6 +99,7 @@ function attachMateKnowledgeService(ctx) {
|
|
|
97
99
|
var listMates = context.listMates || function () { return []; };
|
|
98
100
|
var baseDir = context.baseDir;
|
|
99
101
|
var importerCache = new Map();
|
|
102
|
+
var textTools = textToolsModule.createMateKnowledgeTextTools({ baseDir: baseDir });
|
|
100
103
|
|
|
101
104
|
function importerForScope(scopeId) {
|
|
102
105
|
if (context.openImporter) return context.openImporter(scopeId);
|
|
@@ -379,6 +382,8 @@ function attachMateKnowledgeService(ctx) {
|
|
|
379
382
|
if (source.session) {
|
|
380
383
|
var manager = project.getSessionManager ? project.getSessionManager() : project.sm;
|
|
381
384
|
if (!manager || !manager.sessions || manager.sessions.get(source.session.localId) !== source.session) return null;
|
|
385
|
+
if (source.projectOwnerId && source.session.ownerId && source.session.ownerId !== source.projectOwnerId) return null;
|
|
386
|
+
if (isMultiUser() && (!source.projectOwnerId || source.session.ownerId !== source.projectOwnerId)) return null;
|
|
382
387
|
}
|
|
383
388
|
if (typeof resolveMate !== "function") return null;
|
|
384
389
|
var mate = resolveMate(source.projectOwnerId || null, source.mateId);
|
|
@@ -422,36 +427,10 @@ function attachMateKnowledgeService(ctx) {
|
|
|
422
427
|
// An ordinary Mate's own scope, and nothing else. There is no argument on
|
|
423
428
|
// any tool that could widen it.
|
|
424
429
|
function createMateBound(revalidate, initial) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
var current = revalidate(); if (!current.databaseService || typeof current.databaseService[method] !== "function") throw new Error("Knowledge databases are unavailable for this Mate.");
|
|
430
|
-
return current.databaseService[method](Object.assign({}, args || {}, { actor: current.actor }));
|
|
431
|
-
}
|
|
432
|
-
return {
|
|
433
|
-
isClay: false,
|
|
434
|
-
mateId: initial.own.mateId,
|
|
435
|
-
scopeId: initial.own.scopeId,
|
|
436
|
-
listKnowledge: function (args) { return listFor(ownScope(), args || {}, false); },
|
|
437
|
-
searchKnowledge: function (args) { return searchFor(ownScope(), args || {}, false); },
|
|
438
|
-
readKnowledge: function (args) { return readFor(ownScope(), args || {}, false); },
|
|
439
|
-
listDatabases: function (args) { return db("list", args); },
|
|
440
|
-
createDatabase: function (args) { return db("create", args); },
|
|
441
|
-
readDatabase: function (args) { return db("read", args); },
|
|
442
|
-
renameDatabase: function (args) { return db("rename", args); },
|
|
443
|
-
archiveDatabase: function (args) { return db("archive", args); },
|
|
444
|
-
recoverDatabase: function (args) { return db("recover", args); },
|
|
445
|
-
updateDatabaseSchema: function (args) { return db("updateSchema", args); },
|
|
446
|
-
saveTableView: function (args) { return db("saveView", args); },
|
|
447
|
-
queryDatabase: function (args) { return db("query", args); },
|
|
448
|
-
createDatabaseRecord: function (args) { return db("createRecord", args); },
|
|
449
|
-
readDatabaseRecord: function (args) { return db("readRecord", args); },
|
|
450
|
-
updateDatabaseRecord: function (args) { return db("updateRecord", args); },
|
|
451
|
-
archiveDatabaseRecord: function (args) { return db("archiveRecord", args); },
|
|
452
|
-
recoverDatabaseRecord: function (args) { return db("recoverRecord", args); },
|
|
453
|
-
retryDatabaseSync: function (args) { return db("retryProjection", args); },
|
|
454
|
-
};
|
|
430
|
+
return mateBoundModule.createMateBound({
|
|
431
|
+
revalidate: revalidate, initial: initial, textTools: textTools,
|
|
432
|
+
listFor: listFor, searchFor: searchFor, readFor: readFor,
|
|
433
|
+
});
|
|
455
434
|
}
|
|
456
435
|
|
|
457
436
|
// Every Mate belonging to the same user, resolved fresh on each call so a
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Session-bound text Knowledge mutations for one authorized Mate.
|
|
2
|
+
//
|
|
3
|
+
// This module deliberately uses the existing portable file store and
|
|
4
|
+
// write-through synchronizer. It never accepts a Mate, owner, or filesystem
|
|
5
|
+
// root from a tool caller; the caller supplies only a relative document name
|
|
6
|
+
// or the stable identity returned by the store.
|
|
7
|
+
|
|
8
|
+
var crypto = require("crypto");
|
|
9
|
+
var path = require("path");
|
|
10
|
+
var knowledgeFiles = require("./knowledge-file-store");
|
|
11
|
+
var mateSync = require("./mate-knowledge-sync");
|
|
12
|
+
|
|
13
|
+
function metadataRoot(baseDir, mateDir) {
|
|
14
|
+
var key = crypto.createHash("sha256").update(path.resolve(mateDir)).digest("hex").slice(0, 24);
|
|
15
|
+
return path.join(baseDir || path.join(mateDir, ".clay"), "knowledge-documents", key);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function failedSync(summary) {
|
|
19
|
+
if (!summary) return { indexSyncPending: true, syncError: "Knowledge index synchronization returned no result." };
|
|
20
|
+
if (summary.failed === 0) return { indexSyncPending: false, syncError: null };
|
|
21
|
+
var error = summary.errors && summary.errors[0];
|
|
22
|
+
return { indexSyncPending: true, syncError: error && error.message || "Knowledge index synchronization failed." };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createMateKnowledgeTextTools(options) {
|
|
26
|
+
var settings = options || {};
|
|
27
|
+
var stores = new Map();
|
|
28
|
+
|
|
29
|
+
function storeFor(mate) {
|
|
30
|
+
if (!mate || typeof mate.mateDir !== "string") throw new Error("Knowledge text tools require an authorized Mate.");
|
|
31
|
+
var directory = path.resolve(mate.mateDir);
|
|
32
|
+
if (!stores.has(directory)) stores.set(directory, knowledgeFiles.createKnowledgeFileStore(directory, { metaDir: metadataRoot(settings.baseDir, directory) }));
|
|
33
|
+
return stores.get(directory);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function find(store, input) {
|
|
37
|
+
var args = input || {};
|
|
38
|
+
var hasName = typeof args.name === "string" && args.name.length > 0;
|
|
39
|
+
var hasId = typeof args.documentId === "string" && args.documentId.length > 0;
|
|
40
|
+
if (!hasName && !hasId) throw new Error("A document name or stable document id is required.");
|
|
41
|
+
var item = null;
|
|
42
|
+
if (hasId) item = store.list().filter(function (entry) { return entry.id === args.documentId; })[0] || null;
|
|
43
|
+
if (hasId && !item) throw Object.assign(new Error("Knowledge document not found."), { code: "ENOENT" });
|
|
44
|
+
if (hasName) {
|
|
45
|
+
var byName = store.read(args.name);
|
|
46
|
+
if (item && item.id !== byName.id) throw new Error("The document name and id identify different documents.");
|
|
47
|
+
item = byName;
|
|
48
|
+
}
|
|
49
|
+
if (!item) throw Object.assign(new Error("Knowledge document not found."), { code: "ENOENT" });
|
|
50
|
+
if (hasId && !hasName) item = store.read(item.name);
|
|
51
|
+
if (item.itemType !== "text") throw Object.assign(new Error("Only Markdown Knowledge documents are writable through these tools."), { code: "READ_ONLY" });
|
|
52
|
+
return item;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sync(mate, name, actor) {
|
|
56
|
+
try {
|
|
57
|
+
var summary = mateSync.syncMateSource({ mateDir: mate.mateDir, fileName: name, baseDir: settings.baseDir, actor: actor });
|
|
58
|
+
return failedSync(summary);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return { indexSyncPending: true, syncError: error.message || "Knowledge index synchronization failed." };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function read(mate, args) {
|
|
65
|
+
var item = find(storeFor(mate), args);
|
|
66
|
+
return { documentId: item.id, name: item.name, content: item.content, revision: item.revision };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function create(mate, args, actor) {
|
|
70
|
+
var input = args || {};
|
|
71
|
+
if (typeof input.name !== "string" || !input.name) throw new Error("A document name is required to create a Knowledge document.");
|
|
72
|
+
if (typeof input.content !== "string") throw new Error("Document content is required.");
|
|
73
|
+
var store = storeFor(mate);
|
|
74
|
+
var saved = store.save(input.name, input.content, null);
|
|
75
|
+
return Object.assign({ documentId: saved.id, name: saved.name, revision: saved.revision }, sync(mate, saved.name, actor));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function update(mate, args, actor) {
|
|
79
|
+
var input = args || {};
|
|
80
|
+
if (typeof input.expectedRevision !== "string" || !input.expectedRevision) throw Object.assign(new Error("A current document revision is required for updates."), { code: "STALE" });
|
|
81
|
+
if (typeof input.content !== "string") throw new Error("Document content is required.");
|
|
82
|
+
var store = storeFor(mate);
|
|
83
|
+
var current = find(store, input);
|
|
84
|
+
var saved = store.save(current.name, input.content, input.expectedRevision);
|
|
85
|
+
return Object.assign({ documentId: saved.id, name: saved.name, revision: saved.revision }, sync(mate, saved.name, actor));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function history(mate, args) {
|
|
89
|
+
var current = find(storeFor(mate), args);
|
|
90
|
+
return { documentId: current.id, name: current.name, versions: storeFor(mate).history(current.id) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function restore(mate, args, actor) {
|
|
94
|
+
var input = args || {};
|
|
95
|
+
if (typeof input.expectedRevision !== "string" || !input.expectedRevision) throw Object.assign(new Error("A current document revision is required for restore."), { code: "STALE" });
|
|
96
|
+
if (typeof input.version !== "string" || !input.version) throw new Error("A history version is required.");
|
|
97
|
+
var store = storeFor(mate);
|
|
98
|
+
var current = find(store, input);
|
|
99
|
+
var restored = store.restore(current.id, input.version, current.name, input.expectedRevision);
|
|
100
|
+
return Object.assign({ documentId: restored.id, name: restored.name, revision: restored.revision }, sync(mate, restored.name, actor));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { read: read, create: create, update: update, history: history, restore: restore };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { createMateKnowledgeTextTools: createMateKnowledgeTextTools };
|
package/lib/mates.js
CHANGED
|
@@ -4,6 +4,8 @@ var path = require("path");
|
|
|
4
4
|
var crypto = require("crypto");
|
|
5
5
|
var config = require("./config");
|
|
6
6
|
var workspaceColor = require("./mate-workspace-color");
|
|
7
|
+
var avatarCatalog = require("./mate-avatar-catalog");
|
|
8
|
+
var avatarMigration = require("./mate-avatar-migration");
|
|
7
9
|
|
|
8
10
|
var crisisSafety = require("./crisis-safety");
|
|
9
11
|
var matesPrompts = require("./mates-prompts");
|
|
@@ -98,10 +100,12 @@ function loadMates(ctx) {
|
|
|
98
100
|
var raw = fs.readFileSync(matesFilePath(ctx), "utf8");
|
|
99
101
|
var data = JSON.parse(raw);
|
|
100
102
|
if (!data.mates) data.mates = [];
|
|
101
|
-
return data;
|
|
102
103
|
} catch (e) {
|
|
103
104
|
return defaultData();
|
|
104
105
|
}
|
|
106
|
+
// Migration write failures must propagate, never masquerade as an empty registry.
|
|
107
|
+
if (avatarMigration.migrate(data)) saveMates(ctx, data);
|
|
108
|
+
return data;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
function saveMates(ctx, data) {
|
|
@@ -135,6 +139,7 @@ function createMate(ctx, seedData) {
|
|
|
135
139
|
displayName: null,
|
|
136
140
|
avatarColor: "#5857fc",
|
|
137
141
|
avatarStyle: "imprint",
|
|
142
|
+
avatarCatalogId: "B" + String(crypto.randomInt(1, 49)).padStart(2, "0"),
|
|
138
143
|
avatarSeed: crypto.randomBytes(4).toString("hex"),
|
|
139
144
|
},
|
|
140
145
|
bio: null,
|
|
@@ -142,6 +147,7 @@ function createMate(ctx, seedData) {
|
|
|
142
147
|
interviewProjectPath: null,
|
|
143
148
|
};
|
|
144
149
|
|
|
150
|
+
avatarMigration.normalizeMate(mate);
|
|
145
151
|
data.mates.push(mate);
|
|
146
152
|
saveMates(ctx, data);
|
|
147
153
|
|
|
@@ -205,6 +211,8 @@ function updateMate(ctx, id, updates) {
|
|
|
205
211
|
if (data.mates[i].primary || data.mates[i].builtinKey === "clay") delete updates.workspaceColor;
|
|
206
212
|
else if (!workspaceColor.valid(updates.workspaceColor)) return { error: "Choose a supported workspace color." };
|
|
207
213
|
}
|
|
214
|
+
var avatarError = avatarCatalog.validate(updates.profile, data.mates[i]);
|
|
215
|
+
if (avatarError) return { error: avatarError };
|
|
208
216
|
// Primary mates: protect name and core identity fields
|
|
209
217
|
if (data.mates[i].primary) {
|
|
210
218
|
delete updates.name;
|
|
@@ -219,6 +227,7 @@ function updateMate(ctx, id, updates) {
|
|
|
219
227
|
// Locked built-in artwork is server-managed. Preserve it even when a
|
|
220
228
|
// stale or crafted client submits a replacement profile object.
|
|
221
229
|
if (data.mates[i].profile && data.mates[i].profile.avatarLocked && updates.profile) {
|
|
230
|
+
updates.profile.avatarCatalogId = data.mates[i].profile.avatarCatalogId || "";
|
|
222
231
|
updates.profile.avatarStyle = data.mates[i].profile.avatarStyle;
|
|
223
232
|
updates.profile.avatarSeed = data.mates[i].profile.avatarSeed;
|
|
224
233
|
updates.profile.avatarColor = data.mates[i].profile.avatarColor;
|
|
@@ -229,6 +238,7 @@ function updateMate(ctx, id, updates) {
|
|
|
229
238
|
for (var j = 0; j < keys.length; j++) {
|
|
230
239
|
data.mates[i][keys[j]] = updates[keys[j]];
|
|
231
240
|
}
|
|
241
|
+
avatarMigration.normalizeMate(data.mates[i]);
|
|
232
242
|
saveMates(ctx, data);
|
|
233
243
|
return data.mates[i];
|
|
234
244
|
}
|
|
@@ -506,6 +516,7 @@ function createBuiltinMate(ctx, builtinKey) {
|
|
|
506
516
|
interviewProjectPath: null,
|
|
507
517
|
};
|
|
508
518
|
|
|
519
|
+
avatarMigration.normalizeMate(mate);
|
|
509
520
|
data.mates.push(mate);
|
|
510
521
|
saveMates(ctx, data);
|
|
511
522
|
|
|
@@ -661,8 +672,8 @@ function syncPrimaryMates(ctx) {
|
|
|
661
672
|
}
|
|
662
673
|
// Locked avatars are code-managed — refresh them from the def so a
|
|
663
674
|
// built-in's avatar can be updated by changing the source file
|
|
664
|
-
//
|
|
665
|
-
if (def.avatarLocked) {
|
|
675
|
+
// User uploads are preserved while canonical library artwork follows the definition.
|
|
676
|
+
if (def.avatarLocked && !avatarMigration.hasCustom(mate)) {
|
|
666
677
|
if (def.avatarCustom != null && mate.profile.avatarCustom !== def.avatarCustom) {
|
|
667
678
|
mate.profile.avatarCustom = def.avatarCustom; changed = true;
|
|
668
679
|
}
|