@mastra/factory 0.10.2-alpha.0 → 0.10.2-alpha.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge.js","names":["#inWindow","#windowIds","#fallbackCache","#store","#maxFallbackLookups","#trackOutOfWindow","#fallbackLookups","#cappedSeen","#limits","#resolveTenant","#resolveView","#pinnedNodeIds","#pinnedRecords"],"sources":["../../src/routes/knowledge.ts"],"sourcesContent":["/**\n * Read-only Mastra `apiRoutes` exposing the factory project's knowledge graph.\n *\n * Serves the Knowledge page in factory-ui: a polling graph snapshot (nodes\n * as nodes, wikilink edges derived from record text), a node flyout payload\n * with per-record provenance, and the recent activity feed. Every endpoint is a\n * GET — this module never writes knowledge.\n *\n * Scoping is fail-closed: the org and resource rungs are derived server-side\n * from the authenticated caller and the validated `:id` project. The DEFAULT\n * view queries `[org:<orgId>, resource:<projectId>]` (org + project records).\n * Thread-scoped records are reachable ONLY via an explicit, server-validated\n * `threadId` query parameter (the drill-down view), which appends the thread\n * rung to the query scope. A thread is drillable iff it produced knowledge\n * visible under the caller's org/project prefix; unknown or cross-org threads\n * 404 — never a silent fallback to the default view.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { KnowledgeNode, KnowledgeRecord, KnowledgeScope, KnowledgeStorage } from '@mastra/core/storage';\nimport {\n canonicalizeKnowledgeScope,\n isKnowledgeScopeVisible,\n knowledgeScopeKey,\n parseKnowledgeWikilinks,\n} from '@mastra/core/storage';\nimport type { Context } from 'hono';\n\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\n/** Reserved node that anchors pinned records (see subconscious/pinned.ts). */\nconst PINNED_NODE_NAME = 'pinned';\n\n/** Hover-card budget for record text shipped in the graph payload. */\nconst RECORD_TEXT_LIMIT = 240;\n\nfunction truncateRecordText(text: string): string {\n return text.length > RECORD_TEXT_LIMIT ? `${text.slice(0, RECORD_TEXT_LIMIT - 1)}…` : text;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Window caps. Injectable at construction only — never per-request. */\nexport interface KnowledgeRouteLimits {\n /** Max nodes in a graph snapshot (newest-first). */\n maxNodes: number;\n /** Max records parsed for edges per snapshot (newest-first). */\n maxRecords: number;\n /** Max fallback `resolveNode` store lookups per request (deduped per unique name+scope). */\n maxFallbackLookups: number;\n}\n\nconst DEFAULT_LIMITS: KnowledgeRouteLimits = { maxNodes: 500, maxRecords: 2000, maxFallbackLookups: 100 };\n\nexport interface KnowledgeRoutesDeps extends RouteDependencies {\n /** Factory projects domain — validates the `:id` project belongs to the caller's org. */\n projects: FactoryProjectsStorage;\n /** Lazy handle to the knowledge storage domain; endpoints 503 when absent. */\n knowledge: () => Promise<KnowledgeStorage | undefined>;\n limits?: Partial<KnowledgeRouteLimits>;\n}\n\n/** A graph node. `recordCount` is window-derived (records inside the snapshot window only). */\nexport interface KnowledgeGraphNode {\n id: string;\n name: string;\n kind: string;\n scope: KnowledgeScope;\n /** Deepest rung of the record's scope: org | resource | thread. */\n rung: 'org' | 'resource' | 'thread';\n /**\n * True when a non-deleted pinned record's wikilinks reference ONLY this\n * node (A9: multi-target pins mark their edges instead — the pin is\n * about the relationship; a single-target pin has no edge to carry it).\n */\n pinned: boolean;\n /** Records owned by this node INSIDE the snapshot window (not a total). */\n recordCount: number;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface KnowledgeGraphEdge {\n id: string;\n /** The owning node of the record (its `node`). */\n source: string;\n /** The wikilink-resolved node. */\n target: string;\n /**\n * Always 'wikilink': the record's `node` is the edge SOURCE, so the\n * plan's \"parent link\" collapses into the wikilink edge — nodes carry no\n * separate parent field to derive a second edge type from.\n */\n type: 'wikilink';\n /** The record whose text produced the edge. */\n recordId: string;\n /**\n * True when the edge is derived from a PINNED record linking two nodes —\n * the pin marks the relationship, so the accent lives on the edge (A9).\n */\n pinned?: boolean;\n}\n\n/**\n * A knowledge record as a first-class graph element (A11): every record in the window,\n * with the in-window nodes it touches. The client renders by arity —\n * 1 node: a small dot linked to it; 2: the connecting line; 3+: a midpoint\n * junction splitting to each node. Pin records have their hidden reserved\n * owner omitted, so their arity comes purely from wikilink targets.\n */\nexport interface KnowledgeGraphRecord {\n /** The record id. */\n id: string;\n /** Owner node first (omitted for pins), then resolved wikilink targets. */\n nodeIds: string[];\n pinned: boolean;\n /** Record text, truncated for hover cards. */\n text: string;\n}\n\nexport interface KnowledgeGraphPayload {\n view: 'project' | 'thread';\n threadId?: string;\n nodes: KnowledgeGraphNode[];\n edges: KnowledgeGraphEdge[];\n records: KnowledgeGraphRecord[];\n /** True when the node or record window cap was hit (newest-first window). */\n truncated: boolean;\n /** Wikilink targets that resolved in the store but fell outside the node window. */\n outOfWindow: Array<{ id: string; name: string }>;\n /** Unique unknown names skipped once the fallback-lookup cap was hit. */\n unresolvedCapped: { count: number; names: string[] };\n /** Pin counts per rung of the active view (thread is null in the default view). */\n pinCensus: { resource: number; thread: number | null };\n /** Change hint: newest knowledge activity id (per-process monotonic — hint only). */\n version: string | null;\n}\n\nexport interface KnowledgeNodeRecordPayload {\n id: string;\n node: string;\n /** 'owned' when the node is the record's parent, 'mentions' when it only wikilinks it. */\n relation: 'owned' | 'mentions';\n text: string;\n scope: KnowledgeScope;\n rung: 'org' | 'resource' | 'thread';\n sourceThreadId: string;\n capturedAt: string;\n when?: string;\n pinned: boolean;\n metadata?: Record<string, unknown>;\n}\n\nexport interface KnowledgeNodePayload {\n node: {\n id: string;\n name: string;\n kind: string;\n content: string;\n scope: KnowledgeScope;\n rung: 'org' | 'resource' | 'thread';\n createdAt: string;\n updatedAt: string;\n };\n records: KnowledgeNodeRecordPayload[];\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction deepestRung(scope: KnowledgeScope): 'org' | 'resource' | 'thread' {\n let rung: 'org' | 'resource' | 'thread' = 'org';\n for (const entry of scope) {\n const ns = entry.slice(0, entry.indexOf(':'));\n if (ns === 'thread') return 'thread';\n if (ns === 'resource') rung = 'resource';\n }\n return rung;\n}\n\nfunction boundedThreadId(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n const trimmed = raw.trim();\n return trimmed.length > 0 && trimmed.length <= 512 ? trimmed : undefined;\n}\n\ninterface ResolvedView {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n store: KnowledgeStorage;\n view: 'project' | 'thread';\n threadId?: string;\n /** The query scope for the active view. */\n scope: KnowledgeScope;\n /** Exact scopes where a reserved `pinned` node may live for this view. */\n pinRungs: Array<{ rung: 'resource' | 'thread'; scope: KnowledgeScope }>;\n}\n\n/**\n * In-record + capped-fallback wikilink resolver, shared by both endpoints.\n * Resolution uses the store's own algorithm: a descending-prefix walk over the\n * record's canonical scope matching canonical name + exact scope key at each\n * prefix, so an edge never depends on whether the target landed in the window.\n */\nclass WikilinkResolver {\n /** exact `${scopeKey}\\u0000${lowerName}` → node, from the fetched window. */\n readonly #inWindow = new Map<string, KnowledgeNode>();\n readonly #windowIds = new Set<string>();\n /** `${recordScopeKey}\\u0000${lowerName}` → fallback result (null = dangling). */\n readonly #fallbackCache = new Map<string, KnowledgeNode | null>();\n #fallbackLookups = 0;\n readonly #store: KnowledgeStorage;\n readonly #maxFallbackLookups: number;\n readonly outOfWindow = new Map<string, { id: string; name: string }>();\n readonly cappedNames: string[] = [];\n #cappedSeen = new Set<string>();\n\n constructor(store: KnowledgeStorage, nodes: KnowledgeNode[], maxFallbackLookups: number) {\n this.#store = store;\n this.#maxFallbackLookups = maxFallbackLookups;\n for (const node of nodes) {\n this.#inWindow.set(`${knowledgeScopeKey(node.scope)}\\u0000${node.name.trim().toLocaleLowerCase()}`, node);\n this.#windowIds.add(node.id);\n }\n }\n\n inWindowId(id: string): boolean {\n return this.#windowIds.has(id);\n }\n\n /** Resolve a wikilink name from a knowledge record's scope. Returns the node or null (dangling/capped). */\n async resolve(name: string, recordScope: KnowledgeScope): Promise<KnowledgeNode | null> {\n const canonical = canonicalizeKnowledgeScope(recordScope);\n const lower = name.trim().toLocaleLowerCase();\n for (let length = canonical.length; length > 0; length--) {\n const hit = this.#inWindow.get(`${knowledgeScopeKey(canonical.slice(0, length))}\\u0000${lower}`);\n if (hit) return hit;\n }\n const cacheKey = `${knowledgeScopeKey(canonical)}\\u0000${lower}`;\n if (this.#fallbackCache.has(cacheKey)) {\n return this.#trackOutOfWindow(this.#fallbackCache.get(cacheKey) ?? null);\n }\n if (this.#fallbackLookups >= this.#maxFallbackLookups) {\n if (!this.#cappedSeen.has(lower) && this.cappedNames.length < 100) {\n this.#cappedSeen.add(lower);\n this.cappedNames.push(name.trim());\n } else if (!this.#cappedSeen.has(lower)) {\n this.#cappedSeen.add(lower);\n }\n return null;\n }\n this.#fallbackLookups += 1;\n let resolved: KnowledgeNode | null = null;\n try {\n resolved = await this.#store.resolveNode({ name, scope: canonical });\n } catch {\n resolved = null;\n }\n this.#fallbackCache.set(cacheKey, resolved);\n return this.#trackOutOfWindow(resolved);\n }\n\n #trackOutOfWindow(node: KnowledgeNode | null): KnowledgeNode | null {\n if (node && !this.#windowIds.has(node.id)) {\n this.outOfWindow.set(node.id, { id: node.id, name: node.name });\n }\n return node;\n }\n\n get cappedCount(): number {\n return this.#cappedSeen.size;\n }\n}\n\nexport class KnowledgeRoutes extends Route<KnowledgeRoutesDeps> {\n readonly #limits: KnowledgeRouteLimits;\n\n constructor(deps: KnowledgeRoutesDeps) {\n super(deps);\n this.#limits = { ...DEFAULT_LIMITS, ...deps.limits };\n }\n\n /** Resolve the `(orgId, userId)` tenant or a ready-to-return error response. */\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'The knowledge graph requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n /**\n * Resolve tenant + org-owned project + knowledge store + the active view's\n * query scope. The ONE seam both endpoints share, so the default/thread view\n * scope and the pin rungs cannot drift between them.\n *\n * threadId validation runs a single `listRecordsBySource` lookup with\n * `limit: 1` AT THE CANDIDATE SCOPE `[org, resource, thread:<id>]` — the\n * store's own visibility predicate is the authorization: the thread's own\n * records (equal scope key) and its project/org captures (prefix) match, while\n * a cross-org thread's records match nothing → zero rows → 404.\n */\n async #resolveView(c: Context): Promise<ResolvedView | { response: Response }> {\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n const { projects } = this.deps;\n await projects.ensureReady();\n const project = await projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n\n const store = await this.deps.knowledge();\n if (!store) {\n return {\n response: c.json(\n { error: 'knowledge_unavailable', message: 'The knowledge storage domain is not configured.' },\n 503,\n ),\n };\n }\n\n const defaultScope: KnowledgeScope = [`org:${tenant.orgId}`, `resource:${projectId}`];\n const resourceRungScope = defaultScope;\n\n const threadId = boundedThreadId(c.req.query('threadId'));\n if (c.req.query('threadId') !== undefined && !threadId) {\n return { response: c.json({ error: 'thread_not_found' }, 404) };\n }\n if (!threadId) {\n return {\n ...tenant,\n factoryProjectId: projectId,\n store,\n view: 'project',\n scope: defaultScope,\n pinRungs: [{ rung: 'resource', scope: resourceRungScope }],\n };\n }\n\n const candidateScope: KnowledgeScope = [...defaultScope, `thread:${threadId}`];\n const probe = await store.knowledgeBySource({ sourceThreadId: threadId, scope: candidateScope, limit: 1 });\n if (probe.records.length === 0) {\n return { response: c.json({ error: 'thread_not_found' }, 404) };\n }\n return {\n ...tenant,\n factoryProjectId: projectId,\n store,\n view: 'thread',\n threadId,\n scope: candidateScope,\n pinRungs: [\n { rung: 'resource', scope: resourceRungScope },\n { rung: 'thread', scope: candidateScope },\n ],\n };\n }\n\n /** Reserved `pinned` node ids at the active view's rungs (one exact-scope lookup per rung). */\n async #pinnedNodeIds(view: ResolvedView): Promise<Array<{ rung: 'resource' | 'thread'; id: string }>> {\n const out: Array<{ rung: 'resource' | 'thread'; id: string }> = [];\n for (const { rung, scope } of view.pinRungs) {\n const node = await view.store.getNodeByName({ name: PINNED_NODE_NAME, scope });\n if (node && !node.mergedInto) out.push({ rung, id: node.id });\n }\n return out;\n }\n\n /** Non-deleted pinned records for the given pinned-node ids, visible in the view. */\n async #pinnedRecords(\n view: ResolvedView,\n pinnedNodeIds: Array<{ rung: 'resource' | 'thread'; id: string }>,\n ): Promise<Array<{ rung: 'resource' | 'thread'; record: KnowledgeRecord }>> {\n const out: Array<{ rung: 'resource' | 'thread'; record: KnowledgeRecord }> = [];\n for (const { rung, id } of pinnedNodeIds) {\n const { records } = await view.store.listKnowledgeAbout({ node: id, scope: view.scope, limit: 200 });\n for (const record of records) out.push({ rung, record });\n }\n return out;\n }\n\n routes(): ApiRoute[] {\n return [\n // ── Graph snapshot: nodes + derived edges, polled by the page ──────────\n registerApiRoute('/web/factory/projects/:id/knowledge/graph', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const view = await this.#resolveView(loose(c));\n if ('response' in view) return view.response;\n const { store, scope } = view;\n const limits = this.#limits;\n\n // Nodes, newest-first; +1 to detect truncation.\n const fetched = await store.listNodes({ scope, limit: limits.maxNodes + 1 });\n let truncated = fetched.length > limits.maxNodes;\n const pinnedNodeIds = await this.#pinnedNodeIds(view);\n const pinnedNodeIdSet = new Set(pinnedNodeIds.map(p => p.id));\n const nodes = fetched.slice(0, limits.maxNodes).filter(node => !pinnedNodeIdSet.has(node.id));\n\n // Record window: per-node owned records, then newest-first overall.\n const recordWindow: KnowledgeRecord[] = [];\n for (const node of nodes) {\n if (recordWindow.length > limits.maxRecords) break;\n const { records } = await store.listKnowledgeAbout({\n node: node.id,\n scope,\n limit: limits.maxRecords + 1 - recordWindow.length,\n });\n recordWindow.push(...records);\n }\n // Record ids are ULIDs — descending id = newest-first.\n recordWindow.sort((a, b) => b.id.localeCompare(a.id));\n if (recordWindow.length > limits.maxRecords) {\n truncated = true;\n recordWindow.length = limits.maxRecords;\n }\n\n const resolver = new WikilinkResolver(store, nodes, limits.maxFallbackLookups);\n\n // Edges: owner node (the record's parent link) → wikilinked node.\n // Graph records: every windowed record with its in-window node set,\n // owner first. The client renders dots, lines, or junctions by arity.\n const edges: KnowledgeGraphEdge[] = [];\n const graphRecords: KnowledgeGraphRecord[] = [];\n const edgeSeen = new Set<string>();\n const recordCounts = new Map<string, number>();\n for (const record of recordWindow) {\n recordCounts.set(record.node, (recordCounts.get(record.node) ?? 0) + 1);\n const nodeIds = [record.node];\n for (const name of parseKnowledgeWikilinks(record.text)) {\n const target = await resolver.resolve(name, record.scope);\n if (!target) continue;\n if (target.id === record.node) continue;\n if (!resolver.inWindowId(target.id)) continue;\n if (!nodeIds.includes(target.id)) nodeIds.push(target.id);\n const key = `${record.node}\\u0000${target.id}`;\n if (edgeSeen.has(key)) continue;\n edgeSeen.add(key);\n edges.push({\n id: `wikilink:${record.node}:${target.id}`,\n source: record.node,\n target: target.id,\n type: 'wikilink',\n recordId: record.id,\n });\n }\n graphRecords.push({ id: record.id, nodeIds, pinned: false, text: truncateRecordText(record.text) });\n }\n\n // Pins mark relationships. The reserved owner node is omitted, so\n // arity comes purely from wikilink targets.\n const pinnedRecords = await this.#pinnedRecords(view, pinnedNodeIds);\n const accented = new Set<string>();\n for (const { record } of pinnedRecords) {\n const targets: string[] = [];\n for (const name of parseKnowledgeWikilinks(record.text)) {\n const target = await resolver.resolve(name, record.scope);\n if (target && resolver.inWindowId(target.id) && !targets.includes(target.id)) {\n targets.push(target.id);\n }\n }\n graphRecords.push({ id: record.id, nodeIds: targets, pinned: true, text: truncateRecordText(record.text) });\n if (targets.length === 1) {\n accented.add(targets[0]!);\n continue;\n }\n for (let a = 0; a < targets.length; a += 1) {\n for (let b = a + 1; b < targets.length; b += 1) {\n const key = `${targets[a]}\\u0000${targets[b]}\\u0000pin`;\n if (edgeSeen.has(key)) continue;\n edgeSeen.add(key);\n edges.push({\n id: `pin:${record.id}:${targets[a]}:${targets[b]}`,\n source: targets[a]!,\n target: targets[b]!,\n type: 'wikilink',\n recordId: record.id,\n pinned: true,\n });\n }\n }\n }\n const pinCensus = {\n resource: pinnedRecords.filter(p => p.rung === 'resource').length,\n thread: view.view === 'thread' ? pinnedRecords.filter(p => p.rung === 'thread').length : null,\n };\n\n const activity = await store.listActivity({ scope, limit: 1 });\n\n const payload: KnowledgeGraphPayload = {\n view: view.view,\n ...(view.threadId ? { threadId: view.threadId } : {}),\n nodes: nodes.map(node => ({\n id: node.id,\n name: node.name,\n kind: node.kind,\n scope: node.scope,\n rung: deepestRung(node.scope),\n pinned: accented.has(node.id),\n recordCount: recordCounts.get(node.id) ?? 0,\n createdAt: node.createdAt.toISOString(),\n updatedAt: node.updatedAt.toISOString(),\n })),\n edges,\n records: graphRecords,\n truncated,\n outOfWindow: [...resolver.outOfWindow.values()],\n unresolvedCapped: { count: resolver.cappedCount, names: resolver.cappedNames },\n pinCensus,\n version: activity[0]?.id ?? null,\n };\n return c.json(payload);\n },\n }),\n\n // ── Node flyout payload: details + provenance-rich records ───────────────\n registerApiRoute('/web/factory/projects/:id/knowledge/nodes/:nodeId', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const view = await this.#resolveView(loose(c));\n if ('response' in view) return view.response;\n const { store, scope } = view;\n const nodeId = loose(c).req.param('nodeId');\n if (!nodeId || nodeId.length > 512) return c.json({ error: 'node_not_found' }, 404);\n\n const node = await store.getNode(nodeId);\n // getNode is a bare id lookup with no scope predicate. This explicit\n // visibility check prevents an IDOR.\n if (!node || !isKnowledgeScopeVisible(node.scope, scope)) {\n return c.json({ error: 'node_not_found' }, 404);\n }\n\n const pinnedNodeIds = await this.#pinnedNodeIds(view);\n const pinnedNodeIdSet = new Set(pinnedNodeIds.map(p => p.id));\n\n const [owned, mentioning] = await Promise.all([\n store.listKnowledgeAbout({ node: node.id, scope, limit: 200 }),\n store.listKnowledgeMentioning({ node: node.id, scope, limit: 200 }),\n ]);\n const seen = new Set<string>();\n const records: KnowledgeNodeRecordPayload[] = [];\n const push = (record: KnowledgeRecord, relation: 'owned' | 'mentions') => {\n if (seen.has(record.id)) return;\n seen.add(record.id);\n records.push({\n id: record.id,\n node: record.node,\n relation,\n text: record.text,\n scope: record.scope,\n rung: deepestRung(record.scope),\n sourceThreadId: record.sourceThreadId,\n capturedAt: record.capturedAt.toISOString(),\n ...(record.when ? { when: record.when.toISOString() } : {}),\n pinned: pinnedNodeIdSet.has(record.node),\n ...(record.metadata ? { metadata: record.metadata } : {}),\n });\n };\n // Owned first, newest-first within each group. Record ids are ULIDs.\n for (const record of [...owned.records].sort((a, b) => b.id.localeCompare(a.id))) push(record, 'owned');\n for (const record of [...mentioning.records].sort((a, b) => b.id.localeCompare(a.id))) {\n push(record, 'mentions');\n }\n\n const payload: KnowledgeNodePayload = {\n node: {\n id: node.id,\n name: node.name,\n kind: node.kind,\n content: node.content ?? '',\n scope: node.scope,\n rung: deepestRung(node.scope),\n createdAt: node.createdAt.toISOString(),\n updatedAt: node.updatedAt.toISOString(),\n },\n records,\n };\n return c.json(payload);\n },\n }),\n\n // ── Recent activity feed for the live-arrival affordance ───────────────\n registerApiRoute('/web/factory/projects/:id/knowledge/activity', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const view = await this.#resolveView(loose(c));\n if ('response' in view) return view.response;\n const events = await view.store.listActivity({ scope: view.scope, limit: 100 });\n return c.json({\n events: events.map(event => ({\n id: event.id,\n action: event.action,\n recordType: event.recordType,\n recordId: event.recordId,\n scope: event.scope,\n ...(event.sourceThreadId ? { sourceThreadId: event.sourceThreadId } : {}),\n createdAt: event.createdAt.toISOString(),\n })),\n });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"knowledge.js","names":["#inWindow","#windowIds","#fallbackCache","#store","#maxFallbackLookups","#trackOutOfWindow","#fallbackLookups","#cappedSeen","#limits","#resolveTenant","#resolveView","#pinnedNodeIds","#pinnedRecords"],"sources":["../../src/routes/knowledge.ts"],"sourcesContent":["/**\n * Read-only Mastra `apiRoutes` exposing the factory project's knowledge graph.\n *\n * Serves the Knowledge page in factory-ui: a polling graph snapshot (nodes\n * as nodes, wikilink edges derived from record text), a node flyout payload\n * with per-record provenance, and the recent activity feed. Every endpoint is a\n * GET — this module never writes knowledge.\n *\n * Scoping is fail-closed: the org and resource rungs are derived server-side\n * from the authenticated caller and the validated `:id` project. The DEFAULT\n * view queries `[org:<orgId>, resource:<projectId>]` (org + project records).\n * Thread-scoped records are reachable ONLY via an explicit, server-validated\n * `threadId` query parameter (the drill-down view), which appends the thread\n * rung to the query scope. A thread is drillable iff it produced knowledge\n * visible under the caller's org/project prefix; unknown or cross-org threads\n * 404 — never a silent fallback to the default view.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { KnowledgeNode, KnowledgeRecord, KnowledgeScope, KnowledgeStorage } from '@mastra/core/storage';\nimport {\n canonicalizeKnowledgeScope,\n isKnowledgeScopeVisible,\n knowledgeScopeKey,\n parseKnowledgeWikilinks,\n} from '@mastra/core/storage';\nimport type { Context } from 'hono';\n\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\n/** Reserved node that anchors pinned records (see subconscious/pinned.ts). */\nconst PINNED_NODE_NAME = 'pinned';\n\n/** Hover-card budget for record text shipped in the graph payload. */\nconst RECORD_TEXT_LIMIT = 240;\n\nfunction truncateRecordText(text: string): string {\n return text.length > RECORD_TEXT_LIMIT ? `${text.slice(0, RECORD_TEXT_LIMIT - 1)}…` : text;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Window caps. Injectable at construction only — never per-request. */\nexport interface KnowledgeRouteLimits {\n /** Max nodes in a graph snapshot (newest-first). */\n maxNodes: number;\n /** Max records parsed for edges per snapshot (newest-first). */\n maxRecords: number;\n /** Max fallback `resolveNode` store lookups per request (deduped per unique name+scope). */\n maxFallbackLookups: number;\n}\n\nconst DEFAULT_LIMITS: KnowledgeRouteLimits = { maxNodes: 500, maxRecords: 2000, maxFallbackLookups: 100 };\n\nexport interface KnowledgeRoutesDeps extends RouteDependencies {\n /** Factory projects domain — validates the `:id` project belongs to the caller's org. */\n projects: FactoryProjectsStorage;\n /** Lazy handle to the knowledge storage domain; endpoints 503 when absent. */\n knowledge: () => Promise<KnowledgeStorage | undefined>;\n limits?: Partial<KnowledgeRouteLimits>;\n}\n\n/** A graph node. `recordCount` is window-derived (records inside the snapshot window only). */\nexport interface KnowledgeGraphNode {\n id: string;\n name: string;\n kind: string;\n description?: string;\n scope: KnowledgeScope;\n /** Deepest rung of the record's scope: org | resource | thread. */\n rung: 'org' | 'resource' | 'thread';\n /**\n * True when a non-deleted pinned record's wikilinks reference ONLY this\n * node (A9: multi-target pins mark their edges instead — the pin is\n * about the relationship; a single-target pin has no edge to carry it).\n */\n pinned: boolean;\n /** Records owned by this node INSIDE the snapshot window (not a total). */\n recordCount: number;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface KnowledgeGraphEdge {\n id: string;\n /** The owning node of the record (its `node`). */\n source: string;\n /** The wikilink-resolved node. */\n target: string;\n /**\n * Always 'wikilink': the record's `node` is the edge SOURCE, so the\n * plan's \"parent link\" collapses into the wikilink edge — nodes carry no\n * separate parent field to derive a second edge type from.\n */\n type: 'wikilink';\n /** The record whose text produced the edge. */\n recordId: string;\n /**\n * True when the edge is derived from a PINNED record linking two nodes —\n * the pin marks the relationship, so the accent lives on the edge (A9).\n */\n pinned?: boolean;\n}\n\n/**\n * A knowledge record as a first-class graph element (A11): every record in the window,\n * with the in-window nodes it touches. The client renders by arity —\n * 1 node: a small dot linked to it; 2: the connecting line; 3+: a midpoint\n * junction splitting to each node. Pin records have their hidden reserved\n * owner omitted, so their arity comes purely from wikilink targets.\n */\nexport interface KnowledgeGraphRecord {\n /** The record id. */\n id: string;\n /** Owner node first (omitted for pins), then resolved wikilink targets. */\n nodeIds: string[];\n pinned: boolean;\n /** Record text, truncated for hover cards. */\n text: string;\n}\n\nexport interface KnowledgeGraphPayload {\n view: 'project' | 'thread';\n threadId?: string;\n nodes: KnowledgeGraphNode[];\n edges: KnowledgeGraphEdge[];\n records: KnowledgeGraphRecord[];\n /** True when the node or record window cap was hit (newest-first window). */\n truncated: boolean;\n /** Wikilink targets that resolved in the store but fell outside the node window. */\n outOfWindow: Array<{ id: string; name: string }>;\n /** Unique unknown names skipped once the fallback-lookup cap was hit. */\n unresolvedCapped: { count: number; names: string[] };\n /** Pin counts per rung of the active view (thread is null in the default view). */\n pinCensus: { resource: number; thread: number | null };\n /** Change hint: newest knowledge activity id (per-process monotonic — hint only). */\n version: string | null;\n}\n\nexport interface KnowledgeNodeRecordPayload {\n id: string;\n node: string;\n /** 'owned' when the node is the record's parent, 'mentions' when it only wikilinks it. */\n relation: 'owned' | 'mentions';\n text: string;\n scope: KnowledgeScope;\n rung: 'org' | 'resource' | 'thread';\n sourceThreadId: string;\n capturedAt: string;\n when?: string;\n pinned: boolean;\n metadata?: Record<string, unknown>;\n}\n\nexport interface KnowledgeNodePayload {\n node: {\n id: string;\n name: string;\n kind: string;\n content: string;\n scope: KnowledgeScope;\n rung: 'org' | 'resource' | 'thread';\n createdAt: string;\n updatedAt: string;\n };\n records: KnowledgeNodeRecordPayload[];\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction deepestRung(scope: KnowledgeScope): 'org' | 'resource' | 'thread' {\n let rung: 'org' | 'resource' | 'thread' = 'org';\n for (const entry of scope) {\n const ns = entry.slice(0, entry.indexOf(':'));\n if (ns === 'thread') return 'thread';\n if (ns === 'resource') rung = 'resource';\n }\n return rung;\n}\n\nfunction boundedThreadId(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n const trimmed = raw.trim();\n return trimmed.length > 0 && trimmed.length <= 512 ? trimmed : undefined;\n}\n\ninterface ResolvedView {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n store: KnowledgeStorage;\n view: 'project' | 'thread';\n threadId?: string;\n /** The query scope for the active view. */\n scope: KnowledgeScope;\n /** Exact scopes where a reserved `pinned` node may live for this view. */\n pinRungs: Array<{ rung: 'resource' | 'thread'; scope: KnowledgeScope }>;\n}\n\n/**\n * In-record + capped-fallback wikilink resolver, shared by both endpoints.\n * Resolution uses the store's own algorithm: a descending-prefix walk over the\n * record's canonical scope matching canonical name + exact scope key at each\n * prefix, so an edge never depends on whether the target landed in the window.\n */\nclass WikilinkResolver {\n /** exact `${scopeKey}\\u0000${lowerName}` → node, from the fetched window. */\n readonly #inWindow = new Map<string, KnowledgeNode>();\n readonly #windowIds = new Set<string>();\n /** `${recordScopeKey}\\u0000${lowerName}` → fallback result (null = dangling). */\n readonly #fallbackCache = new Map<string, KnowledgeNode | null>();\n #fallbackLookups = 0;\n readonly #store: KnowledgeStorage;\n readonly #maxFallbackLookups: number;\n readonly outOfWindow = new Map<string, { id: string; name: string }>();\n readonly cappedNames: string[] = [];\n #cappedSeen = new Set<string>();\n\n constructor(store: KnowledgeStorage, nodes: KnowledgeNode[], maxFallbackLookups: number) {\n this.#store = store;\n this.#maxFallbackLookups = maxFallbackLookups;\n for (const node of nodes) {\n this.#inWindow.set(`${knowledgeScopeKey(node.scope)}\\u0000${node.name.trim().toLocaleLowerCase()}`, node);\n this.#windowIds.add(node.id);\n }\n }\n\n inWindowId(id: string): boolean {\n return this.#windowIds.has(id);\n }\n\n /** Resolve a wikilink name from a knowledge record's scope. Returns the node or null (dangling/capped). */\n async resolve(name: string, recordScope: KnowledgeScope): Promise<KnowledgeNode | null> {\n const canonical = canonicalizeKnowledgeScope(recordScope);\n const lower = name.trim().toLocaleLowerCase();\n for (let length = canonical.length; length > 0; length--) {\n const hit = this.#inWindow.get(`${knowledgeScopeKey(canonical.slice(0, length))}\\u0000${lower}`);\n if (hit) return hit;\n }\n const cacheKey = `${knowledgeScopeKey(canonical)}\\u0000${lower}`;\n if (this.#fallbackCache.has(cacheKey)) {\n return this.#trackOutOfWindow(this.#fallbackCache.get(cacheKey) ?? null);\n }\n if (this.#fallbackLookups >= this.#maxFallbackLookups) {\n if (!this.#cappedSeen.has(lower) && this.cappedNames.length < 100) {\n this.#cappedSeen.add(lower);\n this.cappedNames.push(name.trim());\n } else if (!this.#cappedSeen.has(lower)) {\n this.#cappedSeen.add(lower);\n }\n return null;\n }\n this.#fallbackLookups += 1;\n let resolved: KnowledgeNode | null = null;\n try {\n resolved = await this.#store.resolveNode({ name, scope: canonical });\n } catch {\n resolved = null;\n }\n this.#fallbackCache.set(cacheKey, resolved);\n return this.#trackOutOfWindow(resolved);\n }\n\n #trackOutOfWindow(node: KnowledgeNode | null): KnowledgeNode | null {\n if (node && !this.#windowIds.has(node.id)) {\n this.outOfWindow.set(node.id, { id: node.id, name: node.name });\n }\n return node;\n }\n\n get cappedCount(): number {\n return this.#cappedSeen.size;\n }\n}\n\nexport class KnowledgeRoutes extends Route<KnowledgeRoutesDeps> {\n readonly #limits: KnowledgeRouteLimits;\n\n constructor(deps: KnowledgeRoutesDeps) {\n super(deps);\n this.#limits = { ...DEFAULT_LIMITS, ...deps.limits };\n }\n\n /** Resolve the `(orgId, userId)` tenant or a ready-to-return error response. */\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'The knowledge graph requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n /**\n * Resolve tenant + org-owned project + knowledge store + the active view's\n * query scope. The ONE seam both endpoints share, so the default/thread view\n * scope and the pin rungs cannot drift between them.\n *\n * threadId validation runs a single `listRecordsBySource` lookup with\n * `limit: 1` AT THE CANDIDATE SCOPE `[org, resource, thread:<id>]` — the\n * store's own visibility predicate is the authorization: the thread's own\n * records (equal scope key) and its project/org captures (prefix) match, while\n * a cross-org thread's records match nothing → zero rows → 404.\n */\n async #resolveView(c: Context): Promise<ResolvedView | { response: Response }> {\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n const { projects } = this.deps;\n await projects.ensureReady();\n const project = await projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n\n const store = await this.deps.knowledge();\n if (!store) {\n return {\n response: c.json(\n { error: 'knowledge_unavailable', message: 'The knowledge storage domain is not configured.' },\n 503,\n ),\n };\n }\n\n const defaultScope: KnowledgeScope = [`org:${tenant.orgId}`, `resource:${projectId}`];\n const resourceRungScope = defaultScope;\n\n const threadId = boundedThreadId(c.req.query('threadId'));\n if (c.req.query('threadId') !== undefined && !threadId) {\n return { response: c.json({ error: 'thread_not_found' }, 404) };\n }\n if (!threadId) {\n return {\n ...tenant,\n factoryProjectId: projectId,\n store,\n view: 'project',\n scope: defaultScope,\n pinRungs: [{ rung: 'resource', scope: resourceRungScope }],\n };\n }\n\n const candidateScope: KnowledgeScope = [...defaultScope, `thread:${threadId}`];\n const probe = await store.knowledgeBySource({ sourceThreadId: threadId, scope: candidateScope, limit: 1 });\n if (probe.records.length === 0) {\n return { response: c.json({ error: 'thread_not_found' }, 404) };\n }\n return {\n ...tenant,\n factoryProjectId: projectId,\n store,\n view: 'thread',\n threadId,\n scope: candidateScope,\n pinRungs: [\n { rung: 'resource', scope: resourceRungScope },\n { rung: 'thread', scope: candidateScope },\n ],\n };\n }\n\n /** Reserved `pinned` node ids at the active view's rungs (one exact-scope lookup per rung). */\n async #pinnedNodeIds(view: ResolvedView): Promise<Array<{ rung: 'resource' | 'thread'; id: string }>> {\n const out: Array<{ rung: 'resource' | 'thread'; id: string }> = [];\n for (const { rung, scope } of view.pinRungs) {\n const node = await view.store.getNodeByName({ name: PINNED_NODE_NAME, scope });\n if (node && !node.mergedInto) out.push({ rung, id: node.id });\n }\n return out;\n }\n\n /** Non-deleted pinned records for the given pinned-node ids, visible in the view. */\n async #pinnedRecords(\n view: ResolvedView,\n pinnedNodeIds: Array<{ rung: 'resource' | 'thread'; id: string }>,\n ): Promise<Array<{ rung: 'resource' | 'thread'; record: KnowledgeRecord }>> {\n const out: Array<{ rung: 'resource' | 'thread'; record: KnowledgeRecord }> = [];\n for (const { rung, id } of pinnedNodeIds) {\n const { records } = await view.store.listKnowledgeAbout({ node: id, scope: view.scope, limit: 200 });\n for (const record of records) out.push({ rung, record });\n }\n return out;\n }\n\n routes(): ApiRoute[] {\n return [\n // ── Graph snapshot: nodes + derived edges, polled by the page ──────────\n registerApiRoute('/web/factory/projects/:id/knowledge/graph', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const view = await this.#resolveView(loose(c));\n if ('response' in view) return view.response;\n const { store, scope } = view;\n const limits = this.#limits;\n\n // Nodes, newest-first; +1 to detect truncation.\n const fetched = await store.listNodes({ scope, limit: limits.maxNodes + 1 });\n let truncated = fetched.length > limits.maxNodes;\n const pinnedNodeIds = await this.#pinnedNodeIds(view);\n const pinnedNodeIdSet = new Set(pinnedNodeIds.map(p => p.id));\n const nodes = fetched.slice(0, limits.maxNodes).filter(node => !pinnedNodeIdSet.has(node.id));\n\n // Record window: per-node owned records, then newest-first overall.\n const recordWindow: KnowledgeRecord[] = [];\n for (const node of nodes) {\n if (recordWindow.length > limits.maxRecords) break;\n const { records } = await store.listKnowledgeAbout({\n node: node.id,\n scope,\n limit: limits.maxRecords + 1 - recordWindow.length,\n });\n recordWindow.push(...records);\n }\n // Record ids are ULIDs — descending id = newest-first.\n recordWindow.sort((a, b) => b.id.localeCompare(a.id));\n if (recordWindow.length > limits.maxRecords) {\n truncated = true;\n recordWindow.length = limits.maxRecords;\n }\n\n const resolver = new WikilinkResolver(store, nodes, limits.maxFallbackLookups);\n\n // Edges: owner node (the record's parent link) → wikilinked node.\n // Graph records: every windowed record with its in-window node set,\n // owner first. The client renders dots, lines, or junctions by arity.\n const edges: KnowledgeGraphEdge[] = [];\n const graphRecords: KnowledgeGraphRecord[] = [];\n const edgeSeen = new Set<string>();\n const recordCounts = new Map<string, number>();\n for (const record of recordWindow) {\n recordCounts.set(record.node, (recordCounts.get(record.node) ?? 0) + 1);\n const nodeIds = [record.node];\n for (const name of parseKnowledgeWikilinks(record.text)) {\n const target = await resolver.resolve(name, record.scope);\n if (!target) continue;\n if (target.id === record.node) continue;\n if (!resolver.inWindowId(target.id)) continue;\n if (!nodeIds.includes(target.id)) nodeIds.push(target.id);\n const key = `${record.node}\\u0000${target.id}`;\n if (edgeSeen.has(key)) continue;\n edgeSeen.add(key);\n edges.push({\n id: `wikilink:${record.node}:${target.id}`,\n source: record.node,\n target: target.id,\n type: 'wikilink',\n recordId: record.id,\n });\n }\n graphRecords.push({ id: record.id, nodeIds, pinned: false, text: truncateRecordText(record.text) });\n }\n\n // Pins mark relationships. The reserved owner node is omitted, so\n // arity comes purely from wikilink targets.\n const pinnedRecords = await this.#pinnedRecords(view, pinnedNodeIds);\n const accented = new Set<string>();\n for (const { record } of pinnedRecords) {\n const targets: string[] = [];\n for (const name of parseKnowledgeWikilinks(record.text)) {\n const target = await resolver.resolve(name, record.scope);\n if (target && resolver.inWindowId(target.id) && !targets.includes(target.id)) {\n targets.push(target.id);\n }\n }\n graphRecords.push({ id: record.id, nodeIds: targets, pinned: true, text: truncateRecordText(record.text) });\n if (targets.length === 1) {\n accented.add(targets[0]!);\n continue;\n }\n for (let a = 0; a < targets.length; a += 1) {\n for (let b = a + 1; b < targets.length; b += 1) {\n const key = `${targets[a]}\\u0000${targets[b]}\\u0000pin`;\n if (edgeSeen.has(key)) continue;\n edgeSeen.add(key);\n edges.push({\n id: `pin:${record.id}:${targets[a]}:${targets[b]}`,\n source: targets[a]!,\n target: targets[b]!,\n type: 'wikilink',\n recordId: record.id,\n pinned: true,\n });\n }\n }\n }\n const pinCensus = {\n resource: pinnedRecords.filter(p => p.rung === 'resource').length,\n thread: view.view === 'thread' ? pinnedRecords.filter(p => p.rung === 'thread').length : null,\n };\n\n const activity = await store.listActivity({ scope, limit: 1 });\n\n const payload: KnowledgeGraphPayload = {\n view: view.view,\n ...(view.threadId ? { threadId: view.threadId } : {}),\n nodes: nodes.map(node => ({\n id: node.id,\n name: node.name,\n kind: node.kind,\n // Empty string is a curator clear — omit it so the payload carries no dead keys.\n ...(node.description ? { description: node.description } : {}),\n scope: node.scope,\n rung: deepestRung(node.scope),\n pinned: accented.has(node.id),\n recordCount: recordCounts.get(node.id) ?? 0,\n createdAt: node.createdAt.toISOString(),\n updatedAt: node.updatedAt.toISOString(),\n })),\n edges,\n records: graphRecords,\n truncated,\n outOfWindow: [...resolver.outOfWindow.values()],\n unresolvedCapped: { count: resolver.cappedCount, names: resolver.cappedNames },\n pinCensus,\n version: activity[0]?.id ?? null,\n };\n return c.json(payload);\n },\n }),\n\n // ── Node flyout payload: details + provenance-rich records ───────────────\n registerApiRoute('/web/factory/projects/:id/knowledge/nodes/:nodeId', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const view = await this.#resolveView(loose(c));\n if ('response' in view) return view.response;\n const { store, scope } = view;\n const nodeId = loose(c).req.param('nodeId');\n if (!nodeId || nodeId.length > 512) return c.json({ error: 'node_not_found' }, 404);\n\n const node = await store.getNode(nodeId);\n // getNode is a bare id lookup with no scope predicate. This explicit\n // visibility check prevents an IDOR.\n if (!node || !isKnowledgeScopeVisible(node.scope, scope)) {\n return c.json({ error: 'node_not_found' }, 404);\n }\n\n const pinnedNodeIds = await this.#pinnedNodeIds(view);\n const pinnedNodeIdSet = new Set(pinnedNodeIds.map(p => p.id));\n\n const [owned, mentioning] = await Promise.all([\n store.listKnowledgeAbout({ node: node.id, scope, limit: 200 }),\n store.listKnowledgeMentioning({ node: node.id, scope, limit: 200 }),\n ]);\n const seen = new Set<string>();\n const records: KnowledgeNodeRecordPayload[] = [];\n const push = (record: KnowledgeRecord, relation: 'owned' | 'mentions') => {\n if (seen.has(record.id)) return;\n seen.add(record.id);\n records.push({\n id: record.id,\n node: record.node,\n relation,\n text: record.text,\n scope: record.scope,\n rung: deepestRung(record.scope),\n sourceThreadId: record.sourceThreadId,\n capturedAt: record.capturedAt.toISOString(),\n ...(record.when ? { when: record.when.toISOString() } : {}),\n pinned: pinnedNodeIdSet.has(record.node),\n ...(record.metadata ? { metadata: record.metadata } : {}),\n });\n };\n // Owned first, newest-first within each group. Record ids are ULIDs.\n for (const record of [...owned.records].sort((a, b) => b.id.localeCompare(a.id))) push(record, 'owned');\n for (const record of [...mentioning.records].sort((a, b) => b.id.localeCompare(a.id))) {\n push(record, 'mentions');\n }\n\n const payload: KnowledgeNodePayload = {\n node: {\n id: node.id,\n name: node.name,\n kind: node.kind,\n content: node.content ?? '',\n scope: node.scope,\n rung: deepestRung(node.scope),\n createdAt: node.createdAt.toISOString(),\n updatedAt: node.updatedAt.toISOString(),\n },\n records,\n };\n return c.json(payload);\n },\n }),\n\n // ── Recent activity feed for the live-arrival affordance ───────────────\n registerApiRoute('/web/factory/projects/:id/knowledge/activity', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const view = await this.#resolveView(loose(c));\n if ('response' in view) return view.response;\n const events = await view.store.listActivity({ scope: view.scope, limit: 100 });\n return c.json({\n events: events.map(event => ({\n id: event.id,\n action: event.action,\n recordType: event.recordType,\n recordId: event.recordId,\n scope: event.scope,\n ...(event.sourceThreadId ? { sourceThreadId: event.sourceThreadId } : {}),\n createdAt: event.createdAt.toISOString(),\n })),\n });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { seedSessionOrg } from "./org-seed.js";
1
2
  import { factoryMemorySettingsUserId } from "../storage/domains/memory-settings/base.js";
2
3
  import { applyStoredMemorySettings } from "./memory-settings-hydration.js";
3
4
  import { randomUUID } from "crypto";
@@ -150,6 +151,7 @@ async function ensureFactorySourceSession(args) {
150
151
  * default it was created with, and the reason is logged.
151
152
  */
152
153
  async function hydrateFactorySession(session, args) {
154
+ await seedSessionOrg(session, args.orgId);
153
155
  try {
154
156
  const record = args.memorySettings && args.factoryProjectId ? await args.memorySettings.get({
155
157
  orgId: args.orgId,
@@ -1 +1 @@
1
- {"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { factoryMemorySettingsUserId } from '../storage/domains/memory-settings/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { applyStoredMemorySettings } from './memory-settings-hydration.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\n/**\n * Read the factory project's default model. Best-effort: a missing project or an\n * uninitialized storage domain means \"no default\", never a failed run.\n */\nexport async function resolveFactoryDefaultModelId(\n projects: FactoryProjectsStorage | undefined,\n factoryProjectId: string | undefined,\n): Promise<string | undefined> {\n if (!projects || !factoryProjectId) return undefined;\n try {\n const project = await projects.getById({ id: factoryProjectId });\n return project?.defaultModelId ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolveFactorySourceRepository}. A miss carries which step\n * failed: callers differ on whether that is an error (an autonomous run cannot\n * proceed) or a routine fallback (a chat integration drops to a chat-only\n * session), and the two steps fail for different reasons worth reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * Resolve which repository a factory project's source-control runs act on: the\n * owner's connection on the project, then one of its linked repositories.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolveFactorySourceRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, repositorySlug } = args;\n\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n const candidates = connections.filter(candidate => candidate.integrationId === sourceControl.integrationId);\n if (candidates.length === 0) return { found: false, reason: 'connection' };\n\n // A project can carry stale connections: a provider-app reinstall leaves the\n // old connection pointing at an installation that no longer exists, and that\n // row can sit ahead of the healthy one. Try every candidate and skip the ones\n // that no longer resolve rather than failing on the first.\n for (const connection of candidates) {\n let resolved;\n try {\n const projectRepositories = await sourceControl.projectRepositories.list({ orgId, connectionId: connection.id });\n const resolvedRepositories = await Promise.all(\n projectRepositories.map(async projectRepository => ({\n projectRepository,\n repository: await sourceControl.repositories.get({ orgId, id: projectRepository.repositoryId }),\n })),\n );\n resolved = resolvedRepositories.find(\n candidate => candidate.repository && (!repositorySlug || candidate.repository.slug === repositorySlug),\n );\n } catch {\n // The connection no longer resolves (e.g. its installation was deleted).\n continue;\n }\n if (!resolved?.repository) continue;\n\n return {\n found: true,\n projectRepositoryId: resolved.projectRepository.id,\n baseBranch: resolved.projectRepository.branch ?? resolved.repository.defaultBranch,\n connectedByUserId: connection.createdByUserId,\n };\n }\n\n return { found: false, reason: 'repository' };\n}\n\n/**\n * Walk a Factory user-session id back to the project it belongs to.\n *\n * Repo-backed channel threads are keyed by their Factory session id, which is\n * the only handle a session-start hook gets. This turns that id back into the\n * project whose configuration the session should adopt. Durable by\n * construction — it reads the same rows the session was created from, so it\n * survives restarts without any in-memory mapping.\n */\nexport async function resolveFactoryProjectForSession(args: {\n sourceControl: SourceControlStorageHandle;\n sessionId: string;\n}): Promise<{ factoryProjectId: string; orgId: string; userId: string } | null> {\n const { sourceControl, sessionId } = args;\n\n const session = await sourceControl.sessions.getBySessionId(sessionId);\n if (!session) return null;\n const projectRepository = await sourceControl.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) return null;\n const connection = await sourceControl.connections.get({ orgId: session.orgId, id: projectRepository.connectionId });\n if (!connection) return null;\n\n return { factoryProjectId: connection.factoryProjectId, orgId: session.orgId, userId: session.userId };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, one of\n * its linked repositories, and a session on the requested branch with the\n * repository's pinned or default branch as the base.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, repositorySlug } = args;\n\n const resolved = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId, repositorySlug });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: 'org',\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n /**\n * The factory project whose shared memory settings apply. Factory sessions\n * never read an individual user's personal memory settings — the project's\n * own row (or the built-in defaults) is what they run with.\n */\n factoryProjectId?: string;\n /** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */\n defaultModelId?: string;\n /**\n * When provided, the factory project's stored memory-settings row is\n * applied. When omitted (or no row exists) the session is reset to the\n * built-in memory defaults.\n */\n memorySettings?: MemorySettingsStorage;\n}\n\n/**\n * Apply a factory project's configuration to a freshly created session:\n * observational-memory settings, then the project's default model.\n *\n * Both steps are best-effort. A retired model id or an unreachable settings row\n * must not sink a run that is otherwise ready — the session simply keeps the\n * default it was created with, and the reason is logged.\n */\nexport async function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void> {\n try {\n const record =\n args.memorySettings && args.factoryProjectId\n ? await args.memorySettings.get({\n orgId: args.orgId,\n userId: factoryMemorySettingsUserId(args.factoryProjectId),\n })\n : null;\n // Without a stored row, fall back to the low-cost OM model of the factory\n // default model's provider — a factory connected only to Anthropic should\n // not observe with the (uncredentialed) built-in Google default.\n const provider = args.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider ? resolveProviderOMDefault(provider, args.defaultModelId).modelId : undefined;\n await applyStoredMemorySettings(session, record, fallbackOmModelId);\n } catch (error) {\n console.warn('[Factory Start] Failed to apply observational-memory settings', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (args.defaultModelId) {\n try {\n await session.model.switch({ modelId: args.defaultModelId });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default model', {\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { factoryMemorySettingsUserId } from '../storage/domains/memory-settings/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { applyStoredMemorySettings } from './memory-settings-hydration.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\n/**\n * Read the factory project's default model. Best-effort: a missing project or an\n * uninitialized storage domain means \"no default\", never a failed run.\n */\nexport async function resolveFactoryDefaultModelId(\n projects: FactoryProjectsStorage | undefined,\n factoryProjectId: string | undefined,\n): Promise<string | undefined> {\n if (!projects || !factoryProjectId) return undefined;\n try {\n const project = await projects.getById({ id: factoryProjectId });\n return project?.defaultModelId ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolveFactorySourceRepository}. A miss carries which step\n * failed: callers differ on whether that is an error (an autonomous run cannot\n * proceed) or a routine fallback (a chat integration drops to a chat-only\n * session), and the two steps fail for different reasons worth reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * Resolve which repository a factory project's source-control runs act on: the\n * owner's connection on the project, then one of its linked repositories.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolveFactorySourceRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, repositorySlug } = args;\n\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n const candidates = connections.filter(candidate => candidate.integrationId === sourceControl.integrationId);\n if (candidates.length === 0) return { found: false, reason: 'connection' };\n\n // A project can carry stale connections: a provider-app reinstall leaves the\n // old connection pointing at an installation that no longer exists, and that\n // row can sit ahead of the healthy one. Try every candidate and skip the ones\n // that no longer resolve rather than failing on the first.\n for (const connection of candidates) {\n let resolved;\n try {\n const projectRepositories = await sourceControl.projectRepositories.list({ orgId, connectionId: connection.id });\n const resolvedRepositories = await Promise.all(\n projectRepositories.map(async projectRepository => ({\n projectRepository,\n repository: await sourceControl.repositories.get({ orgId, id: projectRepository.repositoryId }),\n })),\n );\n resolved = resolvedRepositories.find(\n candidate => candidate.repository && (!repositorySlug || candidate.repository.slug === repositorySlug),\n );\n } catch {\n // The connection no longer resolves (e.g. its installation was deleted).\n continue;\n }\n if (!resolved?.repository) continue;\n\n return {\n found: true,\n projectRepositoryId: resolved.projectRepository.id,\n baseBranch: resolved.projectRepository.branch ?? resolved.repository.defaultBranch,\n connectedByUserId: connection.createdByUserId,\n };\n }\n\n return { found: false, reason: 'repository' };\n}\n\n/**\n * Walk a Factory user-session id back to the project it belongs to.\n *\n * Repo-backed channel threads are keyed by their Factory session id, which is\n * the only handle a session-start hook gets. This turns that id back into the\n * project whose configuration the session should adopt. Durable by\n * construction — it reads the same rows the session was created from, so it\n * survives restarts without any in-memory mapping.\n */\nexport async function resolveFactoryProjectForSession(args: {\n sourceControl: SourceControlStorageHandle;\n sessionId: string;\n}): Promise<{ factoryProjectId: string; orgId: string; userId: string } | null> {\n const { sourceControl, sessionId } = args;\n\n const session = await sourceControl.sessions.getBySessionId(sessionId);\n if (!session) return null;\n const projectRepository = await sourceControl.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) return null;\n const connection = await sourceControl.connections.get({ orgId: session.orgId, id: projectRepository.connectionId });\n if (!connection) return null;\n\n return { factoryProjectId: connection.factoryProjectId, orgId: session.orgId, userId: session.userId };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, one of\n * its linked repositories, and a session on the requested branch with the\n * repository's pinned or default branch as the base.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, repositorySlug } = args;\n\n const resolved = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId, repositorySlug });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: 'org',\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n /**\n * The factory project whose shared memory settings apply. Factory sessions\n * never read an individual user's personal memory settings — the project's\n * own row (or the built-in defaults) is what they run with.\n */\n factoryProjectId?: string;\n /** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */\n defaultModelId?: string;\n /**\n * When provided, the factory project's stored memory-settings row is\n * applied. When omitted (or no row exists) the session is reset to the\n * built-in memory defaults.\n */\n memorySettings?: MemorySettingsStorage;\n}\n\n/**\n * Apply a factory project's configuration to a freshly created session:\n * observational-memory settings, then the project's default model.\n *\n * Both steps are best-effort. A retired model id or an unreachable settings row\n * must not sink a run that is otherwise ready — the session simply keeps the\n * default it was created with, and the reason is logged.\n */\nexport async function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void> {\n // The org rung knowledge capture scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n try {\n const record =\n args.memorySettings && args.factoryProjectId\n ? await args.memorySettings.get({\n orgId: args.orgId,\n userId: factoryMemorySettingsUserId(args.factoryProjectId),\n })\n : null;\n // Without a stored row, fall back to the low-cost OM model of the factory\n // default model's provider — a factory connected only to Anthropic should\n // not observe with the (uncredentialed) built-in Google default.\n const provider = args.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider ? resolveProviderOMDefault(provider, args.defaultModelId).modelId : undefined;\n await applyStoredMemorySettings(session, record, fallbackOmModelId);\n } catch (error) {\n console.warn('[Factory Start] Failed to apply observational-memory settings', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (args.defaultModelId) {\n try {\n await session.model.switch({ modelId: args.defaultModelId });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default model', {\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n"],"mappings":"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"}
@@ -21,6 +21,7 @@ interface OMStateWrites {
21
21
  observationThreshold?: number;
22
22
  reflectionThreshold?: number;
23
23
  observeAttachments?: 'auto' | boolean;
24
+ factoryOrgId?: string;
24
25
  }
25
26
  /** The slice of a session needed to apply stored observational-memory settings. */
26
27
  export interface OMConfigurableSession {
@@ -56,15 +57,27 @@ export interface MemorySettingsHydrationDependencies {
56
57
  memorySettings: Pick<MemorySettingsStorage, 'get'>;
57
58
  }
58
59
  /**
59
- * Seed a freshly created controller session's observational-memory settings
60
- * from the owner's stored `memory-settings` row. Registered as a blocking
61
- * session-created listener so the seed lands before the caller can start a run.
60
+ * Seed a freshly created controller session's tenant org and its
61
+ * observational-memory settings from the owner's source-control row. Registered
62
+ * as a blocking session-created listener so the seed lands before the caller can
63
+ * start a run.
62
64
  *
63
- * Sessions tagged `factoryProjectId` (work/review runs, created with that tag)
64
- * hydrate through the start coordinator; sessions without a GitHub
65
- * source-control row (e.g. chat-only channel sessions) hydrate through
66
- * `hydrateFactorySession` with their own resolved tenant. Both are skipped
67
- * here. Best-effort: failures are logged, never thrown.
65
+ * The org seed matters beyond settings. Subconscious knowledge capture scopes
66
+ * every node and record on `factoryOrgId`; before the SDK refusal guard,
67
+ * missing it made capture substitute the session owner id. For web chat sessions
68
+ * that is the agent controller's own id rather than a tenant, so captured
69
+ * knowledge landed under an org rung no reader ever queries. Same rule as the
70
+ * start coordinator: the org
71
+ * comes from the row the session was created from, never improvised from an
72
+ * owner id.
73
+ *
74
+ * Memory settings for sessions tagged `factoryProjectId` (work/review runs) are
75
+ * owned by the start coordinator, and sessions without a GitHub source-control
76
+ * row (e.g. chat-only channel sessions) hydrate through `hydrateFactorySession`
77
+ * with their own resolved tenant; both are skipped here. The org seed is not
78
+ * skipped on the tag alone: a web chat session persists `factoryProjectId` from
79
+ * its browser seed, so on resume it carries the tag without ever having been
80
+ * through the coordinator. Best-effort: failures are logged, never thrown.
68
81
  */
69
82
  export declare function hydrateSessionMemorySettings(session: MemorySettingsHydrationSession, { sourceControl, memorySettings }: MemorySettingsHydrationDependencies): Promise<void>;
70
83
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"memory-settings-hydration.d.ts","sourceRoot":"","sources":["../../src/session/memory-settings-hydration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAC9G,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAE5F,yDAAyD;AACzD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,2DAA2D;AAC3D,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED;;;;GAIG;AACH,UAAU,aAAa;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACvC;AAED,mFAAmF;AACnF,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE;QAAE,QAAQ,EAAE,WAAW,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,CAAC;IACtD,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KACvD,CAAC;CACH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,MAAM,WAAW,8BAA+B,SAAQ,qBAAqB;IAC3E,QAAQ,CAAC,QAAQ,EAAE;QAAE,aAAa,IAAI,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,WAAW,mCAAmC;IAClD,4FAA4F;IAC5F,aAAa,EAAE;QACb,QAAQ,EAAE,IAAI,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;KAC1E,CAAC;IACF,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;CACpD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,EACvC,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,mCAAmC,GACrE,OAAO,CAAC,IAAI,CAAC,CAUf"}
1
+ {"version":3,"file":"memory-settings-hydration.d.ts","sourceRoot":"","sources":["../../src/session/memory-settings-hydration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAC9G,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAG5F,yDAAyD;AACzD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,2DAA2D;AAC3D,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED;;;;GAIG;AACH,UAAU,aAAa;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE;QAAE,QAAQ,EAAE,WAAW,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,CAAC;IACtD,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KACvD,CAAC;CACH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,MAAM,WAAW,8BAA+B,SAAQ,qBAAqB;IAC3E,QAAQ,CAAC,QAAQ,EAAE;QAAE,aAAa,IAAI,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,WAAW,mCAAmC;IAClD,4FAA4F;IAC5F,aAAa,EAAE;QACb,QAAQ,EAAE,IAAI,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;KAC1E,CAAC;IACF,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,EACvC,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,mCAAmC,GACrE,OAAO,CAAC,IAAI,CAAC,CAsBf"}
@@ -1,3 +1,4 @@
1
+ import { hasResolvedOrg, seedSessionOrg } from "./org-seed.js";
1
2
  import { DEFAULT_OM_MODEL_ID } from "@mastra/code-sdk/constants";
2
3
  //#region src/session/memory-settings-hydration.ts
3
4
  /** Default thresholds mirror the TUI `/om` fallbacks. */
@@ -28,27 +29,44 @@ async function applyStoredMemorySettings(session, record, fallbackOmModelId) {
28
29
  if (Object.keys(updates).length > 0) await session.state.set(updates);
29
30
  }
30
31
  /**
31
- * Seed a freshly created controller session's observational-memory settings
32
- * from the owner's stored `memory-settings` row. Registered as a blocking
33
- * session-created listener so the seed lands before the caller can start a run.
32
+ * Seed a freshly created controller session's tenant org and its
33
+ * observational-memory settings from the owner's source-control row. Registered
34
+ * as a blocking session-created listener so the seed lands before the caller can
35
+ * start a run.
34
36
  *
35
- * Sessions tagged `factoryProjectId` (work/review runs, created with that tag)
36
- * hydrate through the start coordinator; sessions without a GitHub
37
- * source-control row (e.g. chat-only channel sessions) hydrate through
38
- * `hydrateFactorySession` with their own resolved tenant. Both are skipped
39
- * here. Best-effort: failures are logged, never thrown.
37
+ * The org seed matters beyond settings. Subconscious knowledge capture scopes
38
+ * every node and record on `factoryOrgId`; before the SDK refusal guard,
39
+ * missing it made capture substitute the session owner id. For web chat sessions
40
+ * that is the agent controller's own id rather than a tenant, so captured
41
+ * knowledge landed under an org rung no reader ever queries. Same rule as the
42
+ * start coordinator: the org
43
+ * comes from the row the session was created from, never improvised from an
44
+ * owner id.
45
+ *
46
+ * Memory settings for sessions tagged `factoryProjectId` (work/review runs) are
47
+ * owned by the start coordinator, and sessions without a GitHub source-control
48
+ * row (e.g. chat-only channel sessions) hydrate through `hydrateFactorySession`
49
+ * with their own resolved tenant; both are skipped here. The org seed is not
50
+ * skipped on the tag alone: a web chat session persists `factoryProjectId` from
51
+ * its browser seed, so on resume it carries the tag without ever having been
52
+ * through the coordinator. Best-effort: failures are logged, never thrown.
40
53
  */
41
54
  async function hydrateSessionMemorySettings(session, { sourceControl, memorySettings }) {
42
- if (session.state.get()?.factoryProjectId) return;
55
+ const state = session.state.get() ?? {};
56
+ const isFactoryRun = Boolean(state.factoryProjectId);
57
+ if (isFactoryRun && hasResolvedOrg(state.factoryOrgId)) return;
43
58
  try {
44
59
  const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());
60
+ await seedSessionOrg(session, record?.orgId);
45
61
  if (!record) return;
62
+ if (isFactoryRun) return;
46
63
  await applyStoredMemorySettings(session, await memorySettings.get({
47
64
  orgId: record.orgId,
48
65
  userId: record.userId
49
66
  }));
50
67
  } catch (error) {
51
68
  console.warn("[Factory memory-settings hydration] Unable to apply stored memory settings.", error);
69
+ if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, void 0);
52
70
  }
53
71
  }
54
72
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import { DEFAULT_OM_MODEL_ID } from '@mastra/code-sdk/constants';\n\nimport type { MemorySettingsRecord, MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nexport const DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nexport const DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRoleSlice {\n modelId: () => string | undefined;\n switchModel: (args: { modelId: string }) => Promise<unknown>;\n}\n\n/**\n * Session-state fields memory-settings hydration writes. The index signatures\n * mirror `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n}\n\n/** The slice of a session needed to apply stored observational-memory settings. */\nexport interface OMConfigurableSession {\n om: { observer: OMRoleSlice; reflector: OMRoleSlice };\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * Apply a stored memory-settings row onto a session, so the DB — not whatever\n * happens to sit in persisted session state (e.g. a stale boot-time seed from\n * before memory settings moved to the DB) — is what the web surface reads and\n * what the session's OM actually runs with. The row is authoritative: knobs\n * without a stored value reset to the built-in defaults. This is the single\n * application path shared by the settings routes, coordinator hydration, and\n * the web session boot seed.\n */\nexport async function applyStoredMemorySettings(\n session: OMConfigurableSession,\n record: MemorySettingsRecord | null,\n fallbackOmModelId?: string,\n): Promise<void> {\n for (const role of ['observer', 'reflector'] as const) {\n const stored = role === 'observer' ? record?.observerModelId : record?.reflectorModelId;\n const target = stored ?? fallbackOmModelId ?? DEFAULT_OM_MODEL_ID;\n if (session.om[role].modelId() !== target) {\n await session.om[role].switchModel({ modelId: target });\n }\n }\n const state = session.state.get() ?? {};\n const updates: OMStateWrites = {};\n const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;\n if (state.observationThreshold !== observationThreshold) {\n updates.observationThreshold = observationThreshold;\n }\n const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;\n if (state.reflectionThreshold !== reflectionThreshold) {\n updates.reflectionThreshold = reflectionThreshold;\n }\n const observeAttachments = record?.observeAttachments ?? 'auto';\n if ((state.observeAttachments ?? 'auto') !== observeAttachments) {\n updates.observeAttachments = observeAttachments;\n }\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n}\n\nexport interface MemorySettingsHydrationSession extends OMConfigurableSession {\n readonly identity: { getResourceId(): string };\n}\n\nexport interface MemorySettingsHydrationDependencies {\n /** GitHub-integration source-control rows — the only creator of web user sessions today. */\n sourceControl: {\n sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;\n };\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Seed a freshly created controller session's observational-memory settings\n * from the owner's stored `memory-settings` row. Registered as a blocking\n * session-created listener so the seed lands before the caller can start a run.\n *\n * Sessions tagged `factoryProjectId` (work/review runs, created with that tag)\n * hydrate through the start coordinator; sessions without a GitHub\n * source-control row (e.g. chat-only channel sessions) hydrate through\n * `hydrateFactorySession` with their own resolved tenant. Both are skipped\n * here. Best-effort: failures are logged, never thrown.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl, memorySettings }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n if (session.state.get()?.factoryProjectId) return;\n try {\n const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());\n if (!record) return;\n const settings = await memorySettings.get({ orgId: record.orgId, userId: record.userId });\n await applyStoredMemorySettings(session, settings);\n } catch (error) {\n console.warn('[Factory memory-settings hydration] Unable to apply stored memory settings.', error);\n }\n}\n"],"mappings":";;;AAMA,MAAa,gCAAgC;AAC7C,MAAa,+BAA+B;;;;;;;;;;AAuC5C,eAAsB,0BACpB,SACA,QACA,mBACe;CACf,KAAK,MAAM,QAAQ,CAAC,YAAY,WAAW,GAAY;EAErD,MAAM,UADS,SAAS,aAAa,QAAQ,kBAAkB,QAAQ,qBAC9C,qBAAqB;EAC9C,IAAI,QAAQ,GAAG,KAAK,CAAC,QAAQ,MAAM,QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,EAAE,SAAS,OAAO,CAAC;CAE1D;CACA,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,UAAyB,CAAC;CAChC,MAAM,uBAAuB,QAAQ,wBAAA;CACrC,IAAI,MAAM,yBAAyB,sBACjC,QAAQ,uBAAuB;CAEjC,MAAM,sBAAsB,QAAQ,uBAAA;CACpC,IAAI,MAAM,wBAAwB,qBAChC,QAAQ,sBAAsB;CAEhC,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,KAAK,MAAM,sBAAsB,YAAY,oBAC3C,QAAQ,qBAAqB;CAE/B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;AACtE;;;;;;;;;;;;AAyBA,eAAsB,6BACpB,SACA,EAAE,eAAe,kBACF;CACf,IAAI,QAAQ,MAAM,IAAI,CAAC,EAAE,kBAAkB;CAC3C,IAAI;EACF,MAAM,SAAS,MAAM,cAAc,SAAS,eAAe,QAAQ,SAAS,cAAc,CAAC;EAC3F,IAAI,CAAC,QAAQ;EAEb,MAAM,0BAA0B,SAAS,MADlB,eAAe,IAAI;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO,CAAC,CACvC;CACnD,SAAS,OAAO;EACd,QAAQ,KAAK,+EAA+E,KAAK;CACnG;AACF"}
1
+ {"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import { DEFAULT_OM_MODEL_ID } from '@mastra/code-sdk/constants';\n\nimport type { MemorySettingsRecord, MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { hasResolvedOrg, seedSessionOrg } from './org-seed.js';\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nexport const DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nexport const DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRoleSlice {\n modelId: () => string | undefined;\n switchModel: (args: { modelId: string }) => Promise<unknown>;\n}\n\n/**\n * Session-state fields memory-settings hydration writes. The index signatures\n * mirror `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n factoryOrgId?: string;\n}\n\n/** The slice of a session needed to apply stored observational-memory settings. */\nexport interface OMConfigurableSession {\n om: { observer: OMRoleSlice; reflector: OMRoleSlice };\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * Apply a stored memory-settings row onto a session, so the DB — not whatever\n * happens to sit in persisted session state (e.g. a stale boot-time seed from\n * before memory settings moved to the DB) — is what the web surface reads and\n * what the session's OM actually runs with. The row is authoritative: knobs\n * without a stored value reset to the built-in defaults. This is the single\n * application path shared by the settings routes, coordinator hydration, and\n * the web session boot seed.\n */\nexport async function applyStoredMemorySettings(\n session: OMConfigurableSession,\n record: MemorySettingsRecord | null,\n fallbackOmModelId?: string,\n): Promise<void> {\n for (const role of ['observer', 'reflector'] as const) {\n const stored = role === 'observer' ? record?.observerModelId : record?.reflectorModelId;\n const target = stored ?? fallbackOmModelId ?? DEFAULT_OM_MODEL_ID;\n if (session.om[role].modelId() !== target) {\n await session.om[role].switchModel({ modelId: target });\n }\n }\n const state = session.state.get() ?? {};\n const updates: OMStateWrites = {};\n const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;\n if (state.observationThreshold !== observationThreshold) {\n updates.observationThreshold = observationThreshold;\n }\n const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;\n if (state.reflectionThreshold !== reflectionThreshold) {\n updates.reflectionThreshold = reflectionThreshold;\n }\n const observeAttachments = record?.observeAttachments ?? 'auto';\n if ((state.observeAttachments ?? 'auto') !== observeAttachments) {\n updates.observeAttachments = observeAttachments;\n }\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n}\n\nexport interface MemorySettingsHydrationSession extends OMConfigurableSession {\n readonly identity: { getResourceId(): string };\n}\n\nexport interface MemorySettingsHydrationDependencies {\n /** GitHub-integration source-control rows — the only creator of web user sessions today. */\n sourceControl: {\n sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;\n };\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Seed a freshly created controller session's tenant org and its\n * observational-memory settings from the owner's source-control row. Registered\n * as a blocking session-created listener so the seed lands before the caller can\n * start a run.\n *\n * The org seed matters beyond settings. Subconscious knowledge capture scopes\n * every node and record on `factoryOrgId`; before the SDK refusal guard,\n * missing it made capture substitute the session owner id. For web chat sessions\n * that is the agent controller's own id rather than a tenant, so captured\n * knowledge landed under an org rung no reader ever queries. Same rule as the\n * start coordinator: the org\n * comes from the row the session was created from, never improvised from an\n * owner id.\n *\n * Memory settings for sessions tagged `factoryProjectId` (work/review runs) are\n * owned by the start coordinator, and sessions without a GitHub source-control\n * row (e.g. chat-only channel sessions) hydrate through `hydrateFactorySession`\n * with their own resolved tenant; both are skipped here. The org seed is not\n * skipped on the tag alone: a web chat session persists `factoryProjectId` from\n * its browser seed, so on resume it carries the tag without ever having been\n * through the coordinator. Best-effort: failures are logged, never thrown.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl, memorySettings }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n const state = session.state.get() ?? {};\n const isFactoryRun = Boolean(state.factoryProjectId);\n // A coordinator-hydrated run already carries both halves. Nothing to add.\n if (isFactoryRun && hasResolvedOrg(state.factoryOrgId)) return;\n try {\n const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());\n // No row, or a row whose org is blank, leaves the session with no tenant.\n // Mark it rather than returning silently: an unmarked projectless factory\n // session is indistinguishable from a local one, and capture would file it\n // under the local scope — the same bug wearing a different rung.\n await seedSessionOrg(session, record?.orgId);\n if (!record) return;\n if (isFactoryRun) return;\n const settings = await memorySettings.get({ orgId: record.orgId, userId: record.userId });\n await applyStoredMemorySettings(session, settings);\n } catch (error) {\n console.warn('[Factory memory-settings hydration] Unable to apply stored memory settings.', error);\n // A failed lookup is an unresolved org, not an absent one — unless the seed\n // already landed and a later step is what threw.\n if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, undefined);\n }\n}\n"],"mappings":";;;;AAOA,MAAa,gCAAgC;AAC7C,MAAa,+BAA+B;;;;;;;;;;AAwC5C,eAAsB,0BACpB,SACA,QACA,mBACe;CACf,KAAK,MAAM,QAAQ,CAAC,YAAY,WAAW,GAAY;EAErD,MAAM,UADS,SAAS,aAAa,QAAQ,kBAAkB,QAAQ,qBAC9C,qBAAqB;EAC9C,IAAI,QAAQ,GAAG,KAAK,CAAC,QAAQ,MAAM,QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,EAAE,SAAS,OAAO,CAAC;CAE1D;CACA,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,UAAyB,CAAC;CAChC,MAAM,uBAAuB,QAAQ,wBAAA;CACrC,IAAI,MAAM,yBAAyB,sBACjC,QAAQ,uBAAuB;CAEjC,MAAM,sBAAsB,QAAQ,uBAAA;CACpC,IAAI,MAAM,wBAAwB,qBAChC,QAAQ,sBAAsB;CAEhC,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,KAAK,MAAM,sBAAsB,YAAY,oBAC3C,QAAQ,qBAAqB;CAE/B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;AACtE;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,eAAsB,6BACpB,SACA,EAAE,eAAe,kBACF;CACf,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,eAAe,QAAQ,MAAM,gBAAgB;CAEnD,IAAI,gBAAgB,eAAe,MAAM,YAAY,GAAG;CACxD,IAAI;EACF,MAAM,SAAS,MAAM,cAAc,SAAS,eAAe,QAAQ,SAAS,cAAc,CAAC;EAK3F,MAAM,eAAe,SAAS,QAAQ,KAAK;EAC3C,IAAI,CAAC,QAAQ;EACb,IAAI,cAAc;EAElB,MAAM,0BAA0B,SAAS,MADlB,eAAe,IAAI;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO,CAAC,CACvC;CACnD,SAAS,OAAO;EACd,QAAQ,KAAK,+EAA+E,KAAK;EAGjG,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,EAAE,cAAc,MAAM,eAAe,SAAS,KAAA,CAAS;CACjF;AACF"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The tenant organization a session's knowledge is scoped to.
3
+ *
4
+ * Subconscious knowledge capture scopes every node and record on
5
+ * `factoryOrgId`. A session that reaches the capture seam without one used to
6
+ * fall back to the session owner id — for factory sessions the agent
7
+ * controller's own id — so the knowledge landed under an org rung no reader
8
+ * ever queries. The write succeeded and the read could never see it.
9
+ *
10
+ * The fix is that every session-creation path seeds the org it already holds,
11
+ * and a path that cannot resolve one marks the session `factoryOrgUnresolved`
12
+ * so the capture side refuses loudly instead of inventing an identity. "No
13
+ * project id" is not a proxy for "not a factory session" — chat sessions and
14
+ * Slack channel sessions are factory-owned and carry no project id — which is
15
+ * why the unresolved case needs its own explicit marker.
16
+ */
17
+ /**
18
+ * Session-state fields org seeding writes. The index signatures mirror
19
+ * `MastraCodeState` so a concrete `Session.state.set(Partial<MastraCodeState>)`
20
+ * stays assignable to this minimal surface (contravariant parameter check).
21
+ */
22
+ export interface OrgSeedStateWrites {
23
+ [key: string]: unknown;
24
+ [key: `subagentModelId_${string}`]: string | undefined;
25
+ factoryOrgId?: string;
26
+ factoryOrgUnresolved?: boolean;
27
+ }
28
+ /** The slice of a session needed to seed its organization. */
29
+ export interface OrgSeedableSession {
30
+ state: {
31
+ get: () => Record<string, unknown> | undefined;
32
+ set: (updates: OrgSeedStateWrites) => Promise<void> | void;
33
+ };
34
+ }
35
+ /**
36
+ * A request context carrying the tenant on its `user` key. Slack stamps
37
+ * `{ id, organizationId }` and the GitHub webhook `{ workosId, organizationId }`,
38
+ * so only the shared `organizationId` field may be read here.
39
+ */
40
+ export interface OrgBearingRequestContext {
41
+ get: (key: string) => unknown;
42
+ }
43
+ /** Read the tenant org off a request context's `user` key, if there is one. */
44
+ export declare function readRequestContextOrgId(requestContext: OrgBearingRequestContext | undefined): string | undefined;
45
+ /**
46
+ * Whether a session state value counts as a resolved organization.
47
+ *
48
+ * The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the
49
+ * recovery guards have to agree with it: a whitespace-only value that reads as
50
+ * truthy here would look resolved to a heal path while capture still refuses,
51
+ * and nothing would ever repair it. Not every seam routes its seed through
52
+ * `seedSessionOrg`, so this cannot be assumed away.
53
+ */
54
+ export declare function hasResolvedOrg(orgId: unknown): boolean;
55
+ /**
56
+ * Seed the session's organization, or mark it unresolved when there is none.
57
+ *
58
+ * An absent, empty, or whitespace-only org is a refusal, not a fallback: a
59
+ * blank org rung is not something canonicalization can save. A successful
60
+ * resolve also clears a stale marker, because the session-start hook runs at
61
+ * most once per session per process and nothing else would ever clear it.
62
+ *
63
+ * Best-effort by contract — every caller is a session-created listener that
64
+ * must not sink a run that is otherwise ready.
65
+ */
66
+ export declare function seedSessionOrg(session: OrgSeedableSession, orgId: string | null | undefined): Promise<void>;
67
+ //# sourceMappingURL=org-seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-seed.d.ts","sourceRoot":"","sources":["../../src/session/org-seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC5D,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/B;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,wBAAwB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMhH;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjH"}
@@ -0,0 +1,53 @@
1
+ //#region src/session/org-seed.ts
2
+ /** Read the tenant org off a request context's `user` key, if there is one. */
3
+ function readRequestContextOrgId(requestContext) {
4
+ if (!requestContext) return void 0;
5
+ const user = requestContext.get("user");
6
+ if (!user || typeof user !== "object") return void 0;
7
+ const orgId = user.organizationId;
8
+ return typeof orgId === "string" ? orgId : void 0;
9
+ }
10
+ /**
11
+ * Whether a session state value counts as a resolved organization.
12
+ *
13
+ * The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the
14
+ * recovery guards have to agree with it: a whitespace-only value that reads as
15
+ * truthy here would look resolved to a heal path while capture still refuses,
16
+ * and nothing would ever repair it. Not every seam routes its seed through
17
+ * `seedSessionOrg`, so this cannot be assumed away.
18
+ */
19
+ function hasResolvedOrg(orgId) {
20
+ return typeof orgId === "string" && orgId.trim().length > 0;
21
+ }
22
+ /**
23
+ * Seed the session's organization, or mark it unresolved when there is none.
24
+ *
25
+ * An absent, empty, or whitespace-only org is a refusal, not a fallback: a
26
+ * blank org rung is not something canonicalization can save. A successful
27
+ * resolve also clears a stale marker, because the session-start hook runs at
28
+ * most once per session per process and nothing else would ever clear it.
29
+ *
30
+ * Best-effort by contract — every caller is a session-created listener that
31
+ * must not sink a run that is otherwise ready.
32
+ */
33
+ async function seedSessionOrg(session, orgId) {
34
+ const resolved = typeof orgId === "string" ? orgId.trim() : "";
35
+ if (typeof session.state?.get !== "function" || typeof session.state?.set !== "function") return;
36
+ try {
37
+ const state = session.state.get() ?? {};
38
+ if (!resolved) {
39
+ if (state.factoryOrgUnresolved !== true) await session.state.set({ factoryOrgUnresolved: true });
40
+ return;
41
+ }
42
+ const updates = {};
43
+ if (state.factoryOrgId !== resolved) updates.factoryOrgId = resolved;
44
+ if (state.factoryOrgUnresolved) updates.factoryOrgUnresolved = false;
45
+ if (Object.keys(updates).length > 0) await session.state.set(updates);
46
+ } catch (error) {
47
+ console.warn("[Factory org seed] Unable to record the session organization.", error);
48
+ }
49
+ }
50
+ //#endregion
51
+ export { hasResolvedOrg, readRequestContextOrgId, seedSessionOrg };
52
+
53
+ //# sourceMappingURL=org-seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-seed.js","names":[],"sources":["../../src/session/org-seed.ts"],"sourcesContent":["/**\n * The tenant organization a session's knowledge is scoped to.\n *\n * Subconscious knowledge capture scopes every node and record on\n * `factoryOrgId`. A session that reaches the capture seam without one used to\n * fall back to the session owner id — for factory sessions the agent\n * controller's own id — so the knowledge landed under an org rung no reader\n * ever queries. The write succeeded and the read could never see it.\n *\n * The fix is that every session-creation path seeds the org it already holds,\n * and a path that cannot resolve one marks the session `factoryOrgUnresolved`\n * so the capture side refuses loudly instead of inventing an identity. \"No\n * project id\" is not a proxy for \"not a factory session\" — chat sessions and\n * Slack channel sessions are factory-owned and carry no project id — which is\n * why the unresolved case needs its own explicit marker.\n */\n\n/**\n * Session-state fields org seeding writes. The index signatures mirror\n * `MastraCodeState` so a concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\nexport interface OrgSeedStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n factoryOrgId?: string;\n factoryOrgUnresolved?: boolean;\n}\n\n/** The slice of a session needed to seed its organization. */\nexport interface OrgSeedableSession {\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OrgSeedStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * A request context carrying the tenant on its `user` key. Slack stamps\n * `{ id, organizationId }` and the GitHub webhook `{ workosId, organizationId }`,\n * so only the shared `organizationId` field may be read here.\n */\nexport interface OrgBearingRequestContext {\n get: (key: string) => unknown;\n}\n\n/** Read the tenant org off a request context's `user` key, if there is one. */\nexport function readRequestContextOrgId(requestContext: OrgBearingRequestContext | undefined): string | undefined {\n if (!requestContext) return undefined;\n const user = requestContext.get('user');\n if (!user || typeof user !== 'object') return undefined;\n const orgId = (user as { organizationId?: unknown }).organizationId;\n return typeof orgId === 'string' ? orgId : undefined;\n}\n\n/**\n * Whether a session state value counts as a resolved organization.\n *\n * The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the\n * recovery guards have to agree with it: a whitespace-only value that reads as\n * truthy here would look resolved to a heal path while capture still refuses,\n * and nothing would ever repair it. Not every seam routes its seed through\n * `seedSessionOrg`, so this cannot be assumed away.\n */\nexport function hasResolvedOrg(orgId: unknown): boolean {\n return typeof orgId === 'string' && orgId.trim().length > 0;\n}\n\n/**\n * Seed the session's organization, or mark it unresolved when there is none.\n *\n * An absent, empty, or whitespace-only org is a refusal, not a fallback: a\n * blank org rung is not something canonicalization can save. A successful\n * resolve also clears a stale marker, because the session-start hook runs at\n * most once per session per process and nothing else would ever clear it.\n *\n * Best-effort by contract — every caller is a session-created listener that\n * must not sink a run that is otherwise ready.\n */\nexport async function seedSessionOrg(session: OrgSeedableSession, orgId: string | null | undefined): Promise<void> {\n const resolved = typeof orgId === 'string' ? orgId.trim() : '';\n // Some session shapes (approval stubs, lightweight doubles) carry no state at\n // all. There is nothing to seed and nothing to mark, so this is not a warning.\n if (typeof session.state?.get !== 'function' || typeof session.state?.set !== 'function') return;\n try {\n const state = session.state.get() ?? {};\n if (!resolved) {\n if (state.factoryOrgUnresolved !== true) {\n await session.state.set({ factoryOrgUnresolved: true });\n }\n return;\n }\n const updates: OrgSeedStateWrites = {};\n if (state.factoryOrgId !== resolved) updates.factoryOrgId = resolved;\n if (state.factoryOrgUnresolved) updates.factoryOrgUnresolved = false;\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n } catch (error) {\n console.warn('[Factory org seed] Unable to record the session organization.', error);\n }\n}\n"],"mappings":";;AA+CA,SAAgB,wBAAwB,gBAA0E;CAChH,IAAI,CAAC,gBAAgB,OAAO,KAAA;CAC5B,MAAM,OAAO,eAAe,IAAI,MAAM;CACtC,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO,KAAA;CAC9C,MAAM,QAAS,KAAsC;CACrD,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C;;;;;;;;;;AAWA,SAAgB,eAAe,OAAyB;CACtD,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS;AAC5D;;;;;;;;;;;;AAaA,eAAsB,eAAe,SAA6B,OAAiD;CACjH,MAAM,WAAW,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI;CAG5D,IAAI,OAAO,QAAQ,OAAO,QAAQ,cAAc,OAAO,QAAQ,OAAO,QAAQ,YAAY;CAC1F,IAAI;EACF,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;EACtC,IAAI,CAAC,UAAU;GACb,IAAI,MAAM,yBAAyB,MACjC,MAAM,QAAQ,MAAM,IAAI,EAAE,sBAAsB,KAAK,CAAC;GAExD;EACF;EACA,MAAM,UAA8B,CAAC;EACrC,IAAI,MAAM,iBAAiB,UAAU,QAAQ,eAAe;EAC5D,IAAI,MAAM,sBAAsB,QAAQ,uBAAuB;EAC/D,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;CACtE,SAAS,OAAO;EACd,QAAQ,KAAK,iEAAiE,KAAK;CACrF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.10.2-alpha.0",
3
+ "version": "0.10.2-alpha.2",
4
4
  "description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -53,9 +53,9 @@
53
53
  "zod": "^4.3.6",
54
54
  "@mastra/auth-studio": "1.3.4",
55
55
  "@mastra/auth-workos": "1.6.4",
56
- "@mastra/core": "1.63.1-alpha.0",
56
+ "@mastra/code-sdk": "1.5.2-alpha.2",
57
57
  "@mastra/slack": "1.6.2",
58
- "@mastra/code-sdk": "1.5.2-alpha.0"
58
+ "@mastra/core": "1.63.1-alpha.2"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "22.20.1",
@@ -64,10 +64,10 @@
64
64
  "typescript": "^6.0.3",
65
65
  "typescript-eslint": "^8.57.0",
66
66
  "vitest": "4.1.10",
67
- "@internal/lint": "0.0.127",
68
- "@mastra/libsql": "1.22.0",
69
- "@mastra/pg": "1.22.0",
70
- "@internal/types-builder": "0.0.102"
67
+ "@mastra/libsql": "1.22.1-alpha.0",
68
+ "@internal/types-builder": "0.0.102",
69
+ "@mastra/pg": "1.22.1-alpha.0",
70
+ "@internal/lint": "0.0.127"
71
71
  },
72
72
  "engines": {
73
73
  "node": ">=22.19.0"