@mastra/code-sdk 1.17.0 → 1.18.0-alpha.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.
Files changed (37) hide show
  1. package/dist/agents/instructions.d.ts +2 -10
  2. package/dist/agents/instructions.d.ts.map +1 -1
  3. package/dist/agents/instructions.js +2 -4
  4. package/dist/agents/instructions.js.map +1 -1
  5. package/dist/agents/memory.d.ts +1 -17
  6. package/dist/agents/memory.d.ts.map +1 -1
  7. package/dist/agents/memory.js +6 -55
  8. package/dist/agents/memory.js.map +1 -1
  9. package/dist/agents/prompts/index.d.ts +0 -2
  10. package/dist/agents/prompts/index.d.ts.map +1 -1
  11. package/dist/agents/prompts/index.js +0 -1
  12. package/dist/agents/prompts/index.js.map +1 -1
  13. package/dist/agents/prompts/tool-guidance.d.ts +0 -2
  14. package/dist/agents/prompts/tool-guidance.d.ts.map +1 -1
  15. package/dist/agents/prompts/tool-guidance.js +0 -30
  16. package/dist/agents/prompts/tool-guidance.js.map +1 -1
  17. package/dist/index.d.ts +15 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +28 -16
  20. package/dist/index.js.map +1 -1
  21. package/dist/knowledge-inspector.d.ts +26 -10
  22. package/dist/knowledge-inspector.d.ts.map +1 -1
  23. package/dist/knowledge-inspector.js +266 -241
  24. package/dist/knowledge-inspector.js.map +1 -1
  25. package/dist/knowledge-scope.d.ts +21 -23
  26. package/dist/knowledge-scope.d.ts.map +1 -1
  27. package/dist/knowledge-scope.js +49 -33
  28. package/dist/knowledge-scope.js.map +1 -1
  29. package/dist/mcp/manager.d.ts +3 -1
  30. package/dist/mcp/manager.d.ts.map +1 -1
  31. package/dist/mcp/manager.js +139 -104
  32. package/dist/mcp/manager.js.map +1 -1
  33. package/dist/onboarding/settings.d.ts +2 -0
  34. package/dist/onboarding/settings.d.ts.map +1 -1
  35. package/dist/onboarding/settings.js +3 -1
  36. package/dist/onboarding/settings.js.map +1 -1
  37. package/package.json +8 -8
@@ -1,5 +1,6 @@
1
- import { resolveKnowledgeScopeIdentity } from "./knowledge-scope.js";
2
- import { createKnowledgeNodeCursor, isKnowledgeScopeVisible, parseKnowledgeWikilinks } from "@mastra/core/storage";
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 DEFAULT_RECORD_LIMIT = 50;
15
- const MAX_RECORD_LIMIT = 50;
16
- const DEFAULT_FACT_LIMIT = 25;
17
- const MAX_FACT_LIMIT = 100;
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 MAX_RELATED_RECORDS = 25;
21
+ const MAX_RELATED_PAGES = 25;
21
22
  const MAX_RANK_CANDIDATES = 50;
22
- const MAX_RANK_FACTS = 100;
23
- const RRF_K = 60;
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 scopeBadge(scope) {
34
- const entry = scope.at(-1);
35
- if (!entry) throw new KnowledgeInspectorError("unavailable", "Knowledge record has no scope.");
36
- const separator = entry.indexOf(":");
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
- text: record.text,
45
- scope: scopeBadge(record.scope),
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.#invalidateIdnode();
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.organizationId,
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 scope = this.#scope(binding, input.level);
110
- const limit = boundedLimit(input.limit, DEFAULT_RECORD_LIMIT, MAX_RECORD_LIMIT);
111
- const sort = input.sort ?? "relevant";
112
- if (sort === "recent") {
113
- const cursor = this.#consumeCursor(input.cursor, binding, input.level, "node", {
114
- namePrefix: input.namePrefix,
115
- kind: input.kind,
116
- sort
117
- });
118
- const records = await this.#knowledge.listNodes({
119
- scope,
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(records.map(async (record) => ({
126
- ...this.#recordSummary(record, binding, input.level),
127
- relationshipCounts: (await this.#sampledRelationshipCounts(record, scope)).counts
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: records.length === limit ? this.#mintCursor(binding, input.level, "node", createKnowledgeNodeCursor(records.at(-1), {
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 filters = {
147
- namePrefix: input.namePrefix,
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 page) {
156
- const node = await this.#knowledge.getNode(entry.id);
157
- if (!node || !isKnowledgeScopeVisible(node.scope, scope)) continue;
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.#recordSummary(node, binding, input.level),
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: "recent-window"
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 scope = this.#scope(binding, handle.level);
181
- const node = await this.#knowledge.getNode(handle.recordId);
182
- this.#assertVisible(node, scope);
183
- const limit = boundedLimit(input.recordLimit, DEFAULT_FACT_LIMIT, MAX_FACT_LIMIT);
184
- const recordsAfter = this.#consumeCursor(input.recordsCursor, binding, handle.level, "records");
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 [recordsResult, mentioningResult] = await Promise.all([this.#knowledge.listKnowledgeAbout({
187
- node: node.id,
188
- scope,
189
- after: recordsAfter,
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.#knowledge.listKnowledgeMentioning({
192
- node: node.id,
193
- scope,
194
- after: mentioningAfter,
191
+ }), this.#store.listNotesMentioning({
192
+ pageId: page.id,
193
+ read: view.read,
194
+ cursor: mentioningAfter,
195
195
  limit
196
196
  })]);
197
- const mentioningRecords = mentioningResult.records.filter((record) => record.node !== node.id);
198
- const content = truncateUtf8(node.content ?? "", MAX_NODE_CONTENT_BYTES);
199
- const [outgoingTargets, incomingParents, relationship] = await Promise.all([
200
- this.#outgoingTargets(node, recordsResult.records, scope, binding, handle.level),
201
- this.#incomingParents(node, mentioningRecords, scope, binding, handle.level),
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(content.value).slice(0, MAX_RELATED_RECORDS).map(async (label) => {
205
- const target = await this.#knowledge.resolveNode({
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
- scope
206
+ read: view.read,
207
+ limit: 1
208
208
  });
209
209
  return {
210
210
  label,
211
- node: target ? this.#recordSummary(target, binding, handle.level) : void 0
211
+ node: target ? await this.#summary(target, binding, view) : void 0
212
212
  };
213
213
  }));
214
- await this.#assertStable(binding);
215
- return {
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.#recordSummary(node, binding, handle.level),
220
- relationshipCounts: relationship.counts
219
+ ...await this.#summary(page, binding, view),
220
+ relationshipCounts: counts
221
221
  },
222
- records: recordsResult.records.map(knowledgeSummary),
223
- recordsNextCursor: recordsResult.nextCursor ? this.#mintCursor(binding, handle.level, "records", recordsResult.nextCursor) : void 0,
224
- mentioningRecords: mentioningRecords.map(knowledgeSummary),
225
- mentioningRecordsNextCursor: mentioningResult.nextCursor ? this.#mintCursor(binding, handle.level, "mentioning-records", mentioningResult.nextCursor) : void 0,
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: outgoingTargets.nodes,
228
- partial: outgoingTargets.partial || Boolean(recordsResult.nextCursor)
227
+ nodes: await summaries(outgoing.pages),
228
+ partial: outgoing.truncated || Boolean(notes.nextCursor)
229
229
  },
230
230
  incomingParents: {
231
- nodes: incomingParents.nodes,
232
- partial: incomingParents.partial || Boolean(mentioningResult.nextCursor)
231
+ nodes: await summaries(incoming.pages),
232
+ partial: incoming.truncated || Boolean(mentioning.nextCursor)
233
233
  },
234
- relationshipCounts: relationship.counts,
235
- content: node.content === void 0 ? void 0 : content.value,
236
- contentTruncated: content.truncated,
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 scope = this.#scope(binding, input.level);
244
+ const view = await this.#view(binding, input.level);
243
245
  const limit = boundedLimit(input.limit, DEFAULT_ACTIVITY_LIMIT, MAX_ACTIVITY_LIMIT);
244
- const after = this.#consumeCursor(input.cursor, binding, input.level, "activity");
245
- const events = await this.#knowledge.listActivity({
246
- scope,
247
- after,
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 activityEvents = [];
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: activityEvents,
267
- nextCursor: events.length === limit ? this.#mintCursor(binding, input.level, "activity", events.at(-1).id) : void 0
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 identity = resolveKnowledgeScopeIdentity(this.#session.state.get());
263
+ const session = resolveKnowledgeSessionScopes(this.#session.state.get(), this.#hostScopes);
272
264
  const sessionResourceId = this.#session.identity.getResourceId();
273
- if (!identity.resolved) {
274
- this.#invalidateIdnode();
275
- throw new KnowledgeInspectorError("unavailable", identity.reason ? `Knowledge inspection unavailable: ${identity.reason}` : "Knowledge inspection requires a resolved organization for this session.");
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 organizationId = identity.organizationId;
279
- const resourceId = identity.knowledgeResourceId ?? sessionResourceId;
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 = `${organizationId}\0${resourceId}\0${threadId ?? ""}`;
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
- organizationId,
283
+ sessionTags,
292
284
  resourceId,
293
285
  threadId,
294
286
  fingerprint,
295
287
  identityKey: this.#identityKey
296
288
  };
297
289
  }
298
- #scope(binding, level) {
299
- if (level === "org") return [`org:${binding.organizationId}`];
300
- const scope = [`org:${binding.organizationId}`, `resource:${binding.resourceId}`];
301
- if (level === "resource") return scope;
302
- if (!binding.threadId) throw new KnowledgeInspectorError("unavailable", "The active thread does not belong to this project.");
303
- return [...scope, `thread:${binding.threadId}`];
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
- #assertVisible(record, scope) {
310
- if (!record || !isKnowledgeScopeVisible(record.scope, scope)) throw new KnowledgeInspectorError("not-visible", "Knowledge record is not visible in the selected scope.");
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
- #recordSummary(record, binding, level) {
313
- const type = "node";
337
+ async #noteSummary(note) {
314
338
  return {
315
- handle: this.#mintHandle(binding, level, type, record.id),
316
- type,
317
- name: record.name,
318
- kind: record.kind,
319
- scope: scopeBadge(record.scope),
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
- async #rankedNodeSnapshot(scope, namePrefix, kind, sort) {
325
- const records = await this.#knowledge.listNodes({
326
- scope,
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(records.map(async (node, recencyRank) => ({
332
- node,
333
- recencyRank,
334
- ...await this.#sampledRelationshipCounts(node, scope)
335
- })));
336
- const connected = [...ranked].sort((a, b) => b.degree - a.degree || a.recencyRank - b.recencyRank || a.node.id.localeCompare(b.node.id));
337
- const connectedRank = new Map(connected.map((entry, index) => [entry.node.id, index]));
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: (sort === "connected" ? connected : [...ranked].sort((a, b) => {
341
- const aScore = 1 / (RRF_K + a.recencyRank + 1) + 1 / (RRF_K + connectedRank.get(a.node.id) + 1);
342
- return 1 / (RRF_K + b.recencyRank + 1) + 1 / (RRF_K + connectedRank.get(b.node.id) + 1) - aScore || a.recencyRank - b.recencyRank || a.node.id.localeCompare(b.node.id);
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 #sampledRelationshipCounts(node, scope) {
351
- const [aboutResult, mentioningResult] = await Promise.all([this.#knowledge.listKnowledgeAbout({
352
- node: node.id,
353
- scope,
354
- limit: MAX_RANK_FACTS
355
- }), this.#knowledge.listKnowledgeMentioning({
356
- node: node.id,
357
- scope,
358
- limit: MAX_RANK_FACTS
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.#outgoingNodeRecords(node, aboutResult.records, scope), this.#incomingParentRecords(node, mentioningResult.records, scope)]);
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
- degree: new Set([...outgoing.nodes, ...incoming.nodes].map((record) => record.id)).size,
364
- counts: {
365
- records: aboutResult.records.length + mentioningRecords.length,
366
- outgoing: outgoing.nodes.length,
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
- async #outgoingNodeRecords(current, records, scope) {
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
- let truncated = false;
375
- const sources = [current.content ?? "", ...records.map((record) => record.text)];
376
- for (const source of sources) {
377
- for (const name of parseKnowledgeWikilinks(source)) {
378
- if (related.size >= MAX_RELATED_RECORDS) {
379
- truncated = true;
380
- break;
381
- }
382
- const node = await this.#knowledge.resolveNode({
383
- name,
384
- scope
385
- });
386
- if (node && node.id !== current.id && isKnowledgeScopeVisible(node.scope, scope)) related.set(node.id, node);
387
- }
388
- if (truncated) break;
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
- nodes: [...related.values()],
392
- truncated
418
+ pages: [...related.values()],
419
+ truncated: false
393
420
  };
394
421
  }
395
- async #incomingParentRecords(current, records, scope) {
422
+ /** Readable pages whose notes mention this page. */
423
+ async #incoming(mentioning, view) {
396
424
  const related = /* @__PURE__ */ new Map();
397
- let truncated = false;
398
- for (const record of records) {
399
- if (record.node === current.id || related.has(record.node)) continue;
400
- if (related.size >= MAX_RELATED_RECORDS) {
401
- truncated = true;
402
- break;
403
- }
404
- const node = await this.#knowledge.getNode(record.node);
405
- if (node && isKnowledgeScopeVisible(node.scope, scope)) related.set(node.id, node);
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
- nodes: [...related.values()],
409
- truncated
437
+ pages: [...related.values()],
438
+ truncated: false
410
439
  };
411
440
  }
412
- async #outgoingTargets(current, records, scope, binding, level) {
413
- const related = await this.#outgoingNodeRecords(current, records, scope);
414
- return {
415
- nodes: related.nodes.map((node) => this.#recordSummary(node, binding, level)),
416
- partial: related.truncated
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
- nodes: related.nodes.map((node) => this.#recordSummary(node, binding, level)),
423
- partial: related.truncated
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?.kind !== filters?.kind || entry.filters?.sort !== filters?.sort) throw new KnowledgeInspectorError("invalid-cursor", "Knowledge cursor does not match the active scope and 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
- #invalidateIdnode() {
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
- const knowledge = selected ? await selected.getStorage() : await input.storage.getStore("knowledge");
508
- return knowledge ? new ScopedKnowledgeInspector({
509
- knowledge,
510
- session: input.session
511
- }) : void 0;
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