clay-server 4.0.0-beta.8 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +15 -1
- package/lib/project-file-history.js +223 -0
- package/lib/project-file-watch.js +77 -49
- package/lib/project-filesystem.js +31 -220
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-request-access.js +66 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +185 -6
- package/lib/public/app.js +63 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +84 -3
- package/lib/public/css/messages.css +40 -1
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +97 -10
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +36 -3
- package/lib/public/index.html +43 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +124 -6
- package/lib/public/modules/app-home-hub.js +12 -8
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +92 -11
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +3 -2
- package/lib/public/modules/app-projects.js +56 -18
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-surface-boot.js +7 -3
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-activation.js +45 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-pair-ui.js +15 -4
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/split-worker-runtime.js +24 -0
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/thinking-lifecycle.js +175 -0
- package/lib/public/modules/thinking-summary.js +28 -0
- package/lib/public/modules/thinking-view.js +75 -0
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +31 -123
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -20
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +135 -46
- package/lib/sdk-message-processor.js +36 -19
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +137 -22
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +41 -0
- package/lib/sessions.js +57 -10
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +6 -6
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex.js +24 -14
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// Shared BM25 adapter for Clay Knowledge surfaces.
|
|
2
|
+
//
|
|
3
|
+
// There is exactly one ranking implementation in Clay: the generic
|
|
4
|
+
// buildIndex/searchIndex engine in session-search.js, including its tokenizer
|
|
5
|
+
// with CJK/Japanese/Thai bigram segmentation. This module is a thin adapter
|
|
6
|
+
// over it, not a second ranker.
|
|
7
|
+
//
|
|
8
|
+
// Field weighting is expressed by controlled repetition of a field's text
|
|
9
|
+
// before indexing, which is how session-search.js already weights digests.
|
|
10
|
+
// A title repeated three times contributes three times the term frequency of
|
|
11
|
+
// the body, so BM25 saturation and length normalisation still apply normally.
|
|
12
|
+
//
|
|
13
|
+
// Long records are segmented rather than truncated. A record whose body runs to
|
|
14
|
+
// tens of thousands of characters is indexed as several overlapping documents,
|
|
15
|
+
// so a term that appears only near the end is still found. Short fields (title,
|
|
16
|
+
// name, tags) are anchors and are repeated into every segment, so weighting is
|
|
17
|
+
// identical in each one. Segments are collapsed back to one result per original
|
|
18
|
+
// item by taking its best-scoring segment; the caller still applies its own
|
|
19
|
+
// score, recency, and reference tie-breaks on top.
|
|
20
|
+
|
|
21
|
+
var sessionSearch = require("./session-search");
|
|
22
|
+
|
|
23
|
+
var MAX_SNIPPET_CHARS = 320;
|
|
24
|
+
var SNIPPET_LEAD_CHARS = 80;
|
|
25
|
+
// A field at or under this length is an anchor, replicated into every segment.
|
|
26
|
+
// Anything longer is windowed.
|
|
27
|
+
var SEGMENT_CHARS = 8000;
|
|
28
|
+
// Overlap so a term straddling a window boundary is still indexed intact.
|
|
29
|
+
var SEGMENT_OVERLAP = 800;
|
|
30
|
+
// Memory bound. A record needing more windows than this is indexed up to the
|
|
31
|
+
// cap; the cap is high enough that no realistic Knowledge record reaches it.
|
|
32
|
+
var MAX_SEGMENTS_PER_ITEM = 64;
|
|
33
|
+
|
|
34
|
+
function repeat(text, weight) {
|
|
35
|
+
var out = [];
|
|
36
|
+
for (var i = 0; i < weight; i++) out.push(text);
|
|
37
|
+
return out.join(" ");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeWeight(weight) {
|
|
41
|
+
return Number.isFinite(weight) ? Math.max(1, Math.floor(weight)) : 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function usableFields(fields) {
|
|
45
|
+
var out = [];
|
|
46
|
+
for (var i = 0; i < (fields || []).length; i++) {
|
|
47
|
+
var field = fields[i];
|
|
48
|
+
if (!field || typeof field.text !== "string" || !field.text) continue;
|
|
49
|
+
out.push({ text: field.text, weight: normalizeWeight(field.weight) });
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Kept for callers and tests that want the single-document form. Anchors and
|
|
55
|
+
// short bodies produce exactly this text.
|
|
56
|
+
function weightedText(fields) {
|
|
57
|
+
var parts = [];
|
|
58
|
+
var usable = usableFields(fields);
|
|
59
|
+
for (var i = 0; i < usable.length; i++) parts.push(repeat(usable[i].text, usable[i].weight));
|
|
60
|
+
return parts.join(" ");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var SEGMENT_STRIDE = SEGMENT_CHARS - SEGMENT_OVERLAP;
|
|
64
|
+
// The maximum searchable characters of one long field that can be indexed
|
|
65
|
+
// before the segment cap stops coverage. Reported to callers so an incomplete
|
|
66
|
+
// index is stated rather than implied.
|
|
67
|
+
var MAX_INDEXED_CHARS = SEGMENT_CHARS + (MAX_SEGMENTS_PER_ITEM - 1) * SEGMENT_STRIDE;
|
|
68
|
+
|
|
69
|
+
// One shared plan drives both the indexed documents and the coverage report,
|
|
70
|
+
// so what a caller is told was indexed is exactly what was indexed.
|
|
71
|
+
function planSegments(fields) {
|
|
72
|
+
var usable = usableFields(fields);
|
|
73
|
+
var anchors = [];
|
|
74
|
+
var longs = [];
|
|
75
|
+
for (var i = 0; i < usable.length; i++) {
|
|
76
|
+
if (usable[i].text.length > SEGMENT_CHARS) longs.push(usable[i]);
|
|
77
|
+
else anchors.push(usable[i]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var windows = [];
|
|
81
|
+
var totalChars = 0;
|
|
82
|
+
var indexedChars = 0;
|
|
83
|
+
for (var l = 0; l < longs.length; l++) {
|
|
84
|
+
var text = longs[l].text;
|
|
85
|
+
totalChars += text.length;
|
|
86
|
+
var covered = 0;
|
|
87
|
+
for (var offset = 0; offset < text.length && windows.length < MAX_SEGMENTS_PER_ITEM; offset += SEGMENT_STRIDE) {
|
|
88
|
+
var end = Math.min(text.length, offset + SEGMENT_CHARS);
|
|
89
|
+
windows.push({ field: longs[l], start: offset, end: end });
|
|
90
|
+
covered = end;
|
|
91
|
+
if (end >= text.length) break;
|
|
92
|
+
}
|
|
93
|
+
indexedChars += covered;
|
|
94
|
+
}
|
|
95
|
+
for (var a = 0; a < anchors.length; a++) {
|
|
96
|
+
totalChars += anchors[a].text.length;
|
|
97
|
+
indexedChars += anchors[a].text.length;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
anchors: anchors,
|
|
102
|
+
longs: longs,
|
|
103
|
+
windows: windows,
|
|
104
|
+
totalChars: totalChars,
|
|
105
|
+
indexedChars: indexedChars,
|
|
106
|
+
complete: indexedChars >= totalChars,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// How much of an item's searchable text this adapter can index. `complete` is
|
|
111
|
+
// false when the segment cap stopped short, which means a term appearing only
|
|
112
|
+
// in the uncovered tail cannot be ranked.
|
|
113
|
+
function coverage(fields) {
|
|
114
|
+
var plan = planSegments(fields);
|
|
115
|
+
return { totalChars: plan.totalChars, indexedChars: plan.indexedChars, complete: plan.complete };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// One item becomes one or more BM25 documents. Short fields anchor every
|
|
119
|
+
// segment so title and tag weighting is consistent wherever the body matched.
|
|
120
|
+
function segmentTexts(fields) {
|
|
121
|
+
var plan = planSegments(fields);
|
|
122
|
+
var anchorText = weightedText(plan.anchors);
|
|
123
|
+
if (plan.windows.length === 0) return [anchorText];
|
|
124
|
+
|
|
125
|
+
var texts = [];
|
|
126
|
+
for (var w = 0; w < plan.windows.length; w++) {
|
|
127
|
+
var window = plan.windows[w];
|
|
128
|
+
var body = repeat(window.field.text.substring(window.start, window.end), window.field.weight);
|
|
129
|
+
texts.push(anchorText ? anchorText + " " + body : body);
|
|
130
|
+
}
|
|
131
|
+
return texts;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Build the generic {id, text, meta} documents the shared engine expects.
|
|
135
|
+
// project(item, position) must return { id, fields, meta }. One item may yield
|
|
136
|
+
// several documents; each carries the item's position so results collapse back.
|
|
137
|
+
function buildDocs(items, project) {
|
|
138
|
+
var docs = [];
|
|
139
|
+
for (var i = 0; i < (items || []).length; i++) {
|
|
140
|
+
var projected = project(items[i], i);
|
|
141
|
+
if (!projected) continue;
|
|
142
|
+
var texts = segmentTexts(projected.fields);
|
|
143
|
+
for (var s = 0; s < texts.length; s++) {
|
|
144
|
+
docs.push({
|
|
145
|
+
id: projected.id,
|
|
146
|
+
text: texts[s],
|
|
147
|
+
meta: { position: i, segment: s, id: projected.id, meta: projected.meta },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return docs;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Rank with the shared engine. Returns [{ id, score, meta }] highest first, one
|
|
155
|
+
// entry per original item. A query that tokenizes to nothing scores nothing,
|
|
156
|
+
// rather than silently falling back to a different matching strategy.
|
|
157
|
+
//
|
|
158
|
+
// Aggregation across an item's segments is the maximum segment score: a record
|
|
159
|
+
// is as relevant as its best-matching part, so a long record is neither
|
|
160
|
+
// penalised for its unmatched bulk nor rewarded for repeating a term across
|
|
161
|
+
// windows that overlap.
|
|
162
|
+
function rank(items, query, project, maxResults) {
|
|
163
|
+
var docs = buildDocs(items, project);
|
|
164
|
+
if (docs.length === 0) return [];
|
|
165
|
+
var index = sessionSearch.buildIndex(docs);
|
|
166
|
+
var scored = sessionSearch.searchIndex(index, query, docs.length);
|
|
167
|
+
|
|
168
|
+
var bestByPosition = new Map();
|
|
169
|
+
for (var i = 0; i < scored.length; i++) {
|
|
170
|
+
var envelope = scored[i].meta;
|
|
171
|
+
if (!envelope) continue;
|
|
172
|
+
var existing = bestByPosition.get(envelope.position);
|
|
173
|
+
if (existing && existing.score >= scored[i].score) continue;
|
|
174
|
+
bestByPosition.set(envelope.position, { id: envelope.id, score: scored[i].score, meta: envelope.meta });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
var collapsed = [];
|
|
178
|
+
bestByPosition.forEach(function (entry) { collapsed.push(entry); });
|
|
179
|
+
// Stable, deterministic ordering before the caller applies its own
|
|
180
|
+
// recency and reference tie-breaks.
|
|
181
|
+
collapsed.sort(function (a, b) {
|
|
182
|
+
return b.score - a.score || compareIds(a.id, b.id);
|
|
183
|
+
});
|
|
184
|
+
var limit = maxResults || collapsed.length;
|
|
185
|
+
return collapsed.slice(0, limit);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Locale-independent lexical order over UTF-16 code units, for the opaque ids
|
|
189
|
+
// and refs that Knowledge uses as its deterministic tie-break.
|
|
190
|
+
//
|
|
191
|
+
// Those identifiers are base64url, so they contain both "-" (0x2D) and "_"
|
|
192
|
+
// (0x5F). String.prototype.localeCompare applies collation rules that order
|
|
193
|
+
// those two the other way round from code-unit order, and the result depends on
|
|
194
|
+
// the ICU data the runtime happens to carry, so it cannot express a
|
|
195
|
+
// deterministic order that holds across machines. This matches the default
|
|
196
|
+
// Array.prototype.sort ordering exactly.
|
|
197
|
+
function compareIds(a, b) {
|
|
198
|
+
var left = String(a);
|
|
199
|
+
var right = String(b);
|
|
200
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// A snippet centred on the first query term that appears in the text, so the
|
|
204
|
+
// excerpt shows why the document matched rather than just its opening. Works
|
|
205
|
+
// across the whole record, not only its first segment.
|
|
206
|
+
function snippet(text, query, maxChars) {
|
|
207
|
+
if (typeof text !== "string" || !text) return "";
|
|
208
|
+
var limit = maxChars || MAX_SNIPPET_CHARS;
|
|
209
|
+
var haystack = text.toLowerCase();
|
|
210
|
+
var tokens = sessionSearch.tokenize(query || "");
|
|
211
|
+
var best = -1;
|
|
212
|
+
for (var i = 0; i < tokens.length; i++) {
|
|
213
|
+
var at = haystack.indexOf(tokens[i]);
|
|
214
|
+
if (at !== -1 && (best === -1 || at < best)) best = at;
|
|
215
|
+
}
|
|
216
|
+
if (best === -1) {
|
|
217
|
+
var raw = (query || "").trim().toLowerCase();
|
|
218
|
+
if (raw) best = haystack.indexOf(raw);
|
|
219
|
+
}
|
|
220
|
+
var start = best === -1 ? 0 : Math.max(0, best - SNIPPET_LEAD_CHARS);
|
|
221
|
+
return text.substring(start, start + limit);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
MAX_SNIPPET_CHARS: MAX_SNIPPET_CHARS,
|
|
226
|
+
SEGMENT_CHARS: SEGMENT_CHARS,
|
|
227
|
+
SEGMENT_OVERLAP: SEGMENT_OVERLAP,
|
|
228
|
+
SEGMENT_STRIDE: SEGMENT_STRIDE,
|
|
229
|
+
MAX_SEGMENTS_PER_ITEM: MAX_SEGMENTS_PER_ITEM,
|
|
230
|
+
MAX_INDEXED_CHARS: MAX_INDEXED_CHARS,
|
|
231
|
+
tokenize: sessionSearch.tokenize,
|
|
232
|
+
weightedText: weightedText,
|
|
233
|
+
planSegments: planSegments,
|
|
234
|
+
coverage: coverage,
|
|
235
|
+
segmentTexts: segmentTexts,
|
|
236
|
+
buildDocs: buildDocs,
|
|
237
|
+
compareIds: compareIds,
|
|
238
|
+
rank: rank,
|
|
239
|
+
snippet: snippet,
|
|
240
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// SDK-free `clay-knowledge` MCP tool definitions for Mate Knowledge.
|
|
2
|
+
//
|
|
3
|
+
// Two disjoint tool sets. An ordinary Mate gets tools with no mateId, owner, or
|
|
4
|
+
// scope argument at all, because the binding decides the scope and a tool
|
|
5
|
+
// argument must never be able to widen it or reveal that other Mates exist.
|
|
6
|
+
// Authoritative builtin Clay gets same-user cross-Mate read tools that may name
|
|
7
|
+
// a mateId for list and search, while read resolves an opaque reference inside
|
|
8
|
+
// the authorized user's Mate scopes only.
|
|
9
|
+
|
|
10
|
+
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
11
|
+
var service = require("./mate-knowledge-service");
|
|
12
|
+
|
|
13
|
+
var MATE_CONTRACT =
|
|
14
|
+
"Your Knowledge is the durable personal and expertise context you own: what you have learned, the material you keep, and your accumulated observations. " +
|
|
15
|
+
"It is yours alone. There are no other Knowledge collections available to you here, and project Logs are a separate, unrelated surface. " +
|
|
16
|
+
"Search it when recalling something specific would genuinely change your answer, and read only the records you actually need. " +
|
|
17
|
+
"Do not enumerate or restate your Knowledge to the user unasked, and never reproduce whole records as filler.";
|
|
18
|
+
|
|
19
|
+
var CLAY_CONTRACT =
|
|
20
|
+
"You may read the Knowledge of Mates belonging to the current user in order to coordinate between them, and only for that user. " +
|
|
21
|
+
"Use it to find who holds relevant expertise or context, and to answer with that context attributed to the Mate that owns it. " +
|
|
22
|
+
"Search first, read narrowly, and cite the owning Mate. Never reproduce a Mate's Knowledge wholesale, and never present it as your own.";
|
|
23
|
+
|
|
24
|
+
var REF_DESCRIPTION = "Opaque knowledge reference returned by a list or search tool.";
|
|
25
|
+
var PAGE_DESCRIPTION = "Page size, from 1 to " + service.MAX_PAGE + ".";
|
|
26
|
+
var CURSOR_DESCRIPTION = "Opaque pagination cursor from a previous response.";
|
|
27
|
+
var OFFSET_DESCRIPTION = "Character offset to start from. Omit for the beginning; pass the previous response's nextOffset to continue.";
|
|
28
|
+
var MAX_CHARS_DESCRIPTION = "Characters to return, from 1 to " + service.MAX_READ_CHARS + ". Defaults to " + service.DEFAULT_READ_CHARS + ".";
|
|
29
|
+
var READ_RESULT = " The whole record is reassembled and verified before any slice is returned. The response carries offset, totalChars, nextOffset, and complete; when nextOffset is not null, call again with it to continue.";
|
|
30
|
+
|
|
31
|
+
function textResult(value) {
|
|
32
|
+
return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function errorResult(error) {
|
|
36
|
+
return Promise.resolve({
|
|
37
|
+
content: [{ type: "text", text: "Error: " + (error && error.message ? error.message : String(error)) }],
|
|
38
|
+
isError: true,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// An unbound descriptor exists only so a tool list can be advertised before a
|
|
43
|
+
// session is known. Every call against it fails closed.
|
|
44
|
+
function handler(bound, method) {
|
|
45
|
+
return function (args) {
|
|
46
|
+
if (!bound || typeof bound[method] !== "function") {
|
|
47
|
+
return errorResult(new Error("Knowledge tools require an exact session-bound Mate."));
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return textResult(bound[method](args || {}));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return errorResult(e);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function mateTools(bound) {
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
name: "list_knowledge",
|
|
61
|
+
description: MATE_CONTRACT + " List your own Knowledge records, most recently updated first. Returns summaries with a short preview, not full records.",
|
|
62
|
+
inputSchema: buildShape({
|
|
63
|
+
kind: { type: "string", description: "Optional record kind filter, such as knowledge-file, memory-summary, session-digest, or user-observation." },
|
|
64
|
+
cursor: { type: "string", description: CURSOR_DESCRIPTION },
|
|
65
|
+
limit: { type: "number", description: PAGE_DESCRIPTION },
|
|
66
|
+
}),
|
|
67
|
+
handler: handler(bound, "listKnowledge"),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "search_knowledge",
|
|
71
|
+
description: MATE_CONTRACT + " Search your own Knowledge by relevance. Returns ranked summaries with a matching snippet.",
|
|
72
|
+
inputSchema: buildShape({
|
|
73
|
+
query: { type: "string", description: "Search query." },
|
|
74
|
+
kind: { type: "string", description: "Optional record kind filter." },
|
|
75
|
+
cursor: { type: "string", description: CURSOR_DESCRIPTION },
|
|
76
|
+
limit: { type: "number", description: PAGE_DESCRIPTION },
|
|
77
|
+
}, ["query"]),
|
|
78
|
+
handler: handler(bound, "searchKnowledge"),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "read_knowledge",
|
|
82
|
+
description: MATE_CONTRACT + " Read one of your own Knowledge records. Read only what you need." + READ_RESULT,
|
|
83
|
+
inputSchema: buildShape({
|
|
84
|
+
ref: { type: "string", description: REF_DESCRIPTION },
|
|
85
|
+
offset: { type: "number", description: OFFSET_DESCRIPTION },
|
|
86
|
+
maxChars: { type: "number", description: MAX_CHARS_DESCRIPTION },
|
|
87
|
+
}, ["ref"]),
|
|
88
|
+
handler: handler(bound, "readKnowledge"),
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function clayTools(bound) {
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
name: "list_mate_knowledge",
|
|
97
|
+
description: CLAY_CONTRACT + " List Knowledge records across the current user's Mates, or one named Mate. Available only to authoritative builtin Clay.",
|
|
98
|
+
inputSchema: buildShape({
|
|
99
|
+
mateId: { type: "string", description: "Optional exact Mate id belonging to the current user. Omit to span all of them." },
|
|
100
|
+
kind: { type: "string", description: "Optional record kind filter." },
|
|
101
|
+
cursor: { type: "string", description: CURSOR_DESCRIPTION },
|
|
102
|
+
limit: { type: "number", description: PAGE_DESCRIPTION },
|
|
103
|
+
}),
|
|
104
|
+
handler: handler(bound, "listMateKnowledge"),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "search_mate_knowledge",
|
|
108
|
+
description: CLAY_CONTRACT + " Search Knowledge across the current user's Mates, or one named Mate. Results identify the owning Mate. Available only to authoritative builtin Clay.",
|
|
109
|
+
inputSchema: buildShape({
|
|
110
|
+
query: { type: "string", description: "Search query." },
|
|
111
|
+
mateId: { type: "string", description: "Optional exact Mate id belonging to the current user." },
|
|
112
|
+
kind: { type: "string", description: "Optional record kind filter." },
|
|
113
|
+
cursor: { type: "string", description: CURSOR_DESCRIPTION },
|
|
114
|
+
limit: { type: "number", description: PAGE_DESCRIPTION },
|
|
115
|
+
}, ["query"]),
|
|
116
|
+
handler: handler(bound, "searchMateKnowledge"),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "read_mate_knowledge",
|
|
120
|
+
description: CLAY_CONTRACT + " Read one Knowledge record using the opaque reference returned by a list or search tool. The reference resolves only inside the current user's Mate Knowledge. Available only to authoritative builtin Clay." + READ_RESULT,
|
|
121
|
+
inputSchema: buildShape({
|
|
122
|
+
ref: { type: "string", description: REF_DESCRIPTION },
|
|
123
|
+
offset: { type: "number", description: OFFSET_DESCRIPTION },
|
|
124
|
+
maxChars: { type: "number", description: MAX_CHARS_DESCRIPTION },
|
|
125
|
+
}, ["ref"]),
|
|
126
|
+
handler: handler(bound, "readMateKnowledge"),
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// A binding is either one Mate's own scope or Clay's cross-Mate read view. The
|
|
132
|
+
// two sets are never advertised together, so no tool name is duplicated.
|
|
133
|
+
function getToolDefs(bound, includeClay) {
|
|
134
|
+
return includeClay === true ? clayTools(bound) : mateTools(bound);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function createMcpServer(adapter, bound, includeClay) {
|
|
138
|
+
if (!adapter || typeof adapter.createToolServer !== "function") return null;
|
|
139
|
+
return adapter.createToolServer({
|
|
140
|
+
name: "clay-knowledge",
|
|
141
|
+
version: "1.0.0",
|
|
142
|
+
tools: getToolDefs(bound, includeClay),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
MATE_CONTRACT: MATE_CONTRACT,
|
|
148
|
+
CLAY_CONTRACT: CLAY_CONTRACT,
|
|
149
|
+
getToolDefs: getToolDefs,
|
|
150
|
+
createMcpServer: createMcpServer,
|
|
151
|
+
};
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// Automatic upgrade migration: legacy Mate memory and knowledge files into the
|
|
2
|
+
// Clay-wide append-only Knowledge backend.
|
|
3
|
+
//
|
|
4
|
+
// This module owns discovery, the run lock, and versioned state. The actual
|
|
5
|
+
// source-to-record work lives in mate-knowledge-sync.js, which is the same code
|
|
6
|
+
// the live write-through bridge uses, so a startup import and a post-mutation
|
|
7
|
+
// mirror can never disagree about how a source is represented.
|
|
8
|
+
//
|
|
9
|
+
// Properties this migration guarantees:
|
|
10
|
+
// - Non-destructive. Legacy files are read and never written, renamed, or
|
|
11
|
+
// removed. They stay the live source for the existing read paths.
|
|
12
|
+
// - Idempotent. Every source unit carries a stable import key, so a restart,
|
|
13
|
+
// a partial run, or a deleted state file re-imports nothing.
|
|
14
|
+
// - Overlap-safe. A run lock keeps two daemons from importing at once, and
|
|
15
|
+
// the importer folds away a duplicate even if the lock is bypassed.
|
|
16
|
+
// - Torn-input tolerant. An unparseable journal line is counted and skipped;
|
|
17
|
+
// it never aborts the run.
|
|
18
|
+
// - Honest. A source that cannot be represented is reported as a failure
|
|
19
|
+
// rather than silently truncated or skipped.
|
|
20
|
+
// - Isolated. Each user's Mates land in their own scope, derived from the
|
|
21
|
+
// Mate storage root that already separates users on disk.
|
|
22
|
+
|
|
23
|
+
var fs = require("fs");
|
|
24
|
+
var path = require("path");
|
|
25
|
+
var config = require("./config");
|
|
26
|
+
var recordStore = require("./knowledge-record-store");
|
|
27
|
+
var mateSync = require("./mate-knowledge-sync");
|
|
28
|
+
|
|
29
|
+
var MIGRATION_NAME = "mate-knowledge";
|
|
30
|
+
var MIGRATION_VERSION = 2;
|
|
31
|
+
var LOCK_STALE_MS = 10 * 60 * 1000;
|
|
32
|
+
|
|
33
|
+
function knowledgeRoot() {
|
|
34
|
+
return recordStore.knowledgeRoot();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function statePath(baseDir) {
|
|
38
|
+
return path.join(baseDir || knowledgeRoot(), "migration-state.json");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function lockPath(baseDir) {
|
|
42
|
+
return path.join(baseDir || knowledgeRoot(), ".mate-migration.lock");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isMateDirName(name) {
|
|
46
|
+
return typeof name === "string" && name.indexOf("mate_") === 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function safeReadDir(dir) {
|
|
50
|
+
try {
|
|
51
|
+
return fs.readdirSync(dir, { withFileTypes: true });
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function mateNames(matesRoot) {
|
|
58
|
+
var names = {};
|
|
59
|
+
try {
|
|
60
|
+
var parsed = JSON.parse(fs.readFileSync(path.join(matesRoot, "mates.json"), "utf8"));
|
|
61
|
+
var list = (parsed && parsed.mates) || [];
|
|
62
|
+
for (var i = 0; i < list.length; i++) {
|
|
63
|
+
if (list[i] && list[i].id) names[list[i].id] = list[i].name || null;
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// A missing or corrupt registry only costs provenance detail.
|
|
67
|
+
}
|
|
68
|
+
return names;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function collectFromRoot(matesRoot, out) {
|
|
72
|
+
var names = mateNames(matesRoot);
|
|
73
|
+
var entries = safeReadDir(matesRoot);
|
|
74
|
+
for (var i = 0; i < entries.length; i++) {
|
|
75
|
+
if (!entries[i].isDirectory() || !isMateDirName(entries[i].name)) continue;
|
|
76
|
+
var scope = mateSync.scopeForMateDir(path.join(matesRoot, entries[i].name));
|
|
77
|
+
if (!scope) continue;
|
|
78
|
+
scope.mateName = names[entries[i].name] || null;
|
|
79
|
+
out.push(scope);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Both storage layouts are walked directly rather than through the Mate
|
|
84
|
+
// registry, so a Mate whose registry entry is missing or corrupt still gets
|
|
85
|
+
// its durable content migrated.
|
|
86
|
+
// single-user: {CONFIG_DIR}/mates/mate_*
|
|
87
|
+
// multi-user: {CONFIG_DIR}/mates/{userId}/mate_*
|
|
88
|
+
// OS users: roots supplied by the caller
|
|
89
|
+
function discoverMates(opts) {
|
|
90
|
+
var options = opts || {};
|
|
91
|
+
var out = [];
|
|
92
|
+
var matesHome = options.matesHome || path.join(config.CONFIG_DIR, "mates");
|
|
93
|
+
collectFromRoot(matesHome, out);
|
|
94
|
+
|
|
95
|
+
var entries = safeReadDir(matesHome);
|
|
96
|
+
for (var i = 0; i < entries.length; i++) {
|
|
97
|
+
if (!entries[i].isDirectory() || isMateDirName(entries[i].name)) continue;
|
|
98
|
+
collectFromRoot(path.join(matesHome, entries[i].name), out);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
var roots = options.extraRoots || [];
|
|
102
|
+
for (var r = 0; r < roots.length; r++) {
|
|
103
|
+
if (!roots[r] || !roots[r].matesRoot) continue;
|
|
104
|
+
collectFromRoot(roots[r].matesRoot, out);
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- Versioned state -------------------------------------------------
|
|
110
|
+
|
|
111
|
+
// State is a fast-path hint and an audit trail, never the authority. Deleting
|
|
112
|
+
// it makes the next run re-scan and import nothing, because the import keys
|
|
113
|
+
// already present in the records are what actually decide.
|
|
114
|
+
function loadState(baseDir) {
|
|
115
|
+
try {
|
|
116
|
+
var parsed = JSON.parse(fs.readFileSync(statePath(baseDir), "utf8"));
|
|
117
|
+
if (parsed && parsed.version === MIGRATION_VERSION && parsed.scopes) return parsed;
|
|
118
|
+
} catch (e) {}
|
|
119
|
+
return { version: MIGRATION_VERSION, migration: MIGRATION_NAME, scopes: {}, lastRunAt: 0 };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function saveState(baseDir, state) {
|
|
123
|
+
var target = statePath(baseDir);
|
|
124
|
+
try {
|
|
125
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
126
|
+
var temporary = target + ".tmp." + process.pid;
|
|
127
|
+
fs.writeFileSync(temporary, JSON.stringify(state, null, 2) + "\n");
|
|
128
|
+
config.chmodSafe(temporary, 0o600);
|
|
129
|
+
fs.renameSync(temporary, target);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// A migration that cannot record state still completed its imports.
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Run lock ---------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
function writeLock(target) {
|
|
138
|
+
var payload = JSON.stringify({ pid: process.pid, startedAt: Date.now() }) + "\n";
|
|
139
|
+
var fd = fs.openSync(target, "wx");
|
|
140
|
+
try {
|
|
141
|
+
fs.writeSync(fd, Buffer.from(payload, "utf8"));
|
|
142
|
+
} finally {
|
|
143
|
+
fs.closeSync(fd);
|
|
144
|
+
}
|
|
145
|
+
return target;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// A live holder always wins, no matter how long it has been running. Stealing
|
|
149
|
+
// a lock from a running daemon would let two daemons import at once and would
|
|
150
|
+
// let the first one unlink the second one's lock on completion. Age alone can
|
|
151
|
+
// only reclaim a lock whose holder is gone or unidentifiable.
|
|
152
|
+
function acquireLock(baseDir) {
|
|
153
|
+
var target = lockPath(baseDir);
|
|
154
|
+
try {
|
|
155
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
156
|
+
} catch (e) {}
|
|
157
|
+
try {
|
|
158
|
+
return writeLock(target);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
if (!e || e.code !== "EEXIST") return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
var holder = null;
|
|
164
|
+
try {
|
|
165
|
+
holder = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
166
|
+
} catch (e2) {}
|
|
167
|
+
|
|
168
|
+
var pid = holder && holder.pid;
|
|
169
|
+
if (pid && config.isPidAlive(pid)) return null;
|
|
170
|
+
|
|
171
|
+
// No identifiable holder: only reclaim once it is unambiguously abandoned.
|
|
172
|
+
if (!pid) {
|
|
173
|
+
var age = Date.now() - ((holder && holder.startedAt) || 0);
|
|
174
|
+
if (age < LOCK_STALE_MS) return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
fs.unlinkSync(target);
|
|
179
|
+
return writeLock(target);
|
|
180
|
+
} catch (e3) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function releaseLock(target) {
|
|
186
|
+
if (!target) return;
|
|
187
|
+
try { fs.unlinkSync(target); } catch (e) {}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- Entry point -------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
function run(opts) {
|
|
193
|
+
var options = opts || {};
|
|
194
|
+
var baseDir = options.baseDir || knowledgeRoot();
|
|
195
|
+
var summary = mateSync.newSummary();
|
|
196
|
+
summary.migration = MIGRATION_NAME;
|
|
197
|
+
summary.version = MIGRATION_VERSION;
|
|
198
|
+
summary.scopes = 0;
|
|
199
|
+
|
|
200
|
+
var lock = acquireLock(baseDir);
|
|
201
|
+
if (!lock) {
|
|
202
|
+
summary.skipped = "locked";
|
|
203
|
+
return summary;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
var mates = discoverMates(options);
|
|
207
|
+
var state = loadState(baseDir);
|
|
208
|
+
for (var i = 0; i < mates.length; i++) {
|
|
209
|
+
var before = summary.failed;
|
|
210
|
+
try {
|
|
211
|
+
mateSync.reconcileMate({
|
|
212
|
+
scope: mates[i],
|
|
213
|
+
mateName: mates[i].mateName,
|
|
214
|
+
baseDir: baseDir,
|
|
215
|
+
actor: { type: "migration" },
|
|
216
|
+
summary: summary,
|
|
217
|
+
});
|
|
218
|
+
} catch (e) {
|
|
219
|
+
summary.failed++;
|
|
220
|
+
summary.errors.push({ scopeId: mates[i].scopeId, message: e.message });
|
|
221
|
+
}
|
|
222
|
+
summary.scopes++;
|
|
223
|
+
state.scopes[mates[i].scopeId] = {
|
|
224
|
+
version: MIGRATION_VERSION,
|
|
225
|
+
mateId: mates[i].mateId,
|
|
226
|
+
userId: mates[i].userId,
|
|
227
|
+
completedAt: Date.now(),
|
|
228
|
+
// A scope with a failure is recorded as incomplete so it is never
|
|
229
|
+
// reported as migrated while a source is missing content.
|
|
230
|
+
complete: summary.failed === before,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
state.lastRunAt = Date.now();
|
|
234
|
+
saveState(baseDir, state);
|
|
235
|
+
return summary;
|
|
236
|
+
} finally {
|
|
237
|
+
releaseLock(lock);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Startup wrapper: never throws, never blocks boot, logs only when it did work
|
|
242
|
+
// or when a source could not be represented.
|
|
243
|
+
function runAtStartup(opts) {
|
|
244
|
+
var summary;
|
|
245
|
+
try {
|
|
246
|
+
summary = run(opts);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
console.error("[knowledge-migration] Mate knowledge migration failed:", e.message);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
if (summary.skipped === "locked") return summary;
|
|
252
|
+
if (summary.created > 0 || summary.revised > 0 || summary.deleted > 0) {
|
|
253
|
+
console.log("[knowledge-migration] Mate knowledge: " + summary.created + " imported, " +
|
|
254
|
+
summary.revised + " revised, " + summary.deleted + " tombstoned, " +
|
|
255
|
+
summary.unchanged + " unchanged across " + summary.scopes + " scope(s)");
|
|
256
|
+
}
|
|
257
|
+
if (summary.failed > 0) {
|
|
258
|
+
console.error("[knowledge-migration] " + summary.failed + " source(s) could not be migrated; " +
|
|
259
|
+
"legacy files remain authoritative. First error: " + (summary.errors[0] && summary.errors[0].message));
|
|
260
|
+
}
|
|
261
|
+
return summary;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = {
|
|
265
|
+
MIGRATION_NAME: MIGRATION_NAME,
|
|
266
|
+
MIGRATION_VERSION: MIGRATION_VERSION,
|
|
267
|
+
LOCK_STALE_MS: LOCK_STALE_MS,
|
|
268
|
+
IDENTITY_FILES: mateSync.IDENTITY_FILES,
|
|
269
|
+
EXCLUDED_FILES: mateSync.EXCLUDED_FILES,
|
|
270
|
+
JOURNAL_KINDS: mateSync.JOURNAL_KINDS,
|
|
271
|
+
ownerKeyForRoot: mateSync.ownerKeyForRoot,
|
|
272
|
+
scopeIdFor: mateSync.scopeIdFor,
|
|
273
|
+
scopeForMateDir: mateSync.scopeForMateDir,
|
|
274
|
+
sourceKindFor: mateSync.sourceKindFor,
|
|
275
|
+
discoverMates: discoverMates,
|
|
276
|
+
statePath: statePath,
|
|
277
|
+
lockPath: lockPath,
|
|
278
|
+
loadState: loadState,
|
|
279
|
+
acquireLock: acquireLock,
|
|
280
|
+
releaseLock: releaseLock,
|
|
281
|
+
run: run,
|
|
282
|
+
runAtStartup: runAtStartup,
|
|
283
|
+
};
|