@mastra/code-sdk 1.17.0 → 1.18.0-alpha.1
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/dist/agents/instructions.d.ts +2 -10
- package/dist/agents/instructions.d.ts.map +1 -1
- package/dist/agents/instructions.js +2 -4
- package/dist/agents/instructions.js.map +1 -1
- package/dist/agents/memory.d.ts +1 -17
- package/dist/agents/memory.d.ts.map +1 -1
- package/dist/agents/memory.js +6 -55
- package/dist/agents/memory.js.map +1 -1
- package/dist/agents/prompts/index.d.ts +0 -2
- package/dist/agents/prompts/index.d.ts.map +1 -1
- package/dist/agents/prompts/index.js +0 -1
- package/dist/agents/prompts/index.js.map +1 -1
- package/dist/agents/prompts/tool-guidance.d.ts +0 -2
- package/dist/agents/prompts/tool-guidance.d.ts.map +1 -1
- package/dist/agents/prompts/tool-guidance.js +0 -30
- package/dist/agents/prompts/tool-guidance.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -16
- package/dist/index.js.map +1 -1
- package/dist/knowledge-inspector.d.ts +26 -10
- package/dist/knowledge-inspector.d.ts.map +1 -1
- package/dist/knowledge-inspector.js +266 -241
- package/dist/knowledge-inspector.js.map +1 -1
- package/dist/knowledge-scope.d.ts +21 -23
- package/dist/knowledge-scope.d.ts.map +1 -1
- package/dist/knowledge-scope.js +49 -33
- package/dist/knowledge-scope.js.map +1 -1
- package/dist/mcp/manager.d.ts +3 -1
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +139 -104
- package/dist/mcp/manager.js.map +1 -1
- package/dist/onboarding/settings.d.ts +2 -0
- package/dist/onboarding/settings.d.ts.map +1 -1
- package/dist/onboarding/settings.js +3 -1
- package/dist/onboarding/settings.js.map +1 -1
- package/package.json +9 -9
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { knowledgeSessionTags, resolveKnowledgeSessionScopes } from "./knowledge-scope.js";
|
|
2
|
+
import { knowledgeScopeAddress } from "@mastra/core/knowledge";
|
|
3
|
+
import { parseKnowledgeWikilinks } from "@mastra/core/storage";
|
|
3
4
|
// src/knowledge-inspector.ts
|
|
4
5
|
var KnowledgeInspectorError = class extends Error {
|
|
5
6
|
code;
|
|
@@ -11,17 +12,16 @@ var KnowledgeInspectorError = class extends Error {
|
|
|
11
12
|
};
|
|
12
13
|
const HANDLE_TTL_MS = 3e5;
|
|
13
14
|
const MAX_OPAQUE_ENTRIES = 1e3;
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
15
|
+
const DEFAULT_PAGE_LIMIT = 50;
|
|
16
|
+
const MAX_PAGE_LIMIT = 50;
|
|
17
|
+
const DEFAULT_NOTE_LIMIT = 25;
|
|
18
|
+
const MAX_NOTE_LIMIT = 100;
|
|
18
19
|
const DEFAULT_ACTIVITY_LIMIT = 20;
|
|
19
20
|
const MAX_ACTIVITY_LIMIT = 100;
|
|
20
|
-
const
|
|
21
|
+
const MAX_RELATED_PAGES = 25;
|
|
21
22
|
const MAX_RANK_CANDIDATES = 50;
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const MAX_NODE_CONTENT_BYTES = 32768;
|
|
23
|
+
const MAX_RANK_NOTES = 100;
|
|
24
|
+
const MAX_BODY_BYTES = 32768;
|
|
25
25
|
function opaqueToken() {
|
|
26
26
|
return Buffer.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(24))).toString("base64url");
|
|
27
27
|
}
|
|
@@ -30,24 +30,22 @@ function boundedLimit(value, fallback, maximum) {
|
|
|
30
30
|
if (!Number.isInteger(value) || value < 1) return fallback;
|
|
31
31
|
return Math.min(value, maximum);
|
|
32
32
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
level: entry.slice(0, separator),
|
|
39
|
-
id: entry.slice(separator + 1)
|
|
33
|
+
function badgeOf(address) {
|
|
34
|
+
if (!address) return {
|
|
35
|
+
level: "unknown",
|
|
36
|
+
id: ""
|
|
40
37
|
};
|
|
41
|
-
|
|
42
|
-
function knowledgeSummary(record) {
|
|
38
|
+
const separator = address.indexOf(":");
|
|
43
39
|
return {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
sourceThreadId: record.sourceThreadId,
|
|
47
|
-
capturedAt: record.capturedAt.toISOString(),
|
|
48
|
-
when: record.when?.toISOString()
|
|
40
|
+
level: address.slice(0, separator),
|
|
41
|
+
id: address.slice(separator + 1)
|
|
49
42
|
};
|
|
50
43
|
}
|
|
44
|
+
/** A session tag in Knowledge's canonical form; every type the session holds has one `$param`. */
|
|
45
|
+
function canonical(tag) {
|
|
46
|
+
const separator = tag.indexOf(":");
|
|
47
|
+
return knowledgeScopeAddress(`${tag.slice(0, separator)}:$value`, { value: tag.slice(separator + 1) });
|
|
48
|
+
}
|
|
51
49
|
function truncateUtf8(value, maxBytes) {
|
|
52
50
|
const encoded = Buffer.from(value);
|
|
53
51
|
if (encoded.byteLength <= maxBytes) return {
|
|
@@ -62,18 +60,28 @@ function truncateUtf8(value, maxBytes) {
|
|
|
62
60
|
truncated: true
|
|
63
61
|
};
|
|
64
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* The /knowledge inspector: reads the pages and notes a session's code agent can read, one level at a
|
|
65
|
+
* time (the session's shared tags, then its project resource, then its thread), through the same
|
|
66
|
+
* Knowledge access rules the agent's runs use.
|
|
67
|
+
*/
|
|
65
68
|
var ScopedKnowledgeInspector = class {
|
|
66
69
|
#knowledge;
|
|
70
|
+
#store;
|
|
67
71
|
#session;
|
|
68
72
|
#handles = /* @__PURE__ */ new Map();
|
|
69
73
|
#cursors = /* @__PURE__ */ new Map();
|
|
74
|
+
#scopeAddresses = /* @__PURE__ */ new Map();
|
|
70
75
|
#fingerprint;
|
|
71
76
|
#identityKey = opaqueToken();
|
|
77
|
+
#hostScopes;
|
|
72
78
|
constructor(input) {
|
|
73
79
|
this.#knowledge = input.knowledge;
|
|
80
|
+
this.#store = input.store;
|
|
74
81
|
this.#session = input.session;
|
|
82
|
+
this.#hostScopes = input.hostScopes ?? [];
|
|
75
83
|
this.#session.subscribe((event) => {
|
|
76
|
-
if (event.type === "thread_changed" || event.type === "thread_created" || event.type === "thread_deleted") this.#
|
|
84
|
+
if (event.type === "thread_changed" || event.type === "thread_created" || event.type === "thread_deleted") this.#invalidate();
|
|
77
85
|
});
|
|
78
86
|
}
|
|
79
87
|
async getScopeTree() {
|
|
@@ -84,7 +92,7 @@ var ScopedKnowledgeInspector = class {
|
|
|
84
92
|
roots: [
|
|
85
93
|
{
|
|
86
94
|
level: "org",
|
|
87
|
-
id: binding.
|
|
95
|
+
id: binding.sessionTags.join(" + "),
|
|
88
96
|
available: true
|
|
89
97
|
},
|
|
90
98
|
{
|
|
@@ -106,57 +114,46 @@ var ScopedKnowledgeInspector = class {
|
|
|
106
114
|
}
|
|
107
115
|
async listNodes(input) {
|
|
108
116
|
const binding = await this.#binding();
|
|
109
|
-
const
|
|
110
|
-
const limit = boundedLimit(input.limit,
|
|
111
|
-
const sort = input.sort ?? "
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
117
|
+
const view = await this.#view(binding, input.level);
|
|
118
|
+
const limit = boundedLimit(input.limit, DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT);
|
|
119
|
+
const sort = input.sort ?? "name";
|
|
120
|
+
const filters = {
|
|
121
|
+
namePrefix: input.namePrefix,
|
|
122
|
+
sort
|
|
123
|
+
};
|
|
124
|
+
if (sort === "name") {
|
|
125
|
+
const cursor = this.#consumeCursor(input.cursor, binding, input.level, "node", filters);
|
|
126
|
+
const result = await this.#store.listPages({
|
|
127
|
+
read: view.read,
|
|
120
128
|
namePrefix: input.namePrefix,
|
|
121
|
-
kind: input.kind,
|
|
122
129
|
cursor,
|
|
123
130
|
limit
|
|
124
131
|
});
|
|
125
|
-
const nodes = await Promise.all(
|
|
126
|
-
...this.#
|
|
127
|
-
relationshipCounts:
|
|
132
|
+
const nodes = await Promise.all(result.pages.map(async (page) => ({
|
|
133
|
+
...await this.#summary(page, binding, view),
|
|
134
|
+
relationshipCounts: await this.#counts(page, view)
|
|
128
135
|
})));
|
|
129
136
|
await this.#assertStable(binding);
|
|
130
137
|
return {
|
|
131
138
|
identityKey: binding.identityKey,
|
|
132
139
|
scopeLevel: input.level,
|
|
133
140
|
nodes,
|
|
134
|
-
nextCursor:
|
|
135
|
-
namePrefix: input.namePrefix,
|
|
136
|
-
kind: input.kind
|
|
137
|
-
}), {
|
|
138
|
-
namePrefix: input.namePrefix,
|
|
139
|
-
kind: input.kind,
|
|
140
|
-
sort
|
|
141
|
-
}) : void 0,
|
|
141
|
+
nextCursor: result.nextCursor ? this.#mintCursor(binding, input.level, "node", result.nextCursor, filters) : void 0,
|
|
142
142
|
sort,
|
|
143
143
|
coverage: "exact"
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
kind: input.kind,
|
|
149
|
-
sort
|
|
150
|
-
};
|
|
151
|
-
const encodedSnapshot = this.#consumeCursor(input.cursor, binding, input.level, "ranked-node", filters);
|
|
152
|
-
const snapshot = encodedSnapshot ? JSON.parse(encodedSnapshot) : await this.#rankedNodeSnapshot(scope, input.namePrefix, input.kind, sort);
|
|
153
|
-
const page = snapshot.entries.slice(snapshot.offset, snapshot.offset + limit);
|
|
146
|
+
const encoded = this.#consumeCursor(input.cursor, binding, input.level, "ranked-node", filters);
|
|
147
|
+
const snapshot = encoded ? JSON.parse(encoded) : await this.#ranked(view, input.namePrefix);
|
|
154
148
|
const nodes = [];
|
|
155
|
-
for (const entry of
|
|
156
|
-
const
|
|
157
|
-
|
|
149
|
+
for (const entry of snapshot.entries.slice(snapshot.offset, snapshot.offset + limit)) {
|
|
150
|
+
const page = await this.#store.getPage({
|
|
151
|
+
id: entry.id,
|
|
152
|
+
read: view.read
|
|
153
|
+
});
|
|
154
|
+
if (!page || page.deletedAt) continue;
|
|
158
155
|
nodes.push({
|
|
159
|
-
...this.#
|
|
156
|
+
...await this.#summary(page, binding, view),
|
|
160
157
|
relationshipCounts: entry.counts
|
|
161
158
|
});
|
|
162
159
|
}
|
|
@@ -171,116 +168,111 @@ var ScopedKnowledgeInspector = class {
|
|
|
171
168
|
offset: nextOffset
|
|
172
169
|
}), filters) : void 0,
|
|
173
170
|
sort,
|
|
174
|
-
coverage: "
|
|
171
|
+
coverage: "window"
|
|
175
172
|
};
|
|
176
173
|
}
|
|
177
174
|
async getNode(input) {
|
|
178
175
|
const binding = await this.#binding();
|
|
179
176
|
const handle = this.#readHandle(input.handle, binding, "node");
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
const view = await this.#view(binding, handle.level);
|
|
178
|
+
const page = await this.#store.getPage({
|
|
179
|
+
id: handle.recordId,
|
|
180
|
+
read: view.read
|
|
181
|
+
});
|
|
182
|
+
if (!page || page.deletedAt) throw new KnowledgeInspectorError("not-visible", "Knowledge page is not visible in the selected scope.");
|
|
183
|
+
const limit = boundedLimit(input.recordLimit, DEFAULT_NOTE_LIMIT, MAX_NOTE_LIMIT);
|
|
184
|
+
const notesAfter = this.#consumeCursor(input.recordsCursor, binding, handle.level, "records");
|
|
185
185
|
const mentioningAfter = this.#consumeCursor(input.mentioningRecordsCursor, binding, handle.level, "mentioning-records");
|
|
186
|
-
const [
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
const [notes, mentioning] = await Promise.all([this.#store.listNotes({
|
|
187
|
+
pageId: page.id,
|
|
188
|
+
read: view.read,
|
|
189
|
+
cursor: notesAfter,
|
|
190
190
|
limit
|
|
191
|
-
}), this.#
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
}), this.#store.listNotesMentioning({
|
|
192
|
+
pageId: page.id,
|
|
193
|
+
read: view.read,
|
|
194
|
+
cursor: mentioningAfter,
|
|
195
195
|
limit
|
|
196
196
|
})]);
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
this.#
|
|
201
|
-
this.#
|
|
202
|
-
this.#sampledRelationshipCounts(node, scope)
|
|
197
|
+
const body = truncateUtf8(page.body ?? "", MAX_BODY_BYTES);
|
|
198
|
+
const [outgoing, incoming, counts] = await Promise.all([
|
|
199
|
+
this.#outgoing(page, notes.notes, view),
|
|
200
|
+
this.#incoming(mentioning.notes, view),
|
|
201
|
+
this.#counts(page, view)
|
|
203
202
|
]);
|
|
204
|
-
const links = await Promise.all(parseKnowledgeWikilinks(
|
|
205
|
-
const target = await this.#
|
|
203
|
+
const links = await Promise.all(parseKnowledgeWikilinks(body.value).slice(0, MAX_RELATED_PAGES).map(async (label) => {
|
|
204
|
+
const [target] = await this.#store.findPagesByName({
|
|
206
205
|
name: label,
|
|
207
|
-
|
|
206
|
+
read: view.read,
|
|
207
|
+
limit: 1
|
|
208
208
|
});
|
|
209
209
|
return {
|
|
210
210
|
label,
|
|
211
|
-
node: target ? this.#
|
|
211
|
+
node: target ? await this.#summary(target, binding, view) : void 0
|
|
212
212
|
};
|
|
213
213
|
}));
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
const summaries = (pages) => Promise.all(pages.map((related) => this.#summary(related, binding, view)));
|
|
215
|
+
const detail = {
|
|
216
216
|
identityKey: binding.identityKey,
|
|
217
217
|
scopeLevel: handle.level,
|
|
218
218
|
node: {
|
|
219
|
-
...this.#
|
|
220
|
-
relationshipCounts:
|
|
219
|
+
...await this.#summary(page, binding, view),
|
|
220
|
+
relationshipCounts: counts
|
|
221
221
|
},
|
|
222
|
-
records:
|
|
223
|
-
recordsNextCursor:
|
|
224
|
-
mentioningRecords:
|
|
225
|
-
mentioningRecordsNextCursor:
|
|
222
|
+
records: await Promise.all(notes.notes.map((note) => this.#noteSummary(note))),
|
|
223
|
+
recordsNextCursor: notes.nextCursor ? this.#mintCursor(binding, handle.level, "records", notes.nextCursor) : void 0,
|
|
224
|
+
mentioningRecords: await Promise.all(mentioning.notes.map((note) => this.#noteSummary(note))),
|
|
225
|
+
mentioningRecordsNextCursor: mentioning.nextCursor ? this.#mintCursor(binding, handle.level, "mentioning-records", mentioning.nextCursor) : void 0,
|
|
226
226
|
outgoingTargets: {
|
|
227
|
-
nodes:
|
|
228
|
-
partial:
|
|
227
|
+
nodes: await summaries(outgoing.pages),
|
|
228
|
+
partial: outgoing.truncated || Boolean(notes.nextCursor)
|
|
229
229
|
},
|
|
230
230
|
incomingParents: {
|
|
231
|
-
nodes:
|
|
232
|
-
partial:
|
|
231
|
+
nodes: await summaries(incoming.pages),
|
|
232
|
+
partial: incoming.truncated || Boolean(mentioning.nextCursor)
|
|
233
233
|
},
|
|
234
|
-
relationshipCounts:
|
|
235
|
-
content:
|
|
236
|
-
contentTruncated:
|
|
234
|
+
relationshipCounts: counts,
|
|
235
|
+
content: page.body === void 0 ? void 0 : body.value,
|
|
236
|
+
contentTruncated: body.truncated,
|
|
237
237
|
links
|
|
238
238
|
};
|
|
239
|
+
await this.#assertStable(binding);
|
|
240
|
+
return detail;
|
|
239
241
|
}
|
|
240
242
|
async listActivity(input) {
|
|
241
243
|
const binding = await this.#binding();
|
|
242
|
-
const
|
|
244
|
+
const view = await this.#view(binding, input.level);
|
|
243
245
|
const limit = boundedLimit(input.limit, DEFAULT_ACTIVITY_LIMIT, MAX_ACTIVITY_LIMIT);
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
const cursor = this.#consumeCursor(input.cursor, binding, input.level, "activity");
|
|
247
|
+
const result = await this.#store.listPageActivity({
|
|
248
|
+
read: view.read,
|
|
249
|
+
cursor,
|
|
248
250
|
limit
|
|
249
251
|
});
|
|
250
|
-
const
|
|
251
|
-
for (const event of events)
|
|
252
|
-
const record = await this.#activityRecord(event, scope, binding, input.level);
|
|
253
|
-
activityEvents.push({
|
|
254
|
-
action: event.action,
|
|
255
|
-
recordType: event.recordType,
|
|
256
|
-
scope: scopeBadge(event.scope),
|
|
257
|
-
sourceThreadId: record ? event.sourceThreadId : void 0,
|
|
258
|
-
createdAt: event.createdAt.toISOString(),
|
|
259
|
-
record
|
|
260
|
-
});
|
|
261
|
-
}
|
|
252
|
+
const events = [];
|
|
253
|
+
for (const event of result.events) events.push(await this.#activityEvent(event, binding, view));
|
|
262
254
|
await this.#assertStable(binding);
|
|
263
255
|
return {
|
|
264
256
|
identityKey: binding.identityKey,
|
|
265
257
|
scopeLevel: input.level,
|
|
266
|
-
events
|
|
267
|
-
nextCursor:
|
|
258
|
+
events,
|
|
259
|
+
nextCursor: result.nextCursor ? this.#mintCursor(binding, input.level, "activity", result.nextCursor) : void 0
|
|
268
260
|
};
|
|
269
261
|
}
|
|
270
262
|
async #binding() {
|
|
271
|
-
const
|
|
263
|
+
const session = resolveKnowledgeSessionScopes(this.#session.state.get(), this.#hostScopes);
|
|
272
264
|
const sessionResourceId = this.#session.identity.getResourceId();
|
|
273
|
-
if (!
|
|
274
|
-
this.#
|
|
275
|
-
throw new KnowledgeInspectorError("unavailable",
|
|
265
|
+
if (!session.resolved) {
|
|
266
|
+
this.#invalidate();
|
|
267
|
+
throw new KnowledgeInspectorError("unavailable", `Knowledge inspection unavailable: ${session.reason}`);
|
|
276
268
|
}
|
|
277
269
|
if (!sessionResourceId) throw new KnowledgeInspectorError("unavailable", "Knowledge inspection requires an active project.");
|
|
278
|
-
const
|
|
279
|
-
const resourceId =
|
|
270
|
+
const sessionTags = knowledgeSessionTags(session.scopes);
|
|
271
|
+
const resourceId = sessionResourceId;
|
|
280
272
|
const activeThreadId = this.#session.thread.getId() ?? void 0;
|
|
281
273
|
const thread = activeThreadId ? await this.#session.thread.getById({ threadId: activeThreadId }) : null;
|
|
282
274
|
const threadId = thread?.resourceId === sessionResourceId ? thread.id : void 0;
|
|
283
|
-
const fingerprint = `${
|
|
275
|
+
const fingerprint = `${sessionTags.join("")}\0${resourceId}\0${threadId ?? ""}`;
|
|
284
276
|
if (this.#fingerprint !== fingerprint) {
|
|
285
277
|
this.#fingerprint = fingerprint;
|
|
286
278
|
this.#identityKey = opaqueToken();
|
|
@@ -288,154 +280,179 @@ var ScopedKnowledgeInspector = class {
|
|
|
288
280
|
this.#cursors.clear();
|
|
289
281
|
}
|
|
290
282
|
return {
|
|
291
|
-
|
|
283
|
+
sessionTags,
|
|
292
284
|
resourceId,
|
|
293
285
|
threadId,
|
|
294
286
|
fingerprint,
|
|
295
287
|
identityKey: this.#identityKey
|
|
296
288
|
};
|
|
297
289
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (level === "
|
|
302
|
-
|
|
303
|
-
|
|
290
|
+
/** The principals a level reads with: the session's shared tags, plus its resource, plus its thread. */
|
|
291
|
+
async #view(binding, level) {
|
|
292
|
+
let principals;
|
|
293
|
+
if (level === "org") {
|
|
294
|
+
if (binding.sessionTags.length === 0) throw new KnowledgeInspectorError("unavailable", "This session holds no shared scope tags.");
|
|
295
|
+
principals = [...binding.sessionTags];
|
|
296
|
+
} else {
|
|
297
|
+
principals = [...binding.sessionTags, `resource:${binding.resourceId}`];
|
|
298
|
+
if (level === "thread") {
|
|
299
|
+
if (!binding.threadId) throw new KnowledgeInspectorError("unavailable", "The active thread does not belong to this project.");
|
|
300
|
+
principals.push(`thread:${binding.threadId}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
return {
|
|
305
|
+
level,
|
|
306
|
+
read: await this.#knowledge.readScopeFor(principals.map(canonical))
|
|
307
|
+
};
|
|
308
|
+
} catch (error) {
|
|
309
|
+
throw new KnowledgeInspectorError("unavailable", `Knowledge inspection unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
310
|
+
}
|
|
304
311
|
}
|
|
305
312
|
async #assertStable(binding) {
|
|
306
313
|
const current = await this.#binding();
|
|
307
314
|
if (current.identityKey !== binding.identityKey || current.fingerprint !== binding.fingerprint) throw new KnowledgeInspectorError("stale-handle", "Knowledge scope changed while the request was running.");
|
|
308
315
|
}
|
|
309
|
-
#
|
|
310
|
-
if (!
|
|
316
|
+
async #scopeAddress(scopeId) {
|
|
317
|
+
if (!this.#scopeAddresses.has(scopeId)) {
|
|
318
|
+
const page = await this.#store.getPage({
|
|
319
|
+
id: scopeId,
|
|
320
|
+
read: "all"
|
|
321
|
+
});
|
|
322
|
+
this.#scopeAddresses.set(scopeId, page?.address);
|
|
323
|
+
}
|
|
324
|
+
return this.#scopeAddresses.get(scopeId);
|
|
325
|
+
}
|
|
326
|
+
async #summary(page, binding, view) {
|
|
327
|
+
return {
|
|
328
|
+
handle: this.#mintHandle(binding, view.level, "node", page.id),
|
|
329
|
+
id: page.id,
|
|
330
|
+
type: "node",
|
|
331
|
+
name: page.name,
|
|
332
|
+
scope: badgeOf(await this.#scopeAddress(page.scopeId)),
|
|
333
|
+
version: page.version,
|
|
334
|
+
updatedAt: page.updatedAt.toISOString()
|
|
335
|
+
};
|
|
311
336
|
}
|
|
312
|
-
#
|
|
313
|
-
const type = "node";
|
|
337
|
+
async #noteSummary(note) {
|
|
314
338
|
return {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
version: record.version,
|
|
321
|
-
updatedAt: record.updatedAt.toISOString()
|
|
339
|
+
text: note.text,
|
|
340
|
+
scope: badgeOf(await this.#scopeAddress(note.stampScopeIds[0])),
|
|
341
|
+
sourceThreadId: note.provenance.sourceThreadId,
|
|
342
|
+
capturedAt: note.createdAt.toISOString(),
|
|
343
|
+
when: note.when?.toISOString()
|
|
322
344
|
};
|
|
323
345
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
346
|
+
/** The first pages by name, most linked first; ties keep name order. */
|
|
347
|
+
async #ranked(view, namePrefix) {
|
|
348
|
+
const { pages } = await this.#store.listPages({
|
|
349
|
+
read: view.read,
|
|
327
350
|
namePrefix,
|
|
328
|
-
kind,
|
|
329
351
|
limit: MAX_RANK_CANDIDATES
|
|
330
352
|
});
|
|
331
|
-
const ranked = await Promise.all(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
353
|
+
const ranked = await Promise.all(pages.map(async (page, order) => {
|
|
354
|
+
const counts = await this.#counts(page, view);
|
|
355
|
+
return {
|
|
356
|
+
id: page.id,
|
|
357
|
+
order,
|
|
358
|
+
counts,
|
|
359
|
+
degree: counts.outgoing + counts.incoming
|
|
360
|
+
};
|
|
361
|
+
}));
|
|
362
|
+
ranked.sort((a, b) => b.degree - a.degree || a.order - b.order);
|
|
338
363
|
return {
|
|
339
364
|
offset: 0,
|
|
340
|
-
entries:
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
})).map((entry) => ({
|
|
344
|
-
id: entry.node.id,
|
|
345
|
-
degree: entry.degree,
|
|
346
|
-
counts: entry.counts
|
|
365
|
+
entries: ranked.map(({ id, counts }) => ({
|
|
366
|
+
id,
|
|
367
|
+
counts
|
|
347
368
|
}))
|
|
348
369
|
};
|
|
349
370
|
}
|
|
350
|
-
async #
|
|
351
|
-
const [
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
limit:
|
|
355
|
-
}), this.#
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
limit:
|
|
371
|
+
async #counts(page, view) {
|
|
372
|
+
const [notes, mentioning] = await Promise.all([this.#store.listNotes({
|
|
373
|
+
pageId: page.id,
|
|
374
|
+
read: view.read,
|
|
375
|
+
limit: MAX_RANK_NOTES
|
|
376
|
+
}), this.#store.listNotesMentioning({
|
|
377
|
+
pageId: page.id,
|
|
378
|
+
read: view.read,
|
|
379
|
+
limit: MAX_RANK_NOTES
|
|
359
380
|
})]);
|
|
360
|
-
const [outgoing, incoming] = await Promise.all([this.#
|
|
361
|
-
const mentioningRecords = mentioningResult.records.filter((record) => record.node !== node.id);
|
|
381
|
+
const [outgoing, incoming] = await Promise.all([this.#outgoing(page, notes.notes, view), this.#incoming(mentioning.notes, view)]);
|
|
362
382
|
return {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
incoming: incoming.nodes.length,
|
|
368
|
-
sampled: Boolean(aboutResult.nextCursor || mentioningResult.nextCursor || outgoing.truncated || incoming.truncated)
|
|
369
|
-
}
|
|
383
|
+
records: notes.notes.length + mentioning.notes.length,
|
|
384
|
+
outgoing: outgoing.pages.length,
|
|
385
|
+
incoming: incoming.pages.length,
|
|
386
|
+
sampled: Boolean(notes.nextCursor || mentioning.nextCursor || outgoing.truncated || incoming.truncated)
|
|
370
387
|
};
|
|
371
388
|
}
|
|
372
|
-
|
|
389
|
+
/** Readable pages this page's notes mention, then pages its body links to by name. */
|
|
390
|
+
async #outgoing(page, notes, view) {
|
|
373
391
|
const related = /* @__PURE__ */ new Map();
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (
|
|
392
|
+
const add = (target) => {
|
|
393
|
+
if (target && !target.deletedAt && target.id !== page.id) related.set(target.id, target);
|
|
394
|
+
};
|
|
395
|
+
for (const id of new Set(notes.flatMap((note) => note.mentions))) {
|
|
396
|
+
if (related.size >= MAX_RELATED_PAGES) return {
|
|
397
|
+
pages: [...related.values()],
|
|
398
|
+
truncated: true
|
|
399
|
+
};
|
|
400
|
+
add(await this.#store.getPage({
|
|
401
|
+
id,
|
|
402
|
+
read: view.read
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
for (const name of parseKnowledgeWikilinks(page.body ?? "")) {
|
|
406
|
+
if (related.size >= MAX_RELATED_PAGES) return {
|
|
407
|
+
pages: [...related.values()],
|
|
408
|
+
truncated: true
|
|
409
|
+
};
|
|
410
|
+
const [target] = await this.#store.findPagesByName({
|
|
411
|
+
name,
|
|
412
|
+
read: view.read,
|
|
413
|
+
limit: 1
|
|
414
|
+
});
|
|
415
|
+
add(target);
|
|
389
416
|
}
|
|
390
417
|
return {
|
|
391
|
-
|
|
392
|
-
truncated
|
|
418
|
+
pages: [...related.values()],
|
|
419
|
+
truncated: false
|
|
393
420
|
};
|
|
394
421
|
}
|
|
395
|
-
|
|
422
|
+
/** Readable pages whose notes mention this page. */
|
|
423
|
+
async #incoming(mentioning, view) {
|
|
396
424
|
const related = /* @__PURE__ */ new Map();
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
425
|
+
for (const pageId of new Set(mentioning.map((note) => note.pageId))) {
|
|
426
|
+
if (related.size >= MAX_RELATED_PAGES) return {
|
|
427
|
+
pages: [...related.values()],
|
|
428
|
+
truncated: true
|
|
429
|
+
};
|
|
430
|
+
const source = await this.#store.getPage({
|
|
431
|
+
id: pageId,
|
|
432
|
+
read: view.read
|
|
433
|
+
});
|
|
434
|
+
if (source && !source.deletedAt) related.set(source.id, source);
|
|
406
435
|
}
|
|
407
436
|
return {
|
|
408
|
-
|
|
409
|
-
truncated
|
|
437
|
+
pages: [...related.values()],
|
|
438
|
+
truncated: false
|
|
410
439
|
};
|
|
411
440
|
}
|
|
412
|
-
async #
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
async #incomingParents(current, records, scope, binding, level) {
|
|
420
|
-
const related = await this.#incomingParentRecords(current, records, scope);
|
|
441
|
+
async #activityEvent(event, binding, view) {
|
|
442
|
+
const page = await this.#store.getPage({
|
|
443
|
+
id: event.pageId,
|
|
444
|
+
read: view.read
|
|
445
|
+
});
|
|
446
|
+
const live = page && !page.deletedAt ? page : void 0;
|
|
421
447
|
return {
|
|
422
|
-
|
|
423
|
-
|
|
448
|
+
action: event.action,
|
|
449
|
+
recordType: event.noteId ? "note" : "page",
|
|
450
|
+
scope: badgeOf(await this.#scopeAddress(event.scopeId)),
|
|
451
|
+
sourceThreadId: event.provenance.sourceThreadId,
|
|
452
|
+
createdAt: event.createdAt.toISOString(),
|
|
453
|
+
record: live ? await this.#summary(live, binding, view) : void 0
|
|
424
454
|
};
|
|
425
455
|
}
|
|
426
|
-
async #activityRecord(event, scope, binding, level) {
|
|
427
|
-
if (event.recordType === "node") {
|
|
428
|
-
const node = await this.#knowledge.getNode(event.recordId);
|
|
429
|
-
return node && isKnowledgeScopeVisible(node.scope, scope) ? this.#recordSummary(node, binding, level) : void 0;
|
|
430
|
-
}
|
|
431
|
-
const record = await this.#knowledge.getKnowledge({
|
|
432
|
-
id: event.recordId,
|
|
433
|
-
includeDeleted: true
|
|
434
|
-
});
|
|
435
|
-
if (!record || !isKnowledgeScopeVisible(record.scope, scope)) return void 0;
|
|
436
|
-
const node = await this.#knowledge.getNode(record.node);
|
|
437
|
-
return node && isKnowledgeScopeVisible(node.scope, scope) ? this.#recordSummary(node, binding, level) : void 0;
|
|
438
|
-
}
|
|
439
456
|
#mintHandle(binding, level, type, recordId) {
|
|
440
457
|
this.#pruneOpaqueEntries();
|
|
441
458
|
const token = opaqueToken();
|
|
@@ -470,7 +487,7 @@ var ScopedKnowledgeInspector = class {
|
|
|
470
487
|
#consumeCursor(cursor, binding, level, kind, filters) {
|
|
471
488
|
if (!cursor) return void 0;
|
|
472
489
|
const entry = this.#cursors.get(cursor);
|
|
473
|
-
if (!entry || entry.expiresAt < Date.now() || entry.identityKey !== binding.identityKey || entry.level !== level || entry.kind !== kind || entry.filters?.namePrefix !== filters?.namePrefix || entry.filters?.
|
|
490
|
+
if (!entry || entry.expiresAt < Date.now() || entry.identityKey !== binding.identityKey || entry.level !== level || entry.kind !== kind || entry.filters?.namePrefix !== filters?.namePrefix || entry.filters?.sort !== filters?.sort) throw new KnowledgeInspectorError("invalid-cursor", "Knowledge cursor does not match the active scope and filters.");
|
|
474
491
|
return entry.value;
|
|
475
492
|
}
|
|
476
493
|
#pruneOpaqueEntries() {
|
|
@@ -487,13 +504,18 @@ var ScopedKnowledgeInspector = class {
|
|
|
487
504
|
}
|
|
488
505
|
}
|
|
489
506
|
}
|
|
490
|
-
#
|
|
507
|
+
#invalidate() {
|
|
491
508
|
this.#fingerprint = void 0;
|
|
492
509
|
this.#identityKey = opaqueToken();
|
|
493
510
|
this.#handles.clear();
|
|
494
511
|
this.#cursors.clear();
|
|
495
512
|
}
|
|
496
513
|
};
|
|
514
|
+
/**
|
|
515
|
+
* The /knowledge inspector for a session, reading through the session's Knowledge runtime (a key
|
|
516
|
+
* resolves through `mastra`). Undefined when there is no Knowledge runtime: access rules live on the
|
|
517
|
+
* runtime, so a bare storage domain can't be inspected.
|
|
518
|
+
*/
|
|
497
519
|
async function createKnowledgeInspector(input) {
|
|
498
520
|
let selected;
|
|
499
521
|
if (typeof input.knowledge === "string") {
|
|
@@ -504,11 +526,14 @@ async function createKnowledgeInspector(input) {
|
|
|
504
526
|
}
|
|
505
527
|
if (!selected) throw new KnowledgeInspectorError("unavailable", "The configured Knowledge runtime is unavailable.");
|
|
506
528
|
} else selected = input.knowledge;
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
529
|
+
if (!selected) return void 0;
|
|
530
|
+
const store = await selected.getStorage();
|
|
531
|
+
return new ScopedKnowledgeInspector({
|
|
532
|
+
knowledge: selected,
|
|
533
|
+
store,
|
|
534
|
+
session: input.session,
|
|
535
|
+
hostScopes: input.hostScopes
|
|
536
|
+
});
|
|
512
537
|
}
|
|
513
538
|
export { KnowledgeInspectorError, createKnowledgeInspector };
|
|
514
539
|
|