clay-server 5.1.0-beta.1 → 5.1.0-beta.3
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/bin/cli.js +9 -91
- package/lib/builtin-mates.js +7 -7
- package/lib/daemon-project-access.js +7 -1
- package/lib/daemon-settings.js +11 -0
- package/lib/daemon.js +10 -37
- 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/linear-store.js +35 -8
- package/lib/mate-identity-file.js +39 -0
- package/lib/mate-knowledge-mcp-server.js +29 -6
- package/lib/mate-knowledge-service.js +23 -2
- package/lib/mate-knowledge-sync.js +37 -22
- package/lib/mate-ready-creation.js +2 -1
- package/lib/mate-workspace-color.js +4 -0
- package/lib/mates-identity.js +4 -4
- package/lib/mates-prompts.js +4 -3
- package/lib/mates.js +30 -11
- package/lib/os-users.js +5 -16
- package/lib/project-connection.js +36 -9
- package/lib/project-debate-proposal.js +6 -0
- package/lib/project-debate-workspace.js +43 -0
- package/lib/project-debate.js +4 -2
- package/lib/project-filesystem.js +21 -39
- package/lib/project-knowledge.js +315 -136
- package/lib/project-mate-creation-proposal.js +1 -0
- package/lib/project-mate-interaction.js +7 -6
- package/lib/project-server-settings.js +31 -0
- package/lib/project-sessions.js +92 -182
- package/lib/project-user-knowledge.js +60 -0
- package/lib/project.js +62 -21
- package/lib/public/app.js +79 -71
- 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/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/input.css +3 -1
- 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/menus.css +3 -0
- package/lib/public/css/messages.css +1 -114
- package/lib/public/css/mobile-nav.css +16 -16
- package/lib/public/css/overlays.css +15 -0
- package/lib/public/css/project-logs.css +7 -0
- package/lib/public/css/project-mate-creation.css +105 -0
- package/lib/public/css/project-settings-modal.css +56 -0
- package/lib/public/css/right-workbench.css +3 -3
- package/lib/public/css/server-settings-system.css +125 -0
- package/lib/public/css/session-folders.css +80 -21
- package/lib/public/css/session-github.css +25 -16
- package/lib/public/css/settings-ui.css +247 -22
- package/lib/public/css/shared-browser.css +7 -3
- package/lib/public/css/sidebar.css +183 -107
- package/lib/public/css/title-bar.css +156 -1
- 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 +206 -548
- package/lib/public/modules/account-island.js +232 -0
- package/lib/public/modules/admin-settings-email.js +197 -0
- package/lib/public/modules/admin-settings-invitations.js +183 -0
- package/lib/public/modules/admin-settings-projects.js +198 -0
- package/lib/public/modules/admin-settings-ui.js +100 -0
- package/lib/public/modules/admin-settings-users.js +331 -0
- package/lib/public/modules/admin.js +19 -949
- package/lib/public/modules/app-cursors.js +19 -43
- package/lib/public/modules/app-debate-ui.js +4 -3
- package/lib/public/modules/app-dm.js +0 -2
- 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 +47 -61
- package/lib/public/modules/app-notifications.js +4 -7
- package/lib/public/modules/app-projects.js +5 -38
- package/lib/public/modules/app-rate-limit.js +70 -12
- package/lib/public/modules/app-rendering.js +12 -13
- package/lib/public/modules/avatar.js +30 -9
- package/lib/public/modules/chat-render-runtime.js +16 -4
- package/lib/public/modules/command-palette.js +2 -15
- package/lib/public/modules/context-sources.js +27 -18
- 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 +88 -65
- package/lib/public/modules/header-tui-font.js +10 -20
- 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/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/linear-settings.js +3 -2
- package/lib/public/modules/markdown.js +5 -6
- 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/notification-settings.js +81 -0
- package/lib/public/modules/notifications.js +44 -28
- package/lib/public/modules/personal-settings-dialog.js +51 -0
- package/lib/public/modules/profile.js +66 -24
- 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-settings-environment.js +106 -0
- package/lib/public/modules/project-settings.js +72 -628
- 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/server-settings-controls.js +316 -0
- package/lib/public/modules/server-settings-environment.js +194 -0
- package/lib/public/modules/server-settings-navigation.js +39 -0
- package/lib/public/modules/server-settings-state.js +54 -0
- package/lib/public/modules/server-settings.js +116 -788
- package/lib/public/modules/server-storage-settings.js +84 -0
- 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-github.js +2 -1
- package/lib/public/modules/session-list-visibility.js +41 -0
- package/lib/public/modules/session-presence-ui.js +3 -0
- package/lib/public/modules/session-title-marquee.js +51 -0
- package/lib/public/modules/settings-env-values.js +34 -0
- package/lib/public/modules/settings-select.js +121 -0
- package/lib/public/modules/shared-browser.js +22 -12
- 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/stt-picker.js +14 -5
- package/lib/public/modules/terminal-prefs.js +22 -0
- package/lib/public/modules/theme.js +88 -52
- 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/user-settings-account.js +318 -0
- package/lib/public/modules/user-settings-appearance.js +55 -0
- package/lib/public/modules/user-settings-editors.js +24 -0
- package/lib/public/modules/user-settings-integrations.js +104 -0
- package/lib/public/modules/user-settings-layout.js +80 -0
- package/lib/public/modules/user-settings-preferences.js +48 -0
- package/lib/public/modules/user-settings-profile.js +53 -0
- package/lib/public/modules/user-settings-themes.js +38 -0
- package/lib/public/modules/user-settings-voice.js +75 -0
- package/lib/public/modules/user-settings.js +53 -480
- 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 +20 -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/sdk-message-processor.js +16 -4
- package/lib/sdk-quota-snapshot.js +37 -0
- package/lib/server-admin.js +11 -0
- package/lib/server-appearance-settings.js +71 -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-linear.js +14 -8
- package/lib/server-mate-instructions.js +44 -0
- package/lib/server-mates.js +14 -4
- package/lib/server-notification-settings.js +67 -0
- package/lib/server-palette.js +1 -1
- package/lib/server-settings.js +69 -1
- package/lib/server-storage.js +57 -0
- package/lib/server-user-knowledge.js +119 -0
- package/lib/server.js +31 -3
- package/lib/session-visibility.js +7 -2
- package/lib/sessions.js +35 -10
- package/lib/startup-projects.js +40 -0
- package/lib/storage-native-walk.js +85 -0
- package/lib/storage-usage.js +144 -0
- package/lib/user-knowledge-service.js +167 -0
- package/lib/users-permissions.js +7 -0
- package/lib/users-preferences.js +0 -74
- package/lib/users-project-mate-filter-preferences.js +39 -0
- package/lib/users.js +6 -4
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +52 -4
- package/lib/yoke/acp-process-manager.js +2 -2
- package/lib/yoke/adapters/codex.js +12 -52
- package/lib/yoke/codex-app-server.js +2 -2
- package/lib/yoke/codex-model-catalog.js +52 -0
- package/lib/yoke/codex-rate-limits.js +26 -0
- package/package.json +12 -3
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
var path = require("path");
|
|
2
|
+
|
|
3
|
+
function slug(value) {
|
|
4
|
+
return String(value || "").trim().toLowerCase().replace(/<[^>]*>/g, "").replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function frontmatter(content) {
|
|
8
|
+
var text = String(content || "");
|
|
9
|
+
if (text.indexOf("---\n") !== 0) return { properties: {}, tags: [], end: 0, errors: [] };
|
|
10
|
+
var close = text.indexOf("\n---\n", 4);
|
|
11
|
+
if (close === -1 || close > 8192) return { properties: {}, tags: [], end: 0, errors: ["Frontmatter is missing its closing --- line."] };
|
|
12
|
+
var properties = {}; var errors = []; var lines = text.slice(4, close).split("\n");
|
|
13
|
+
for (var i = 0; i < lines.length; i++) {
|
|
14
|
+
if (!lines[i].trim() || /^\s*#/.test(lines[i])) continue;
|
|
15
|
+
var match = /^([A-Za-z][A-Za-z0-9_-]{0,63}):\s*(.*)$/.exec(lines[i]);
|
|
16
|
+
if (!match) { errors.push("Invalid property on line " + (i + 2) + "."); continue; }
|
|
17
|
+
var value = match[2].trim();
|
|
18
|
+
if (value.length > 500) errors.push("Property " + match[1] + " is too long.");
|
|
19
|
+
else properties[match[1]] = value;
|
|
20
|
+
}
|
|
21
|
+
var tags = [];
|
|
22
|
+
if (properties.tags) {
|
|
23
|
+
var raw = properties.tags.replace(/^\[/, "").replace(/\]$/, "").split(",");
|
|
24
|
+
for (var t = 0; t < raw.length; t++) {
|
|
25
|
+
var tag = raw[t].trim().replace(/^#/, "");
|
|
26
|
+
if (/^[\p{L}\p{N}_/-]{1,64}$/u.test(tag) && tags.indexOf(tag) === -1) tags.push(tag);
|
|
27
|
+
else if (tag) errors.push("Invalid tag: " + tag);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { properties: properties, tags: tags, end: close + 5, errors: errors };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ignoredRanges(text) {
|
|
34
|
+
var ranges = []; var lines = text.split("\n"); var offset = 0; var fence = null;
|
|
35
|
+
for (var i = 0; i < lines.length; i++) {
|
|
36
|
+
var line = lines[i]; var marker = /^( {0,3})(`{3,}|~{3,})(.*)$/.exec(line);
|
|
37
|
+
if (marker && (!fence || marker[2][0] === fence.marker && marker[2].length >= fence.length && /^\s*$/.test(marker[3]))) {
|
|
38
|
+
if (!fence) fence = { marker: marker[2][0], length: marker[2].length, start: offset };
|
|
39
|
+
else { ranges.push([fence.start, offset + line.length]); fence = null; }
|
|
40
|
+
}
|
|
41
|
+
if (!fence) {
|
|
42
|
+
var cursor = 0;
|
|
43
|
+
while (cursor < line.length) {
|
|
44
|
+
var opener = /`+/.exec(line.slice(cursor)); if (!opener) break;
|
|
45
|
+
var start = cursor + opener.index; var run = opener[0]; var close = line.indexOf(run, start + run.length);
|
|
46
|
+
if (close === -1) { cursor = start + run.length; continue; }
|
|
47
|
+
ranges.push([offset + start, offset + close + run.length]); cursor = close + run.length;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
offset += line.length + 1;
|
|
51
|
+
}
|
|
52
|
+
if (fence) ranges.push([fence.start, text.length]);
|
|
53
|
+
return ranges;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function escaped(text, start) {
|
|
57
|
+
var slashes = 0;
|
|
58
|
+
for (var i = start - 1; i >= 0 && text[i] === "\\"; i--) slashes++;
|
|
59
|
+
return slashes % 2 === 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function inside(ranges, start) {
|
|
63
|
+
for (var i = 0; i < ranges.length; i++) if (start >= ranges[i][0] && start < ranges[i][1]) return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parse(content) {
|
|
68
|
+
var text = String(content || ""); var ignored = ignoredRanges(text); var links = []; var headings = []; var match;
|
|
69
|
+
var headingPattern = /^(#{1,6})\s+(.+?)\s*#*\s*$/gm;
|
|
70
|
+
while ((match = headingPattern.exec(text))) {
|
|
71
|
+
if (!inside(ignored, match.index)) headings.push({ depth: match[1].length, text: match[2].trim(), slug: slug(match[2]), offset: match.index });
|
|
72
|
+
}
|
|
73
|
+
var wiki = /(!)?\[\[([^\]\n]+)\]\]/g;
|
|
74
|
+
while ((match = wiki.exec(text))) {
|
|
75
|
+
if (inside(ignored, match.index) || escaped(text, match.index)) continue;
|
|
76
|
+
var parts = match[2].split("|"); var target = parts[0].trim(); var alias = parts.length > 1 ? parts.slice(1).join("|").trim() : "";
|
|
77
|
+
var hash = target.indexOf("#");
|
|
78
|
+
var wikiTarget = hash === -1 ? target : target.slice(0, hash);
|
|
79
|
+
links.push({ kind: match[1] ? "embed" : "wiki", target: wikiTarget, heading: hash === -1 ? "" : target.slice(hash + 1), alias: alias,
|
|
80
|
+
start: match.index, end: match.index + match[0].length, raw: match[0], external: /^[a-z][a-z0-9+.-]*:/i.test(wikiTarget) || wikiTarget.indexOf("//") === 0 });
|
|
81
|
+
}
|
|
82
|
+
var markdown = /(!)?\[([^\]\n]*)\]\(([^)\n]+)\)/g;
|
|
83
|
+
while ((match = markdown.exec(text))) {
|
|
84
|
+
if (inside(ignored, match.index) || escaped(text, match.index)) continue;
|
|
85
|
+
var destination = match[3].trim().replace(/^<|>$/g, ""); var hashAt = destination.indexOf("#");
|
|
86
|
+
links.push({ kind: match[1] ? "image" : "markdown", target: hashAt === -1 ? destination : destination.slice(0, hashAt), heading: hashAt === -1 ? "" : destination.slice(hashAt + 1), alias: match[2],
|
|
87
|
+
start: match.index, end: match.index + match[0].length, raw: match[0], external: /^[a-z][a-z0-9+.-]*:/i.test(destination) || destination.indexOf("//") === 0 });
|
|
88
|
+
}
|
|
89
|
+
links.sort(function (a, b) { return a.start - b.start; });
|
|
90
|
+
var meta = frontmatter(text); var tags = meta.tags.slice(); var tagPattern = /(^|\s)#([\p{L}\p{N}_/-]{1,64})/gu;
|
|
91
|
+
while ((match = tagPattern.exec(text))) if (!inside(ignored, match.index) && tags.indexOf(match[2]) === -1) tags.push(match[2]);
|
|
92
|
+
return { links: links, headings: headings, properties: meta.properties, tags: tags, propertyErrors: meta.errors };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeTarget(sourceName, target) {
|
|
96
|
+
var raw = String(target || "").trim();
|
|
97
|
+
if (!raw || /^[a-z][a-z0-9+.-]*:/i.test(raw) || raw.indexOf("//") === 0) return null;
|
|
98
|
+
try { raw = decodeURIComponent(raw); } catch (error) {}
|
|
99
|
+
raw = raw.replace(/\\/g, "/");
|
|
100
|
+
if (!/\.md$/i.test(raw)) raw += ".md";
|
|
101
|
+
return path.posix.normalize(path.posix.join(path.posix.dirname(sourceName), raw)).replace(/^\.\//, "");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveLink(sourceName, link, documents, sourceId) {
|
|
105
|
+
if (link.external) return null;
|
|
106
|
+
if (!link.target && link.heading) {
|
|
107
|
+
var self = documents.filter(function (item) { return sourceId ? item.id === sourceId : item.name === sourceName; });
|
|
108
|
+
return self.length === 1 ? self[0] : null;
|
|
109
|
+
}
|
|
110
|
+
var target = normalizeTarget(sourceName, link.target);
|
|
111
|
+
if (!target) return null;
|
|
112
|
+
var exact = documents.filter(function (item) { return item.name.toLowerCase() === target.toLowerCase(); });
|
|
113
|
+
if (exact.length === 1) return exact[0];
|
|
114
|
+
if (link.target.indexOf("/") === -1 && link.target.indexOf("\\") === -1) {
|
|
115
|
+
var base = path.posix.basename(target).toLowerCase();
|
|
116
|
+
var named = documents.filter(function (item) { return path.posix.basename(item.name).toLowerCase() === base; });
|
|
117
|
+
if (named.length === 1) return named[0];
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function snippet(content, offset, needle) {
|
|
123
|
+
var raw = String(content || ""); var at = typeof offset === "number" ? offset : raw.toLowerCase().indexOf(String(needle || "").toLowerCase());
|
|
124
|
+
if (at < 0) at = 0; var start = Math.max(0, at - 70); var end = Math.min(raw.length, at + 150); var text = raw.slice(start, end).replace(/\s+/g, " ").trim();
|
|
125
|
+
return (start ? "…" : "") + text + (end < raw.length ? "…" : "");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { parse: parse, slug: slug, normalizeTarget: normalizeTarget, resolveLink: resolveLink, snippet: snippet };
|
package/lib/linear-store.js
CHANGED
|
@@ -19,34 +19,61 @@ function attachLinearStore(root) {
|
|
|
19
19
|
catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
20
20
|
return fs.readFileSync(file);
|
|
21
21
|
}
|
|
22
|
-
function
|
|
22
|
+
function record(owner, id) {
|
|
23
23
|
var data = read(owner);
|
|
24
|
+
return !id || id === "default" ? data : Object.hasOwn(data.connections || {}, id) ? data.connections[id] : {};
|
|
25
|
+
}
|
|
26
|
+
function key(owner, id) {
|
|
27
|
+
var data = record(owner, id);
|
|
24
28
|
if (!data.secret) return null;
|
|
25
29
|
var parts = data.secret.split('.').map(function (part) { return Buffer.from(part, 'base64'); });
|
|
26
30
|
var cipher = crypto.createDecipheriv('aes-256-gcm', masterKey(), parts[0]);
|
|
27
|
-
cipher.setAAD(Buffer.from(owner)); cipher.setAuthTag(parts[1]);
|
|
31
|
+
cipher.setAAD(Buffer.from(id && id !== 'default' ? owner + ':' + id : owner)); cipher.setAuthTag(parts[1]);
|
|
28
32
|
return Buffer.concat([cipher.update(parts[2]), cipher.final()]).toString('utf8');
|
|
29
33
|
}
|
|
30
|
-
function view(owner) {
|
|
31
|
-
var data =
|
|
34
|
+
function view(owner, id) {
|
|
35
|
+
var data = record(owner, id);
|
|
32
36
|
return { connected: !!data.secret, workspace: data.workspace || null, revision: data.revision || null };
|
|
33
37
|
}
|
|
34
|
-
function save(owner, secret, workspace) {
|
|
38
|
+
function save(owner, secret, workspace, id, name) {
|
|
39
|
+
if (id && (!/^[a-zA-Z0-9_-]{1,64}$/.test(id) || ['__proto__', 'constructor', 'prototype'].indexOf(id) >= 0)) throw new Error("Invalid connection.");
|
|
40
|
+
var previous = read(owner);
|
|
35
41
|
var file = filename(owner), data = { revision: crypto.randomBytes(16).toString('hex') };
|
|
36
42
|
if (secret !== null) {
|
|
37
43
|
if (typeof secret !== 'string' || !secret.trim() || secret.length > 4096 || /[\r\n]/.test(secret)) throw new Error('Enter a valid Linear API key.');
|
|
38
44
|
var iv = crypto.randomBytes(12), cipher = crypto.createCipheriv('aes-256-gcm', masterKey(), iv);
|
|
39
|
-
cipher.setAAD(Buffer.from(owner));
|
|
45
|
+
cipher.setAAD(Buffer.from(id && id !== 'default' ? owner + ':' + id : owner));
|
|
40
46
|
var bytes = Buffer.concat([cipher.update(secret.trim(), 'utf8'), cipher.final()]);
|
|
41
47
|
data.secret = [iv, cipher.getAuthTag(), bytes].map(function (part) { return part.toString('base64'); }).join('.');
|
|
42
48
|
data.workspace = workspace;
|
|
43
49
|
}
|
|
50
|
+
if (name !== undefined) {
|
|
51
|
+
if (typeof name !== 'string' || !name.trim() || name.trim().length > 80) throw new Error('Enter a connection name of up to 80 characters.');
|
|
52
|
+
data.name = name.trim();
|
|
53
|
+
} else data.name = record(owner, id).name;
|
|
54
|
+
if (id && id !== 'default') {
|
|
55
|
+
previous.connections = previous.connections || {};
|
|
56
|
+
if (secret === null) delete previous.connections[id];
|
|
57
|
+
else {
|
|
58
|
+
if (!previous.connections[id] && Object.keys(previous.connections).length >= 30) throw new Error('Connection limit reached.');
|
|
59
|
+
previous.connections[id] = data;
|
|
60
|
+
}
|
|
61
|
+
data = previous;
|
|
62
|
+
} else data.connections = previous.connections;
|
|
44
63
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
45
64
|
var temp = file + '.' + crypto.randomBytes(8).toString('hex') + '.tmp';
|
|
46
65
|
try { fs.writeFileSync(temp, JSON.stringify(data), { mode: 0o600 }); fs.renameSync(temp, file); }
|
|
47
66
|
finally { try { fs.unlinkSync(temp); } catch (error) { if (error.code !== 'ENOENT') throw error; } }
|
|
48
|
-
return view(owner);
|
|
67
|
+
return view(owner, id);
|
|
68
|
+
}
|
|
69
|
+
function list(owner) {
|
|
70
|
+
var data = read(owner), entries = [];
|
|
71
|
+
if (data.secret) entries.push(Object.assign({ id: 'default', name: data.name || 'Existing connection', legacy: true }, view(owner)));
|
|
72
|
+
Object.keys(data.connections || {}).forEach(function (id) {
|
|
73
|
+
entries.push(Object.assign({ id: id, name: data.connections[id].name || 'Linear', legacy: false }, view(owner, id)));
|
|
74
|
+
});
|
|
75
|
+
return entries;
|
|
49
76
|
}
|
|
50
|
-
return { key: key, view: view, save: save };
|
|
77
|
+
return { key: key, view: view, save: save, list: list };
|
|
51
78
|
}
|
|
52
79
|
module.exports = { attachLinearStore: attachLinearStore };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Resolve the canonical Mate identity without overwriting either document.
|
|
2
|
+
var fs = require('fs');
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
function regularFile(file) {
|
|
7
|
+
try {
|
|
8
|
+
var stat = fs.lstatSync(file);
|
|
9
|
+
if (!stat.isFile()) throw new Error('Mate identity must be a regular file: ' + file);
|
|
10
|
+
return stat;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error.code === 'ENOENT') return null;
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveMateIdentityFile(directory) {
|
|
18
|
+
var canonical = path.join(directory, 'AGENTS.md');
|
|
19
|
+
var legacy = path.join(directory, 'CLAUDE.md');
|
|
20
|
+
var current = regularFile(canonical);
|
|
21
|
+
if (!regularFile(legacy)) return canonical;
|
|
22
|
+
if (!current) {
|
|
23
|
+
// Exclusive linking preserves owner, mode and bytes and never replaces a
|
|
24
|
+
// concurrently created canonical file. Unlink only after publication.
|
|
25
|
+
try { fs.linkSync(legacy, canonical); }
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code !== 'EEXIST') throw error;
|
|
28
|
+
return resolveMateIdentityFile(directory);
|
|
29
|
+
}
|
|
30
|
+
} else if (fs.statSync(canonical).ino !== fs.statSync(legacy).ino) {
|
|
31
|
+
// Preserve a conflicting legacy document outside instruction discovery.
|
|
32
|
+
var backup = path.join(directory, '.identity-legacy-' + crypto.randomBytes(12).toString('hex') + '.md');
|
|
33
|
+
fs.linkSync(legacy, backup);
|
|
34
|
+
}
|
|
35
|
+
fs.unlinkSync(legacy);
|
|
36
|
+
return canonical;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {resolveMateIdentityFile: resolveMateIdentityFile};
|
|
@@ -46,14 +46,37 @@ function handler(bound, method) {
|
|
|
46
46
|
if (!bound || typeof bound[method] !== "function") {
|
|
47
47
|
return errorResult(new Error("Knowledge tools require an exact session-bound Mate."));
|
|
48
48
|
}
|
|
49
|
-
try {
|
|
50
|
-
return textResult(bound[method](args || {}));
|
|
51
|
-
} catch (e) {
|
|
52
|
-
return errorResult(e);
|
|
53
|
-
}
|
|
49
|
+
try { return Promise.resolve(bound[method](args || {})).then(textResult, errorResult); } catch (e) { return errorResult(e); }
|
|
54
50
|
};
|
|
55
51
|
}
|
|
56
52
|
|
|
53
|
+
var ID = { type: "string", description: "Stable opaque id returned by a Knowledge database tool." };
|
|
54
|
+
var REVISION = { type: "number", description: "Revision returned by the most recent read or mutation; stale revisions are rejected." };
|
|
55
|
+
var OPERATION = { type: "string", description: "Stable retry id formatted label_<current epoch milliseconds>_<unique suffix>. Reuse the exact id and payload after an uncertain response; receipts are retained for the documented 24-hour retry window." };
|
|
56
|
+
var FIELD = { type: "object", description: "Field with stable id, name, type, required, and type-specific options or reference target." };
|
|
57
|
+
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
|
+
var SORT = { type: "object", description: "Validated sort with fieldId and asc or desc direction." };
|
|
59
|
+
|
|
60
|
+
function databaseTools(bound) {
|
|
61
|
+
return [
|
|
62
|
+
{ name: "list_databases", description: "List canonical Knowledge databases, including archived databases when requested, so the Mate can discover and recover them without relying on summary projection.", inputSchema: buildShape({ archived: { type: "boolean" } }, []), handler: handler(bound, "listDatabases") },
|
|
63
|
+
{ name: "create_database", description: "Create a first-class database in this Mate's Knowledge. Defaults to one required title field and a Table view.", inputSchema: buildShape({ name: { type: "string" }, schema: { type: "array", items: FIELD }, operationId: OPERATION }, ["name", "operationId"]), handler: handler(bound, "createDatabase") },
|
|
64
|
+
{ name: "read_database", description: "Read one Knowledge database's current schema, views, revision, and record count.", inputSchema: buildShape({ databaseId: ID, includeArchived: { type: "boolean" } }, ["databaseId"]), handler: handler(bound, "readDatabase") },
|
|
65
|
+
{ 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
|
+
{ 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
|
+
{ 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 of records using a saved view or explicit validated filters and sorts. Returns count and numeric sums.", inputSchema: buildShape({ databaseId: ID, viewId: ID, filters: { type: "array", items: FILTER }, sorts: { type: "array", items: SORT }, cursor: { type: "string" }, limit: { type: "number" }, archived: { type: "boolean" } }, ["databaseId"]), handler: handler(bound, "queryDatabase") },
|
|
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") },
|
|
72
|
+
{ 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") },
|
|
74
|
+
{ 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
|
+
{ 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
|
+
{ 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") },
|
|
77
|
+
].map(function (tool) { tool.description = MATE_CONTRACT + " " + tool.description; return tool; });
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
function mateTools(bound) {
|
|
58
81
|
return [
|
|
59
82
|
{
|
|
@@ -87,7 +110,7 @@ function mateTools(bound) {
|
|
|
87
110
|
}, ["ref"]),
|
|
88
111
|
handler: handler(bound, "readKnowledge"),
|
|
89
112
|
},
|
|
90
|
-
];
|
|
113
|
+
].concat(databaseTools(bound));
|
|
91
114
|
}
|
|
92
115
|
|
|
93
116
|
function clayTools(bound) {
|
|
@@ -393,7 +393,9 @@ function attachMateKnowledgeService(ctx) {
|
|
|
393
393
|
|
|
394
394
|
var def = mate.builtinKey ? builtinMates.getBuiltinByKey(mate.builtinKey) : null;
|
|
395
395
|
var isClay = !!(mate.builtinKey === "clay" && def && def.hostAgent === true);
|
|
396
|
-
return { own: own, isClay: isClay, ownerId: source.projectOwnerId || null
|
|
396
|
+
return { own: own, isClay: isClay, ownerId: source.projectOwnerId || null,
|
|
397
|
+
databaseService: typeof project.getKnowledgeDbService === "function" ? project.getKnowledgeDbService() : null,
|
|
398
|
+
actor: { type: "user", userId: source.session && source.session.ownerId || source.projectOwnerId || null, displayName: null } };
|
|
397
399
|
}
|
|
398
400
|
|
|
399
401
|
// A tool handler captured earlier stays valid only while its exact source
|
|
@@ -408,7 +410,7 @@ function attachMateKnowledgeService(ctx) {
|
|
|
408
410
|
function revalidate() {
|
|
409
411
|
var current = resolve(source);
|
|
410
412
|
if (!current) throw new Error("This Knowledge binding is no longer valid for the current session.");
|
|
411
|
-
if (current.isClay !== initial.isClay || current.own.scopeId !== initial.own.scopeId) {
|
|
413
|
+
if (current.isClay !== initial.isClay || current.own.scopeId !== initial.own.scopeId || current.databaseService !== initial.databaseService) {
|
|
412
414
|
throw new Error("This Knowledge binding is no longer valid for the current session.");
|
|
413
415
|
}
|
|
414
416
|
return current;
|
|
@@ -423,6 +425,10 @@ function attachMateKnowledgeService(ctx) {
|
|
|
423
425
|
function ownScope() {
|
|
424
426
|
return [revalidate().own];
|
|
425
427
|
}
|
|
428
|
+
function db(method, args) {
|
|
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
|
+
}
|
|
426
432
|
return {
|
|
427
433
|
isClay: false,
|
|
428
434
|
mateId: initial.own.mateId,
|
|
@@ -430,6 +436,21 @@ function attachMateKnowledgeService(ctx) {
|
|
|
430
436
|
listKnowledge: function (args) { return listFor(ownScope(), args || {}, false); },
|
|
431
437
|
searchKnowledge: function (args) { return searchFor(ownScope(), args || {}, false); },
|
|
432
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); },
|
|
433
454
|
};
|
|
434
455
|
}
|
|
435
456
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// call idempotent and makes deletions and rewrites work without the call site
|
|
13
13
|
// having to describe what changed.
|
|
14
14
|
//
|
|
15
|
-
// Identity is never mirrored. CLAUDE.md, base-template.md, identity-backup.md,
|
|
15
|
+
// Identity is never mirrored. AGENTS.md, legacy CLAUDE.md, base-template.md, identity-backup.md,
|
|
16
16
|
// and identity-history.jsonl define who a Mate is and remain canonical files.
|
|
17
17
|
// Sticky notes are a separate attention layer and are excluded too.
|
|
18
18
|
|
|
@@ -23,7 +23,7 @@ var config = require("./config");
|
|
|
23
23
|
var knowledgeImport = require("./knowledge-import");
|
|
24
24
|
|
|
25
25
|
var SOURCE_NAMESPACE = "mate-knowledge";
|
|
26
|
-
var SOURCE_VERSION =
|
|
26
|
+
var SOURCE_VERSION = 3;
|
|
27
27
|
var MAX_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
28
28
|
|
|
29
29
|
// Identity and template artifacts. Never mirrored, at any version.
|
|
@@ -84,6 +84,13 @@ function sourceKindFor(name) {
|
|
|
84
84
|
return null;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function safeSourceName(value) {
|
|
88
|
+
var name = String(value || "").replace(/\\/g, "/").replace(/^\.\//, ""); var parts = name.split("/");
|
|
89
|
+
if (!name || name[0] === "/" || name.length > 500 || parts.length > 20) return null;
|
|
90
|
+
if (parts.some(function (part) { return !part || part === "." || part === ".." || part.length > 120; })) return null;
|
|
91
|
+
return parts.join("/");
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
function isJournal(name) {
|
|
88
95
|
return !!JOURNAL_KINDS[name] || /\.jsonl$/.test(name);
|
|
89
96
|
}
|
|
@@ -133,9 +140,8 @@ function isMateSourceEntry(entry) {
|
|
|
133
140
|
if (typeof source.sourceVersion !== "number" || source.sourceVersion > SOURCE_VERSION) return false;
|
|
134
141
|
var name = source.fileName;
|
|
135
142
|
if (!name || typeof name !== "string") return false;
|
|
136
|
-
// The
|
|
137
|
-
|
|
138
|
-
if (path.basename(name) !== name) return false;
|
|
143
|
+
// The source name and relative path must agree, so neither field alone is trusted.
|
|
144
|
+
if (safeSourceName(name) !== name) return false;
|
|
139
145
|
return source.relPath === relativeSource(name);
|
|
140
146
|
}
|
|
141
147
|
|
|
@@ -180,6 +186,18 @@ function readStat(filePath) {
|
|
|
180
186
|
}
|
|
181
187
|
}
|
|
182
188
|
|
|
189
|
+
function safeKnowledgeFileStat(root, name) {
|
|
190
|
+
var parts = name.split("/"); var cursor = root;
|
|
191
|
+
for (var i = 0; i < parts.length; i++) {
|
|
192
|
+
cursor = path.join(cursor, parts[i]); var stat;
|
|
193
|
+
try { stat = fs.lstatSync(cursor); } catch (error) { if (error.code === "ENOENT") return null; throw error; }
|
|
194
|
+
if (stat.isSymbolicLink()) throw new Error("Knowledge source contains a symbolic link.");
|
|
195
|
+
if (i < parts.length - 1 && !stat.isDirectory()) throw new Error("Knowledge source path is not a directory.");
|
|
196
|
+
if (i === parts.length - 1 && !stat.isFile()) return null;
|
|
197
|
+
}
|
|
198
|
+
return fs.statSync(cursor);
|
|
199
|
+
}
|
|
200
|
+
|
|
183
201
|
// --- File-addressed sources -------------------------------------------
|
|
184
202
|
|
|
185
203
|
function syncFile(importer, scope, name, kind, filePath, stat, actor, summary) {
|
|
@@ -361,8 +379,8 @@ function syncMateSource(opts) {
|
|
|
361
379
|
var scope = options.scope || scopeForMateDir(options.mateDir);
|
|
362
380
|
if (!scope) return summary;
|
|
363
381
|
if (options.mateName) scope.mateName = options.mateName;
|
|
364
|
-
var
|
|
365
|
-
if (!name) return summary;
|
|
382
|
+
var rawName = String(options.fileName || ""); var name = safeSourceName(rawName);
|
|
383
|
+
if (!name) { if (rawName) summary.unsupported++; return summary; }
|
|
366
384
|
if (IDENTITY_FILES.indexOf(name) !== -1) { summary.identitySkipped++; return summary; }
|
|
367
385
|
if (EXCLUDED_FILES.indexOf(name) !== -1) { summary.excluded++; return summary; }
|
|
368
386
|
var kind = sourceKindFor(name);
|
|
@@ -370,8 +388,8 @@ function syncMateSource(opts) {
|
|
|
370
388
|
|
|
371
389
|
var actor = normalizeActor(options.actor);
|
|
372
390
|
var importer = options.importer || importerFor(scope, options.baseDir);
|
|
373
|
-
var
|
|
374
|
-
var stat =
|
|
391
|
+
var knowledgeRoot = path.join(scope.mateDir, "knowledge"); var filePath = path.join(knowledgeRoot, name);
|
|
392
|
+
var stat = safeKnowledgeFileStat(knowledgeRoot, name);
|
|
375
393
|
|
|
376
394
|
if (isJournal(name)) syncJournal(importer, scope, name, kind, filePath, stat, actor, summary);
|
|
377
395
|
else syncFile(importer, scope, name, kind, filePath, stat, actor, summary);
|
|
@@ -382,19 +400,11 @@ function syncMateSource(opts) {
|
|
|
382
400
|
return summary;
|
|
383
401
|
}
|
|
384
402
|
|
|
385
|
-
// Alias that reads better at a call site reacting to a removal or a rewrite.
|
|
386
403
|
function reconcileSource(opts) {
|
|
387
404
|
return syncMateSource(opts);
|
|
388
405
|
}
|
|
389
406
|
|
|
390
407
|
// Reconcile every durable source in one Mate directory, in both directions.
|
|
391
|
-
//
|
|
392
|
-
// Enumerating the directory alone is not enough. A knowledge file or a whole
|
|
393
|
-
// journal can be deleted while the daemon is stopped, or by direct filesystem
|
|
394
|
-
// tooling, and nothing would ever call syncMateSource for it again. So the
|
|
395
|
-
// scope's own live records are also consulted: any source this module
|
|
396
|
-
// previously imported that is no longer on disk is reconciled, which tombstones
|
|
397
|
-
// a missing file record or every line of a missing journal.
|
|
398
408
|
function reconcileMate(opts) {
|
|
399
409
|
var options = opts || {};
|
|
400
410
|
var summary = options.summary || newSummary();
|
|
@@ -404,11 +414,17 @@ function reconcileMate(opts) {
|
|
|
404
414
|
var knowledgeDir = path.join(scope.mateDir, "knowledge");
|
|
405
415
|
var present = {};
|
|
406
416
|
var entries = [];
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
417
|
+
function discover(relative) {
|
|
418
|
+
var folder = relative ? path.join(knowledgeDir, relative) : knowledgeDir; var children;
|
|
419
|
+
try { children = fs.readdirSync(folder, { withFileTypes: true }); } catch (error) { return; }
|
|
420
|
+
for (var d = 0; d < children.length; d++) {
|
|
421
|
+
var name = relative ? relative + "/" + children[d].name : children[d].name; var stat;
|
|
422
|
+
try { stat = fs.lstatSync(path.join(knowledgeDir, name)); } catch (error) { continue; }
|
|
423
|
+
if (stat.isSymbolicLink()) continue;
|
|
424
|
+
if (stat.isDirectory()) discover(name); else if (stat.isFile()) entries.push({ name: name });
|
|
425
|
+
}
|
|
411
426
|
}
|
|
427
|
+
discover("");
|
|
412
428
|
var importer = options.importer || importerFor(scope, options.baseDir);
|
|
413
429
|
|
|
414
430
|
function reconcileOne(fileName) {
|
|
@@ -423,7 +439,6 @@ function reconcileMate(opts) {
|
|
|
423
439
|
}
|
|
424
440
|
|
|
425
441
|
for (var i = 0; i < entries.length; i++) {
|
|
426
|
-
if (!entries[i].isFile()) continue;
|
|
427
442
|
// Recorded as present whatever its kind, so an excluded or unsupported file
|
|
428
443
|
// that still exists is never mistaken for a deleted source.
|
|
429
444
|
present[entries[i].name] = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
var resolveMateIdentityFile = require('./mate-identity-file').resolveMateIdentityFile;
|
|
1
2
|
// Atomic finalization of an interviewed Mate. No placeholder is exposed unless
|
|
2
3
|
// the complete identity has passed validation and is ready to register.
|
|
3
4
|
|
|
@@ -55,7 +56,7 @@ function createReadyMate(mates, mateCtx, definition) {
|
|
|
55
56
|
autonomy: ready.autonomy,
|
|
56
57
|
});
|
|
57
58
|
var mateDir = mates.getMateDir(mateCtx, mate.id);
|
|
58
|
-
var identityPath =
|
|
59
|
+
var identityPath = resolveMateIdentityFile(mateDir);
|
|
59
60
|
fs.writeFileSync(identityPath, ready.identityMarkdown + "\n");
|
|
60
61
|
mates.enforceAllSections(identityPath, { ctx: mateCtx, mateId: mate.id });
|
|
61
62
|
fs.mkdirSync(path.join(mateDir, "knowledge"), { recursive: true });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Only curated pigments may affect a Mate's workspace surface.
|
|
2
|
+
var COLORS = ['mint', 'rose', 'plum', 'coral', 'peach', 'gold', 'sky', 'indigo', 'stone'];
|
|
3
|
+
function valid(value) { return typeof value === 'string' && COLORS.indexOf(value) !== -1; }
|
|
4
|
+
module.exports = { colors: COLORS, valid: valid };
|
package/lib/mates-identity.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Mates identity module -- Identity extraction, backup, and change tracking.
|
|
3
3
|
*
|
|
4
4
|
* Manages the boundary between a mate's user-authored identity and
|
|
5
|
-
* system-managed sections in
|
|
5
|
+
* system-managed sections in AGENTS.md.
|
|
6
6
|
* Extracted from mates.js to keep module sizes manageable.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ var IDENTITY_MIN_LENGTH = 50;
|
|
|
18
18
|
/**
|
|
19
19
|
* Build the capabilities section for a primary mate.
|
|
20
20
|
* Injected as a system section so it auto-updates with code changes
|
|
21
|
-
* without touching the mate's identity in
|
|
21
|
+
* without touching the mate's identity in AGENTS.md.
|
|
22
22
|
*/
|
|
23
23
|
function buildPrimaryCapabilitiesSection(mate) {
|
|
24
24
|
if (!mate || !mate.primary) return "";
|
|
@@ -52,9 +52,9 @@ function buildPrimaryCapabilitiesSection(mate) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Extract identity content from a
|
|
55
|
+
* Extract identity content from a AGENTS.md string.
|
|
56
56
|
* Identity is everything before the first system marker.
|
|
57
|
-
* @param {string} content - full
|
|
57
|
+
* @param {string} content - full AGENTS.md content
|
|
58
58
|
* @param {string[]} allMarkers - array of all system marker strings
|
|
59
59
|
*/
|
|
60
60
|
function extractIdentity(content, allMarkers) {
|
package/lib/mates-prompts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mates prompts module -- System section enforcers for mate
|
|
2
|
+
* Mates prompts module -- System section enforcers for mate AGENTS.md files.
|
|
3
3
|
*
|
|
4
4
|
* Each enforcer manages a specific system-managed section (team awareness,
|
|
5
5
|
* session memory, sticky notes, project registry, debate awareness).
|
|
@@ -20,7 +20,7 @@ var TEAM_SECTION =
|
|
|
20
20
|
"**This section is managed by the system and cannot be removed.**\n\n" +
|
|
21
21
|
"You are one of several AI Mates in this workspace. Your teammates and their profiles are listed in `../mates.json`. " +
|
|
22
22
|
"Each teammate's identity and working style is described in their own directory:\n\n" +
|
|
23
|
-
"- `../{mate_id}/
|
|
23
|
+
"- `../{mate_id}/AGENTS.md` -- their identity, personality, and working style\n" +
|
|
24
24
|
"- `../{mate_id}/mate.yaml` -- their metadata (name, role, status, activities)\n" +
|
|
25
25
|
"- `../common-knowledge.json` -- shared knowledge registry; files listed here are readable by all mates\n\n" +
|
|
26
26
|
"Check the team registry when it would be relevant to know who else is available or what they do. " +
|
|
@@ -31,6 +31,7 @@ var SESSION_MEMORY_MARKER = "<!-- SESSION_MEMORY_MANAGED_BY_SYSTEM -->";
|
|
|
31
31
|
var SESSION_MEMORY_SECTION =
|
|
32
32
|
"\n\n" + SESSION_MEMORY_MARKER + "\n" +
|
|
33
33
|
"## Session Memory\n\n" +
|
|
34
|
+
"Your identity is stored in `AGENTS.md` in your Mate directory. Read and edit that file when reshaping your identity; do not create a separate `CLAUDE.md`.\n\n" +
|
|
34
35
|
"**This section is managed by the system and cannot be removed.**\n\n" +
|
|
35
36
|
"Your `knowledge/memory-summary.md` file contains your compressed long-term memory, " +
|
|
36
37
|
"automatically maintained across sessions. Refer to it for context about past " +
|
|
@@ -115,7 +116,7 @@ function buildTeamSection(ctx, currentMateId, loadMates) {
|
|
|
115
116
|
|
|
116
117
|
section += "\n" +
|
|
117
118
|
"Each teammate's full identity is in their own directory:\n\n" +
|
|
118
|
-
"- `../{mate_id}/
|
|
119
|
+
"- `../{mate_id}/AGENTS.md` -- identity, personality, working style\n" +
|
|
119
120
|
"- `../{mate_id}/mate.yaml` -- metadata (name, role, status, activities)\n" +
|
|
120
121
|
"- `../common-knowledge.json` -- shared knowledge readable by all mates\n\n" +
|
|
121
122
|
"Use the **ID** (not the name) when referencing teammates in structured data. " +
|