@mastra/factory 0.8.0-alpha.8 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +660 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +42 -4
  4. package/dist/factory.js.map +1 -1
  5. package/dist/integrations/base.d.ts +8 -0
  6. package/dist/integrations/base.d.ts.map +1 -1
  7. package/dist/integrations/github/app-identity.d.ts +43 -0
  8. package/dist/integrations/github/app-identity.d.ts.map +1 -0
  9. package/dist/integrations/github/app-identity.js +62 -0
  10. package/dist/integrations/github/app-identity.js.map +1 -0
  11. package/dist/integrations/github/integration.d.ts +9 -0
  12. package/dist/integrations/github/integration.d.ts.map +1 -1
  13. package/dist/integrations/github/integration.js +9 -1
  14. package/dist/integrations/github/integration.js.map +1 -1
  15. package/dist/integrations/github/reconcile-worker.d.ts +5 -0
  16. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  17. package/dist/integrations/github/reconcile-worker.js +7 -0
  18. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  19. package/dist/integrations/github/routes.d.ts.map +1 -1
  20. package/dist/integrations/github/routes.js +11 -6
  21. package/dist/integrations/github/routes.js.map +1 -1
  22. package/dist/integrations/github/rules.d.ts +8 -0
  23. package/dist/integrations/github/rules.d.ts.map +1 -1
  24. package/dist/integrations/github/rules.js +30 -8
  25. package/dist/integrations/github/rules.js.map +1 -1
  26. package/dist/integrations/github/sandbox.d.ts +39 -6
  27. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  28. package/dist/integrations/github/sandbox.js +48 -14
  29. package/dist/integrations/github/sandbox.js.map +1 -1
  30. package/dist/integrations/github/webhook.d.ts +39 -0
  31. package/dist/integrations/github/webhook.d.ts.map +1 -1
  32. package/dist/integrations/github/webhook.js +74 -13
  33. package/dist/integrations/github/webhook.js.map +1 -1
  34. package/dist/integrations/linear/integration.d.ts.map +1 -1
  35. package/dist/integrations/linear/integration.js +1 -0
  36. package/dist/integrations/linear/integration.js.map +1 -1
  37. package/dist/integrations/linear/routes.d.ts +10 -0
  38. package/dist/integrations/linear/routes.d.ts.map +1 -1
  39. package/dist/integrations/linear/routes.js +35 -1
  40. package/dist/integrations/linear/routes.js.map +1 -1
  41. package/dist/integrations/linear/rules.d.ts.map +1 -1
  42. package/dist/integrations/linear/rules.js +2 -1
  43. package/dist/integrations/linear/rules.js.map +1 -1
  44. package/dist/integrations/platform/github/event-worker.d.ts +11 -0
  45. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  46. package/dist/integrations/platform/github/event-worker.js +64 -16
  47. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  48. package/dist/integrations/platform/github/integration.d.ts +12 -0
  49. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  50. package/dist/integrations/platform/github/integration.js +47 -4
  51. package/dist/integrations/platform/github/integration.js.map +1 -1
  52. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  53. package/dist/integrations/platform/linear/integration.js +1 -0
  54. package/dist/integrations/platform/linear/integration.js.map +1 -1
  55. package/dist/integrations/slack/slack.d.ts.map +1 -1
  56. package/dist/integrations/slack/slack.js +2 -1
  57. package/dist/integrations/slack/slack.js.map +1 -1
  58. package/dist/routes/config.d.ts +12 -6
  59. package/dist/routes/config.d.ts.map +1 -1
  60. package/dist/routes/config.js +97 -56
  61. package/dist/routes/config.js.map +1 -1
  62. package/dist/routes/fs.d.ts.map +1 -1
  63. package/dist/routes/fs.js +2 -0
  64. package/dist/routes/fs.js.map +1 -1
  65. package/dist/routes/intake.d.ts +15 -0
  66. package/dist/routes/intake.d.ts.map +1 -1
  67. package/dist/routes/intake.js +65 -2
  68. package/dist/routes/intake.js.map +1 -1
  69. package/dist/routes/knowledge.d.ts +152 -0
  70. package/dist/routes/knowledge.d.ts.map +1 -0
  71. package/dist/routes/knowledge.js +442 -0
  72. package/dist/routes/knowledge.js.map +1 -0
  73. package/dist/routes/projects.d.ts +9 -1
  74. package/dist/routes/projects.d.ts.map +1 -1
  75. package/dist/routes/projects.js +8 -0
  76. package/dist/routes/projects.js.map +1 -1
  77. package/dist/routes/surface.d.ts +6 -0
  78. package/dist/routes/surface.d.ts.map +1 -1
  79. package/dist/routes/surface.js +10 -0
  80. package/dist/routes/surface.js.map +1 -1
  81. package/dist/routes/work-items.d.ts.map +1 -1
  82. package/dist/routes/work-items.js +3 -1
  83. package/dist/routes/work-items.js.map +1 -1
  84. package/dist/rules/binding-context.d.ts.map +1 -1
  85. package/dist/rules/binding-context.js +4 -1
  86. package/dist/rules/binding-context.js.map +1 -1
  87. package/dist/rules/defaults.d.ts.map +1 -1
  88. package/dist/rules/defaults.js +87 -4
  89. package/dist/rules/defaults.js.map +1 -1
  90. package/dist/rules/dispatcher.d.ts +6 -0
  91. package/dist/rules/dispatcher.d.ts.map +1 -1
  92. package/dist/rules/dispatcher.js +118 -16
  93. package/dist/rules/dispatcher.js.map +1 -1
  94. package/dist/rules/processor.d.ts.map +1 -1
  95. package/dist/rules/processor.js +20 -7
  96. package/dist/rules/processor.js.map +1 -1
  97. package/dist/rules/resolve.d.ts +1 -0
  98. package/dist/rules/resolve.d.ts.map +1 -1
  99. package/dist/rules/resolve.js +3 -2
  100. package/dist/rules/resolve.js.map +1 -1
  101. package/dist/rules/start-coordinator.d.ts +2 -0
  102. package/dist/rules/start-coordinator.d.ts.map +1 -1
  103. package/dist/rules/start-coordinator.js +3 -1
  104. package/dist/rules/start-coordinator.js.map +1 -1
  105. package/dist/rules/terminal-cleanup.d.ts +23 -0
  106. package/dist/rules/terminal-cleanup.d.ts.map +1 -0
  107. package/dist/rules/terminal-cleanup.js +43 -0
  108. package/dist/rules/terminal-cleanup.js.map +1 -0
  109. package/dist/rules/tools.d.ts.map +1 -1
  110. package/dist/rules/tools.js +24 -2
  111. package/dist/rules/tools.js.map +1 -1
  112. package/dist/rules/transition-service.d.ts +3 -0
  113. package/dist/rules/transition-service.d.ts.map +1 -1
  114. package/dist/rules/transition-service.js +10 -6
  115. package/dist/rules/transition-service.js.map +1 -1
  116. package/dist/rules/types.d.ts +32 -3
  117. package/dist/rules/types.d.ts.map +1 -1
  118. package/dist/rules/types.js +2 -0
  119. package/dist/rules/types.js.map +1 -1
  120. package/dist/rules/validation.d.ts.map +1 -1
  121. package/dist/rules/validation.js +8 -3
  122. package/dist/rules/validation.js.map +1 -1
  123. package/dist/sandbox/base-checkpoint-triggers.d.ts +77 -0
  124. package/dist/sandbox/base-checkpoint-triggers.d.ts.map +1 -0
  125. package/dist/sandbox/base-checkpoint-triggers.js +137 -0
  126. package/dist/sandbox/base-checkpoint-triggers.js.map +1 -0
  127. package/dist/sandbox/base-checkpoint.d.ts +59 -0
  128. package/dist/sandbox/base-checkpoint.d.ts.map +1 -0
  129. package/dist/sandbox/base-checkpoint.js +145 -0
  130. package/dist/sandbox/base-checkpoint.js.map +1 -0
  131. package/dist/sandbox/fleet.d.ts +32 -0
  132. package/dist/sandbox/fleet.d.ts.map +1 -1
  133. package/dist/sandbox/fleet.js +39 -10
  134. package/dist/sandbox/fleet.js.map +1 -1
  135. package/dist/session/checkpoint-capture.d.ts +3 -2
  136. package/dist/session/checkpoint-capture.d.ts.map +1 -1
  137. package/dist/session/checkpoint-capture.js +3 -3
  138. package/dist/session/checkpoint-capture.js.map +1 -1
  139. package/dist/session/factory-session.d.ts.map +1 -1
  140. package/dist/session/factory-session.js +2 -1
  141. package/dist/session/factory-session.js.map +1 -1
  142. package/dist/session/filesystem-capture.d.ts +7 -0
  143. package/dist/session/filesystem-capture.d.ts.map +1 -1
  144. package/dist/session/filesystem-capture.js +41 -4
  145. package/dist/session/filesystem-capture.js.map +1 -1
  146. package/dist/session/model-pack-hydration.d.ts +55 -0
  147. package/dist/session/model-pack-hydration.d.ts.map +1 -0
  148. package/dist/session/model-pack-hydration.js +56 -0
  149. package/dist/session/model-pack-hydration.js.map +1 -0
  150. package/dist/skills/service.d.ts +9 -0
  151. package/dist/skills/service.d.ts.map +1 -1
  152. package/dist/skills/service.js +10 -1
  153. package/dist/skills/service.js.map +1 -1
  154. package/dist/storage/domains/intake/base.d.ts +36 -0
  155. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  156. package/dist/storage/domains/intake/base.js +91 -2
  157. package/dist/storage/domains/intake/base.js.map +1 -1
  158. package/dist/storage/domains/model-packs/base.d.ts +26 -0
  159. package/dist/storage/domains/model-packs/base.d.ts.map +1 -1
  160. package/dist/storage/domains/model-packs/base.js +102 -13
  161. package/dist/storage/domains/model-packs/base.js.map +1 -1
  162. package/dist/storage/domains/source-control/base.d.ts +47 -1
  163. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  164. package/dist/storage/domains/source-control/base.js +78 -19
  165. package/dist/storage/domains/source-control/base.js.map +1 -1
  166. package/dist/storage/domains/source-control/inmemory.d.ts +11 -3
  167. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  168. package/dist/storage/domains/source-control/inmemory.js +10 -1
  169. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  170. package/dist/storage/domains/work-items/base.d.ts +67 -1
  171. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  172. package/dist/storage/domains/work-items/base.js +183 -12
  173. package/dist/storage/domains/work-items/base.js.map +1 -1
  174. package/dist/workspace.d.ts +31 -2
  175. package/dist/workspace.d.ts.map +1 -1
  176. package/dist/workspace.js +225 -54
  177. package/dist/workspace.js.map +1 -1
  178. package/factory-skills/factory-plan/SKILL.md +1 -1
  179. package/factory-skills/factory-rereview/SKILL.md +3 -3
  180. package/factory-skills/factory-review/SKILL.md +3 -3
  181. package/factory-skills/factory-triage/SKILL.md +2 -0
  182. package/package.json +8 -8
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Read-only Mastra `apiRoutes` exposing the factory project's knowledge graph.
3
+ *
4
+ * Serves the Knowledge page in factory-ui: a polling graph snapshot (nodes
5
+ * as nodes, wikilink edges derived from record text), a node flyout payload
6
+ * with per-record provenance, and the recent activity feed. Every endpoint is a
7
+ * GET — this module never writes knowledge.
8
+ *
9
+ * Scoping is fail-closed: the org and resource rungs are derived server-side
10
+ * from the authenticated caller and the validated `:id` project. The DEFAULT
11
+ * view queries `[org:<orgId>, resource:<projectId>]` (org + project records).
12
+ * Thread-scoped records are reachable ONLY via an explicit, server-validated
13
+ * `threadId` query parameter (the drill-down view), which appends the thread
14
+ * rung to the query scope. A thread is drillable iff it produced knowledge
15
+ * visible under the caller's org/project prefix; unknown or cross-org threads
16
+ * 404 — never a silent fallback to the default view.
17
+ */
18
+ import type { ApiRoute } from '@mastra/core/server';
19
+ import type { KnowledgeScope, KnowledgeStorage } from '@mastra/core/storage';
20
+ import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
21
+ import type { RouteDependencies } from './route.js';
22
+ import { Route } from './route.js';
23
+ /** Window caps. Injectable at construction only — never per-request. */
24
+ export interface KnowledgeRouteLimits {
25
+ /** Max nodes in a graph snapshot (newest-first). */
26
+ maxNodes: number;
27
+ /** Max records parsed for edges per snapshot (newest-first). */
28
+ maxRecords: number;
29
+ /** Max fallback `resolveNode` store lookups per request (deduped per unique name+scope). */
30
+ maxFallbackLookups: number;
31
+ }
32
+ export interface KnowledgeRoutesDeps extends RouteDependencies {
33
+ /** Factory projects domain — validates the `:id` project belongs to the caller's org. */
34
+ projects: FactoryProjectsStorage;
35
+ /** Lazy handle to the knowledge storage domain; endpoints 503 when absent. */
36
+ knowledge: () => Promise<KnowledgeStorage | undefined>;
37
+ limits?: Partial<KnowledgeRouteLimits>;
38
+ }
39
+ /** A graph node. `recordCount` is window-derived (records inside the snapshot window only). */
40
+ export interface KnowledgeGraphNode {
41
+ id: string;
42
+ name: string;
43
+ kind: string;
44
+ scope: KnowledgeScope;
45
+ /** Deepest rung of the record's scope: org | resource | thread. */
46
+ rung: 'org' | 'resource' | 'thread';
47
+ /**
48
+ * True when a non-deleted pinned record's wikilinks reference ONLY this
49
+ * node (A9: multi-target pins mark their edges instead — the pin is
50
+ * about the relationship; a single-target pin has no edge to carry it).
51
+ */
52
+ pinned: boolean;
53
+ /** Records owned by this node INSIDE the snapshot window (not a total). */
54
+ recordCount: number;
55
+ createdAt: string;
56
+ updatedAt: string;
57
+ }
58
+ export interface KnowledgeGraphEdge {
59
+ id: string;
60
+ /** The owning node of the record (its `node`). */
61
+ source: string;
62
+ /** The wikilink-resolved node. */
63
+ target: string;
64
+ /**
65
+ * Always 'wikilink': the record's `node` is the edge SOURCE, so the
66
+ * plan's "parent link" collapses into the wikilink edge — nodes carry no
67
+ * separate parent field to derive a second edge type from.
68
+ */
69
+ type: 'wikilink';
70
+ /** The record whose text produced the edge. */
71
+ recordId: string;
72
+ /**
73
+ * True when the edge is derived from a PINNED record linking two nodes —
74
+ * the pin marks the relationship, so the accent lives on the edge (A9).
75
+ */
76
+ pinned?: boolean;
77
+ }
78
+ /**
79
+ * A knowledge record as a first-class graph element (A11): every record in the window,
80
+ * with the in-window nodes it touches. The client renders by arity —
81
+ * 1 node: a small dot linked to it; 2: the connecting line; 3+: a midpoint
82
+ * junction splitting to each node. Pin records have their hidden reserved
83
+ * owner omitted, so their arity comes purely from wikilink targets.
84
+ */
85
+ export interface KnowledgeGraphRecord {
86
+ /** The record id. */
87
+ id: string;
88
+ /** Owner node first (omitted for pins), then resolved wikilink targets. */
89
+ nodeIds: string[];
90
+ pinned: boolean;
91
+ /** Record text, truncated for hover cards. */
92
+ text: string;
93
+ }
94
+ export interface KnowledgeGraphPayload {
95
+ view: 'project' | 'thread';
96
+ threadId?: string;
97
+ nodes: KnowledgeGraphNode[];
98
+ edges: KnowledgeGraphEdge[];
99
+ records: KnowledgeGraphRecord[];
100
+ /** True when the node or record window cap was hit (newest-first window). */
101
+ truncated: boolean;
102
+ /** Wikilink targets that resolved in the store but fell outside the node window. */
103
+ outOfWindow: Array<{
104
+ id: string;
105
+ name: string;
106
+ }>;
107
+ /** Unique unknown names skipped once the fallback-lookup cap was hit. */
108
+ unresolvedCapped: {
109
+ count: number;
110
+ names: string[];
111
+ };
112
+ /** Pin counts per rung of the active view (thread is null in the default view). */
113
+ pinCensus: {
114
+ resource: number;
115
+ thread: number | null;
116
+ };
117
+ /** Change hint: newest knowledge activity id (per-process monotonic — hint only). */
118
+ version: string | null;
119
+ }
120
+ export interface KnowledgeNodeRecordPayload {
121
+ id: string;
122
+ node: string;
123
+ /** 'owned' when the node is the record's parent, 'mentions' when it only wikilinks it. */
124
+ relation: 'owned' | 'mentions';
125
+ text: string;
126
+ scope: KnowledgeScope;
127
+ rung: 'org' | 'resource' | 'thread';
128
+ sourceThreadId: string;
129
+ capturedAt: string;
130
+ when?: string;
131
+ pinned: boolean;
132
+ metadata?: Record<string, unknown>;
133
+ }
134
+ export interface KnowledgeNodePayload {
135
+ node: {
136
+ id: string;
137
+ name: string;
138
+ kind: string;
139
+ content: string;
140
+ scope: KnowledgeScope;
141
+ rung: 'org' | 'resource' | 'thread';
142
+ createdAt: string;
143
+ updatedAt: string;
144
+ };
145
+ records: KnowledgeNodeRecordPayload[];
146
+ }
147
+ export declare class KnowledgeRoutes extends Route<KnowledgeRoutesDeps> {
148
+ #private;
149
+ constructor(deps: KnowledgeRoutesDeps);
150
+ routes(): ApiRoute[];
151
+ }
152
+ //# sourceMappingURL=knowledge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../../src/routes/knowledge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAkC,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAS7G,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAcnC,wEAAwE;AACxE,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAID,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,yFAAyF;IACzF,QAAQ,EAAE,sBAAsB,CAAC;IACjC,8EAA8E;IAC9E,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC;IACvD,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACxC;AAED,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,cAAc,CAAC;IACtB,mEAAmE;IACnE,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,QAAQ,CAAC;IACpC;;;;OAIG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,IAAI,EAAE,UAAU,CAAC;IACjB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,qBAAqB;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,6EAA6E;IAC7E,SAAS,EAAE,OAAO,CAAC;IACnB,oFAAoF;IACpF,WAAW,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,yEAAyE;IACzE,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACrD,mFAAmF;IACnF,SAAS,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACvD,qFAAqF;IACrF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,QAAQ,EAAE,OAAO,GAAG,UAAU,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,QAAQ,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,cAAc,CAAC;QACtB,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,QAAQ,CAAC;QACpC,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,EAAE,0BAA0B,EAAE,CAAC;CACvC;AA+GD,qBAAa,eAAgB,SAAQ,KAAK,CAAC,mBAAmB,CAAC;;gBAGjD,IAAI,EAAE,mBAAmB;IAqHrC,MAAM,IAAI,QAAQ,EAAE;CAiOrB"}
@@ -0,0 +1,442 @@
1
+ import { Route } from "./route.js";
2
+ import { registerApiRoute } from "@mastra/core/server";
3
+ import { canonicalizeKnowledgeScope, isKnowledgeScopeVisible, knowledgeScopeKey, parseKnowledgeWikilinks } from "@mastra/core/storage";
4
+ //#region src/routes/knowledge.ts
5
+ /** Reserved node that anchors pinned records (see subconscious/pinned.ts). */
6
+ const PINNED_NODE_NAME = "pinned";
7
+ /** Hover-card budget for record text shipped in the graph payload. */
8
+ const RECORD_TEXT_LIMIT = 240;
9
+ function truncateRecordText(text) {
10
+ return text.length > RECORD_TEXT_LIMIT ? `${text.slice(0, RECORD_TEXT_LIMIT - 1)}…` : text;
11
+ }
12
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
+ const DEFAULT_LIMITS = {
14
+ maxNodes: 500,
15
+ maxRecords: 2e3,
16
+ maxFallbackLookups: 100
17
+ };
18
+ function loose(c) {
19
+ return c;
20
+ }
21
+ function deepestRung(scope) {
22
+ let rung = "org";
23
+ for (const entry of scope) {
24
+ const ns = entry.slice(0, entry.indexOf(":"));
25
+ if (ns === "thread") return "thread";
26
+ if (ns === "resource") rung = "resource";
27
+ }
28
+ return rung;
29
+ }
30
+ function boundedThreadId(raw) {
31
+ if (!raw) return void 0;
32
+ const trimmed = raw.trim();
33
+ return trimmed.length > 0 && trimmed.length <= 512 ? trimmed : void 0;
34
+ }
35
+ /**
36
+ * In-record + capped-fallback wikilink resolver, shared by both endpoints.
37
+ * Resolution uses the store's own algorithm: a descending-prefix walk over the
38
+ * record's canonical scope matching canonical name + exact scope key at each
39
+ * prefix, so an edge never depends on whether the target landed in the window.
40
+ */
41
+ var WikilinkResolver = class {
42
+ /** exact `${scopeKey}\u0000${lowerName}` → node, from the fetched window. */
43
+ #inWindow = /* @__PURE__ */ new Map();
44
+ #windowIds = /* @__PURE__ */ new Set();
45
+ /** `${recordScopeKey}\u0000${lowerName}` → fallback result (null = dangling). */
46
+ #fallbackCache = /* @__PURE__ */ new Map();
47
+ #fallbackLookups = 0;
48
+ #store;
49
+ #maxFallbackLookups;
50
+ outOfWindow = /* @__PURE__ */ new Map();
51
+ cappedNames = [];
52
+ #cappedSeen = /* @__PURE__ */ new Set();
53
+ constructor(store, nodes, maxFallbackLookups) {
54
+ this.#store = store;
55
+ this.#maxFallbackLookups = maxFallbackLookups;
56
+ for (const node of nodes) {
57
+ this.#inWindow.set(`${knowledgeScopeKey(node.scope)}\u0000${node.name.trim().toLocaleLowerCase()}`, node);
58
+ this.#windowIds.add(node.id);
59
+ }
60
+ }
61
+ inWindowId(id) {
62
+ return this.#windowIds.has(id);
63
+ }
64
+ /** Resolve a wikilink name from a knowledge record's scope. Returns the node or null (dangling/capped). */
65
+ async resolve(name, recordScope) {
66
+ const canonical = canonicalizeKnowledgeScope(recordScope);
67
+ const lower = name.trim().toLocaleLowerCase();
68
+ for (let length = canonical.length; length > 0; length--) {
69
+ const hit = this.#inWindow.get(`${knowledgeScopeKey(canonical.slice(0, length))}\u0000${lower}`);
70
+ if (hit) return hit;
71
+ }
72
+ const cacheKey = `${knowledgeScopeKey(canonical)}\u0000${lower}`;
73
+ if (this.#fallbackCache.has(cacheKey)) return this.#trackOutOfWindow(this.#fallbackCache.get(cacheKey) ?? null);
74
+ if (this.#fallbackLookups >= this.#maxFallbackLookups) {
75
+ if (!this.#cappedSeen.has(lower) && this.cappedNames.length < 100) {
76
+ this.#cappedSeen.add(lower);
77
+ this.cappedNames.push(name.trim());
78
+ } else if (!this.#cappedSeen.has(lower)) this.#cappedSeen.add(lower);
79
+ return null;
80
+ }
81
+ this.#fallbackLookups += 1;
82
+ let resolved = null;
83
+ try {
84
+ resolved = await this.#store.resolveNode({
85
+ name,
86
+ scope: canonical
87
+ });
88
+ } catch {
89
+ resolved = null;
90
+ }
91
+ this.#fallbackCache.set(cacheKey, resolved);
92
+ return this.#trackOutOfWindow(resolved);
93
+ }
94
+ #trackOutOfWindow(node) {
95
+ if (node && !this.#windowIds.has(node.id)) this.outOfWindow.set(node.id, {
96
+ id: node.id,
97
+ name: node.name
98
+ });
99
+ return node;
100
+ }
101
+ get cappedCount() {
102
+ return this.#cappedSeen.size;
103
+ }
104
+ };
105
+ var KnowledgeRoutes = class extends Route {
106
+ #limits;
107
+ constructor(deps) {
108
+ super(deps);
109
+ this.#limits = {
110
+ ...DEFAULT_LIMITS,
111
+ ...deps.limits
112
+ };
113
+ }
114
+ /** Resolve the `(orgId, userId)` tenant or a ready-to-return error response. */
115
+ async #resolveTenant(c) {
116
+ await this.deps.auth.ensureUser(c);
117
+ const tenant = this.deps.auth.tenant(c);
118
+ if (!tenant) return { response: c.json({ error: "unauthorized" }, 401) };
119
+ if (!tenant.orgId) return { response: c.json({
120
+ error: "organization_required",
121
+ message: "The knowledge graph requires an organization."
122
+ }, 403) };
123
+ return {
124
+ orgId: tenant.orgId,
125
+ userId: tenant.userId
126
+ };
127
+ }
128
+ /**
129
+ * Resolve tenant + org-owned project + knowledge store + the active view's
130
+ * query scope. The ONE seam both endpoints share, so the default/thread view
131
+ * scope and the pin rungs cannot drift between them.
132
+ *
133
+ * threadId validation runs a single `listRecordsBySource` lookup with
134
+ * `limit: 1` AT THE CANDIDATE SCOPE `[org, resource, thread:<id>]` — the
135
+ * store's own visibility predicate is the authorization: the thread's own
136
+ * records (equal scope key) and its project/org captures (prefix) match, while
137
+ * a cross-org thread's records match nothing → zero rows → 404.
138
+ */
139
+ async #resolveView(c) {
140
+ const tenant = await this.#resolveTenant(c);
141
+ if ("response" in tenant) return tenant;
142
+ const projectId = c.req.param("id");
143
+ if (!projectId || !UUID_RE.test(projectId)) return { response: c.json({ error: "Project not found" }, 404) };
144
+ const { projects } = this.deps;
145
+ await projects.ensureReady();
146
+ if (!await projects.get({
147
+ orgId: tenant.orgId,
148
+ id: projectId
149
+ })) return { response: c.json({ error: "Project not found" }, 404) };
150
+ const store = await this.deps.knowledge();
151
+ if (!store) return { response: c.json({
152
+ error: "knowledge_unavailable",
153
+ message: "The knowledge storage domain is not configured."
154
+ }, 503) };
155
+ const defaultScope = [`org:${tenant.orgId}`, `resource:${projectId}`];
156
+ const resourceRungScope = defaultScope;
157
+ const threadId = boundedThreadId(c.req.query("threadId"));
158
+ if (c.req.query("threadId") !== void 0 && !threadId) return { response: c.json({ error: "thread_not_found" }, 404) };
159
+ if (!threadId) return {
160
+ ...tenant,
161
+ factoryProjectId: projectId,
162
+ store,
163
+ view: "project",
164
+ scope: defaultScope,
165
+ pinRungs: [{
166
+ rung: "resource",
167
+ scope: resourceRungScope
168
+ }]
169
+ };
170
+ const candidateScope = [...defaultScope, `thread:${threadId}`];
171
+ if ((await store.knowledgeBySource({
172
+ sourceThreadId: threadId,
173
+ scope: candidateScope,
174
+ limit: 1
175
+ })).records.length === 0) return { response: c.json({ error: "thread_not_found" }, 404) };
176
+ return {
177
+ ...tenant,
178
+ factoryProjectId: projectId,
179
+ store,
180
+ view: "thread",
181
+ threadId,
182
+ scope: candidateScope,
183
+ pinRungs: [{
184
+ rung: "resource",
185
+ scope: resourceRungScope
186
+ }, {
187
+ rung: "thread",
188
+ scope: candidateScope
189
+ }]
190
+ };
191
+ }
192
+ /** Reserved `pinned` node ids at the active view's rungs (one exact-scope lookup per rung). */
193
+ async #pinnedNodeIds(view) {
194
+ const out = [];
195
+ for (const { rung, scope } of view.pinRungs) {
196
+ const node = await view.store.getNodeByName({
197
+ name: PINNED_NODE_NAME,
198
+ scope
199
+ });
200
+ if (node && !node.mergedInto) out.push({
201
+ rung,
202
+ id: node.id
203
+ });
204
+ }
205
+ return out;
206
+ }
207
+ /** Non-deleted pinned records for the given pinned-node ids, visible in the view. */
208
+ async #pinnedRecords(view, pinnedNodeIds) {
209
+ const out = [];
210
+ for (const { rung, id } of pinnedNodeIds) {
211
+ const { records } = await view.store.listKnowledgeAbout({
212
+ node: id,
213
+ scope: view.scope,
214
+ limit: 200
215
+ });
216
+ for (const record of records) out.push({
217
+ rung,
218
+ record
219
+ });
220
+ }
221
+ return out;
222
+ }
223
+ routes() {
224
+ return [
225
+ registerApiRoute("/web/factory/projects/:id/knowledge/graph", {
226
+ method: "GET",
227
+ requiresAuth: false,
228
+ handler: async (c) => {
229
+ const view = await this.#resolveView(loose(c));
230
+ if ("response" in view) return view.response;
231
+ const { store, scope } = view;
232
+ const limits = this.#limits;
233
+ const fetched = await store.listNodes({
234
+ scope,
235
+ limit: limits.maxNodes + 1
236
+ });
237
+ let truncated = fetched.length > limits.maxNodes;
238
+ const pinnedNodeIds = await this.#pinnedNodeIds(view);
239
+ const pinnedNodeIdSet = new Set(pinnedNodeIds.map((p) => p.id));
240
+ const nodes = fetched.slice(0, limits.maxNodes).filter((node) => !pinnedNodeIdSet.has(node.id));
241
+ const recordWindow = [];
242
+ for (const node of nodes) {
243
+ if (recordWindow.length > limits.maxRecords) break;
244
+ const { records } = await store.listKnowledgeAbout({
245
+ node: node.id,
246
+ scope,
247
+ limit: limits.maxRecords + 1 - recordWindow.length
248
+ });
249
+ recordWindow.push(...records);
250
+ }
251
+ recordWindow.sort((a, b) => b.id.localeCompare(a.id));
252
+ if (recordWindow.length > limits.maxRecords) {
253
+ truncated = true;
254
+ recordWindow.length = limits.maxRecords;
255
+ }
256
+ const resolver = new WikilinkResolver(store, nodes, limits.maxFallbackLookups);
257
+ const edges = [];
258
+ const graphRecords = [];
259
+ const edgeSeen = /* @__PURE__ */ new Set();
260
+ const recordCounts = /* @__PURE__ */ new Map();
261
+ for (const record of recordWindow) {
262
+ recordCounts.set(record.node, (recordCounts.get(record.node) ?? 0) + 1);
263
+ const nodeIds = [record.node];
264
+ for (const name of parseKnowledgeWikilinks(record.text)) {
265
+ const target = await resolver.resolve(name, record.scope);
266
+ if (!target) continue;
267
+ if (target.id === record.node) continue;
268
+ if (!resolver.inWindowId(target.id)) continue;
269
+ if (!nodeIds.includes(target.id)) nodeIds.push(target.id);
270
+ const key = `${record.node}\u0000${target.id}`;
271
+ if (edgeSeen.has(key)) continue;
272
+ edgeSeen.add(key);
273
+ edges.push({
274
+ id: `wikilink:${record.node}:${target.id}`,
275
+ source: record.node,
276
+ target: target.id,
277
+ type: "wikilink",
278
+ recordId: record.id
279
+ });
280
+ }
281
+ graphRecords.push({
282
+ id: record.id,
283
+ nodeIds,
284
+ pinned: false,
285
+ text: truncateRecordText(record.text)
286
+ });
287
+ }
288
+ const pinnedRecords = await this.#pinnedRecords(view, pinnedNodeIds);
289
+ const accented = /* @__PURE__ */ new Set();
290
+ for (const { record } of pinnedRecords) {
291
+ const targets = [];
292
+ for (const name of parseKnowledgeWikilinks(record.text)) {
293
+ const target = await resolver.resolve(name, record.scope);
294
+ if (target && resolver.inWindowId(target.id) && !targets.includes(target.id)) targets.push(target.id);
295
+ }
296
+ graphRecords.push({
297
+ id: record.id,
298
+ nodeIds: targets,
299
+ pinned: true,
300
+ text: truncateRecordText(record.text)
301
+ });
302
+ if (targets.length === 1) {
303
+ accented.add(targets[0]);
304
+ continue;
305
+ }
306
+ for (let a = 0; a < targets.length; a += 1) for (let b = a + 1; b < targets.length; b += 1) {
307
+ const key = `${targets[a]}\u0000${targets[b]}\u0000pin`;
308
+ if (edgeSeen.has(key)) continue;
309
+ edgeSeen.add(key);
310
+ edges.push({
311
+ id: `pin:${record.id}:${targets[a]}:${targets[b]}`,
312
+ source: targets[a],
313
+ target: targets[b],
314
+ type: "wikilink",
315
+ recordId: record.id,
316
+ pinned: true
317
+ });
318
+ }
319
+ }
320
+ const pinCensus = {
321
+ resource: pinnedRecords.filter((p) => p.rung === "resource").length,
322
+ thread: view.view === "thread" ? pinnedRecords.filter((p) => p.rung === "thread").length : null
323
+ };
324
+ const activity = await store.listActivity({
325
+ scope,
326
+ limit: 1
327
+ });
328
+ const payload = {
329
+ view: view.view,
330
+ ...view.threadId ? { threadId: view.threadId } : {},
331
+ nodes: nodes.map((node) => ({
332
+ id: node.id,
333
+ name: node.name,
334
+ kind: node.kind,
335
+ scope: node.scope,
336
+ rung: deepestRung(node.scope),
337
+ pinned: accented.has(node.id),
338
+ recordCount: recordCounts.get(node.id) ?? 0,
339
+ createdAt: node.createdAt.toISOString(),
340
+ updatedAt: node.updatedAt.toISOString()
341
+ })),
342
+ edges,
343
+ records: graphRecords,
344
+ truncated,
345
+ outOfWindow: [...resolver.outOfWindow.values()],
346
+ unresolvedCapped: {
347
+ count: resolver.cappedCount,
348
+ names: resolver.cappedNames
349
+ },
350
+ pinCensus,
351
+ version: activity[0]?.id ?? null
352
+ };
353
+ return c.json(payload);
354
+ }
355
+ }),
356
+ registerApiRoute("/web/factory/projects/:id/knowledge/nodes/:nodeId", {
357
+ method: "GET",
358
+ requiresAuth: false,
359
+ handler: async (c) => {
360
+ const view = await this.#resolveView(loose(c));
361
+ if ("response" in view) return view.response;
362
+ const { store, scope } = view;
363
+ const nodeId = loose(c).req.param("nodeId");
364
+ if (!nodeId || nodeId.length > 512) return c.json({ error: "node_not_found" }, 404);
365
+ const node = await store.getNode(nodeId);
366
+ if (!node || !isKnowledgeScopeVisible(node.scope, scope)) return c.json({ error: "node_not_found" }, 404);
367
+ const pinnedNodeIds = await this.#pinnedNodeIds(view);
368
+ const pinnedNodeIdSet = new Set(pinnedNodeIds.map((p) => p.id));
369
+ const [owned, mentioning] = await Promise.all([store.listKnowledgeAbout({
370
+ node: node.id,
371
+ scope,
372
+ limit: 200
373
+ }), store.listKnowledgeMentioning({
374
+ node: node.id,
375
+ scope,
376
+ limit: 200
377
+ })]);
378
+ const seen = /* @__PURE__ */ new Set();
379
+ const records = [];
380
+ const push = (record, relation) => {
381
+ if (seen.has(record.id)) return;
382
+ seen.add(record.id);
383
+ records.push({
384
+ id: record.id,
385
+ node: record.node,
386
+ relation,
387
+ text: record.text,
388
+ scope: record.scope,
389
+ rung: deepestRung(record.scope),
390
+ sourceThreadId: record.sourceThreadId,
391
+ capturedAt: record.capturedAt.toISOString(),
392
+ ...record.when ? { when: record.when.toISOString() } : {},
393
+ pinned: pinnedNodeIdSet.has(record.node),
394
+ ...record.metadata ? { metadata: record.metadata } : {}
395
+ });
396
+ };
397
+ for (const record of [...owned.records].sort((a, b) => b.id.localeCompare(a.id))) push(record, "owned");
398
+ for (const record of [...mentioning.records].sort((a, b) => b.id.localeCompare(a.id))) push(record, "mentions");
399
+ const payload = {
400
+ node: {
401
+ id: node.id,
402
+ name: node.name,
403
+ kind: node.kind,
404
+ content: node.content ?? "",
405
+ scope: node.scope,
406
+ rung: deepestRung(node.scope),
407
+ createdAt: node.createdAt.toISOString(),
408
+ updatedAt: node.updatedAt.toISOString()
409
+ },
410
+ records
411
+ };
412
+ return c.json(payload);
413
+ }
414
+ }),
415
+ registerApiRoute("/web/factory/projects/:id/knowledge/activity", {
416
+ method: "GET",
417
+ requiresAuth: false,
418
+ handler: async (c) => {
419
+ const view = await this.#resolveView(loose(c));
420
+ if ("response" in view) return view.response;
421
+ const events = await view.store.listActivity({
422
+ scope: view.scope,
423
+ limit: 100
424
+ });
425
+ return c.json({ events: events.map((event) => ({
426
+ id: event.id,
427
+ action: event.action,
428
+ recordType: event.recordType,
429
+ recordId: event.recordId,
430
+ scope: event.scope,
431
+ ...event.sourceThreadId ? { sourceThreadId: event.sourceThreadId } : {},
432
+ createdAt: event.createdAt.toISOString()
433
+ })) });
434
+ }
435
+ })
436
+ ];
437
+ }
438
+ };
439
+ //#endregion
440
+ export { KnowledgeRoutes };
441
+
442
+ //# sourceMappingURL=knowledge.js.map