fuli-context 0.2.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 (220) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +178 -0
  3. package/compose.graphiti.yml +86 -0
  4. package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
  5. package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
  6. package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
  7. package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
  8. package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
  9. package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
  10. package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
  11. package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
  12. package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
  13. package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
  14. package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
  15. package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
  16. package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
  17. package/dist/web/assets/console-tbvTUi6p.js +1 -0
  18. package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
  19. package/dist/web/assets/identity-B3s5CXhv.js +1 -0
  20. package/dist/web/assets/index-Devf7glZ.css +1 -0
  21. package/dist/web/assets/index-DwUDhQKc.js +6 -0
  22. package/dist/web/assets/model-DVT8sjy9.js +1 -0
  23. package/dist/web/assets/paths-B790iniO.js +1 -0
  24. package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
  25. package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
  26. package/dist/web/index.html +22 -0
  27. package/graph-provider/Dockerfile +16 -0
  28. package/graph-provider/fuli_graph/__init__.py +5 -0
  29. package/graph-provider/fuli_graph/app.py +547 -0
  30. package/graph-provider/fuli_graph/auth.py +17 -0
  31. package/graph-provider/fuli_graph/collaboration_context.py +290 -0
  32. package/graph-provider/fuli_graph/config.py +34 -0
  33. package/graph-provider/fuli_graph/graph_models.py +123 -0
  34. package/graph-provider/fuli_graph/graph_projection.py +405 -0
  35. package/graph-provider/fuli_graph/graph_query.py +561 -0
  36. package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
  37. package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
  38. package/graph-provider/fuli_graph/knowledge_management.py +816 -0
  39. package/graph-provider/fuli_graph/knowledge_records.py +65 -0
  40. package/graph-provider/fuli_graph/knowledge_search.py +723 -0
  41. package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
  42. package/graph-provider/fuli_graph/model_validation.py +58 -0
  43. package/graph-provider/fuli_graph/models.py +1179 -0
  44. package/graph-provider/fuli_graph/personal_project_access.py +29 -0
  45. package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
  46. package/graph-provider/fuli_graph/project_action_models.py +142 -0
  47. package/graph-provider/fuli_graph/project_knowledge.py +707 -0
  48. package/graph-provider/fuli_graph/project_management.py +251 -0
  49. package/graph-provider/fuli_graph/provider_values.py +40 -0
  50. package/graph-provider/fuli_graph/runtime.py +350 -0
  51. package/graph-provider/fuli_graph/search_projection.py +156 -0
  52. package/graph-provider/fuli_graph/store.py +24 -0
  53. package/graph-provider/fuli_graph/store_identity.py +237 -0
  54. package/graph-provider/fuli_graph/store_knowledge.py +447 -0
  55. package/graph-provider/fuli_graph/store_projects.py +242 -0
  56. package/graph-provider/fuli_graph/store_publication.py +370 -0
  57. package/graph-provider/fuli_graph/store_records.py +134 -0
  58. package/graph-provider/pyproject.toml +35 -0
  59. package/npm-shrinkwrap.json +6045 -0
  60. package/package.json +99 -0
  61. package/skills/capturing-session-knowledge/SKILL.md +226 -0
  62. package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
  63. package/skills/grilling-project/SKILL.md +129 -0
  64. package/skills/grilling-project/agents/openai.yaml +4 -0
  65. package/skills/grilling-project/references/project-profile.md +105 -0
  66. package/src/agent/candidate-read-service.js +53 -0
  67. package/src/agent/context-pack-service.js +83 -0
  68. package/src/agent/public-read-policy.js +39 -0
  69. package/src/agent/public-read-service.js +69 -0
  70. package/src/agent/read-projection.js +107 -0
  71. package/src/agent/search-service.js +61 -0
  72. package/src/agent/space-boundary.js +33 -0
  73. package/src/agent-tools/graph-definitions.js +479 -0
  74. package/src/agent-tools/graph-handlers.js +96 -0
  75. package/src/agent-tools/schema.js +37 -0
  76. package/src/agent-tools.js +16 -0
  77. package/src/app/application-error.js +13 -0
  78. package/src/app/bootstrap.js +8 -0
  79. package/src/app/create-application.js +143 -0
  80. package/src/app/state-service.js +38 -0
  81. package/src/candidates.js +78 -0
  82. package/src/classifier.js +63 -0
  83. package/src/cli/command-arguments.js +14 -0
  84. package/src/cli/command-registry.js +78 -0
  85. package/src/cli/commands/candidates.js +17 -0
  86. package/src/cli/commands/ingestion.js +32 -0
  87. package/src/cli/commands/reads.js +50 -0
  88. package/src/cli/commands/spaces.js +20 -0
  89. package/src/cli/invocation.js +47 -0
  90. package/src/cli/local-runtime-command.js +94 -0
  91. package/src/cli/local-runtime-options.js +64 -0
  92. package/src/cli/migrate-command.js +71 -0
  93. package/src/cli/migration-destination.js +117 -0
  94. package/src/cli/setup-agent-selection.js +195 -0
  95. package/src/cli/setup-command.js +85 -0
  96. package/src/cli/uninstall-command.js +67 -0
  97. package/src/cli.js +90 -0
  98. package/src/context-pack.js +206 -0
  99. package/src/defaults.js +1 -0
  100. package/src/extractor.js +77 -0
  101. package/src/fact-history.js +39 -0
  102. package/src/facts/fact-service.js +89 -0
  103. package/src/graphiti/agent-access-policy.js +88 -0
  104. package/src/graphiti/capture-policy.js +84 -0
  105. package/src/graphiti/federated-application.js +1292 -0
  106. package/src/graphiti/personal-context.js +29 -0
  107. package/src/graphiti/project-path-context.js +159 -0
  108. package/src/graphiti/provider-client.js +288 -0
  109. package/src/graphiti/runtime-config.js +85 -0
  110. package/src/graphiti/setup.js +43 -0
  111. package/src/graphiti/source-marker.js +142 -0
  112. package/src/http/api-router.js +133 -0
  113. package/src/http/bounded-json-array.js +19 -0
  114. package/src/http/error-mapping.js +36 -0
  115. package/src/http/graph-api-router.js +257 -0
  116. package/src/http/lens-route.js +50 -0
  117. package/src/http/request-policy.js +41 -0
  118. package/src/http/response.js +29 -0
  119. package/src/http/static-handler.js +55 -0
  120. package/src/ingestion.js +161 -0
  121. package/src/lens/lens-correction-service.js +81 -0
  122. package/src/lens/lens-fact-writer.js +85 -0
  123. package/src/lens/lens-input.js +62 -0
  124. package/src/lens/lens-query-projection.js +87 -0
  125. package/src/lens/lens-query-ranking.js +62 -0
  126. package/src/lens/lens-query.js +143 -0
  127. package/src/lens/lens-resource-projection.js +93 -0
  128. package/src/lens/lens-resource-selection.js +45 -0
  129. package/src/lens/lens-resource-service.js +103 -0
  130. package/src/lens/lens-search-query.js +122 -0
  131. package/src/lens/lens-service.js +183 -0
  132. package/src/local-runtime/lifecycle.js +254 -0
  133. package/src/mcp/create-mcp-server.js +85 -0
  134. package/src/mcp/instructions.js +10 -0
  135. package/src/mcp/project-action-preview-tokens.js +72 -0
  136. package/src/mcp/runtime.js +71 -0
  137. package/src/mcp/tool-annotations.js +63 -0
  138. package/src/mcp/tool-result.js +167 -0
  139. package/src/mcp/tool-schema.js +60 -0
  140. package/src/mcp-server.js +76 -0
  141. package/src/models.js +44 -0
  142. package/src/observer.js +83 -0
  143. package/src/package-metadata.js +6 -0
  144. package/src/project-rules.js +33 -0
  145. package/src/publication/canonical-json.js +39 -0
  146. package/src/publication/outbox-service.js +46 -0
  147. package/src/publication/publication-envelope-schema.js +75 -0
  148. package/src/publication/publication-envelope.js +37 -0
  149. package/src/publication/publication-policy.js +82 -0
  150. package/src/publication/publication-service.js +48 -0
  151. package/src/router.js +58 -0
  152. package/src/runtime-options.js +139 -0
  153. package/src/security/episode-visibility.js +14 -0
  154. package/src/security/fact-visibility.js +15 -0
  155. package/src/security/sensitive-content.js +18 -0
  156. package/src/security/source-metadata-policy.js +8 -0
  157. package/src/server/application-lifecycle.js +34 -0
  158. package/src/server/listen.js +24 -0
  159. package/src/server.js +135 -0
  160. package/src/setup/agent-installation-status.js +97 -0
  161. package/src/setup/agents.js +123 -0
  162. package/src/setup/codex-bootstrap.js +118 -0
  163. package/src/setup/codex-config.js +122 -0
  164. package/src/setup/config-backup.js +22 -0
  165. package/src/setup/container-runtime.js +388 -0
  166. package/src/setup/cursor-config.js +61 -0
  167. package/src/setup/graph-runtime.js +360 -0
  168. package/src/setup/node-runtime.js +24 -0
  169. package/src/setup/options.js +91 -0
  170. package/src/setup/paths.js +47 -0
  171. package/src/setup/session-skill.js +91 -0
  172. package/src/setup/setup.js +85 -0
  173. package/src/setup/uninstall.js +90 -0
  174. package/src/storage/episode-evidence-preview.js +100 -0
  175. package/src/storage/fact-search-order.js +18 -0
  176. package/src/storage/file/episode-evidence.js +37 -0
  177. package/src/storage/file/episode-index.js +59 -0
  178. package/src/storage/file/fact-search.js +60 -0
  179. package/src/storage/file-store.js +437 -0
  180. package/src/storage/import-json.js +70 -0
  181. package/src/storage/json-file.js +41 -0
  182. package/src/storage/legacy-record-validation.js +152 -0
  183. package/src/storage/legacy-snapshot-validation.js +32 -0
  184. package/src/storage/migrate.js +128 -0
  185. package/src/storage/migrations/001-initial.sql +83 -0
  186. package/src/storage/migrations/002-storage-integrity.sql +113 -0
  187. package/src/storage/record-validation.js +46 -0
  188. package/src/storage/snapshot-defaults.js +54 -0
  189. package/src/storage/sqlite/candidates-repository.js +84 -0
  190. package/src/storage/sqlite/episode-evidence-repository.js +154 -0
  191. package/src/storage/sqlite/episode-privacy-sql.js +33 -0
  192. package/src/storage/sqlite/episodes-repository.js +52 -0
  193. package/src/storage/sqlite/fact-search-repository.js +162 -0
  194. package/src/storage/sqlite/facts-repository.js +146 -0
  195. package/src/storage/sqlite/imports-repository.js +39 -0
  196. package/src/storage/sqlite/mapper.js +84 -0
  197. package/src/storage/sqlite/open-database.js +15 -0
  198. package/src/storage/sqlite/outbox-repository.js +103 -0
  199. package/src/storage/sqlite/snapshot-repository.js +69 -0
  200. package/src/storage/sqlite/spaces-repository.js +52 -0
  201. package/src/storage/sqlite/subscriptions-repository.js +54 -0
  202. package/src/storage/sqlite-store.js +159 -0
  203. package/src/storage/stable-json.js +24 -0
  204. package/src/storage/store-port.js +20 -0
  205. package/src/store.js +2 -0
  206. package/src/workspace-protocol/array-validation.js +19 -0
  207. package/src/workspace-protocol/base64url-contract.js +27 -0
  208. package/src/workspace-protocol/constants.js +37 -0
  209. package/src/workspace-protocol/error-contract.js +24 -0
  210. package/src/workspace-protocol/fact-contract.js +110 -0
  211. package/src/workspace-protocol/governance-contract.js +47 -0
  212. package/src/workspace-protocol/index.js +53 -0
  213. package/src/workspace-protocol/json-budget.js +19 -0
  214. package/src/workspace-protocol/manifest-contract.js +186 -0
  215. package/src/workspace-protocol/publication-contract.js +212 -0
  216. package/src/workspace-protocol/query-contract.js +149 -0
  217. package/src/workspace-protocol/scalar-contract.js +192 -0
  218. package/src/workspace-protocol/sync-contract.js +184 -0
  219. package/src/workspace-protocol/text-contract.js +39 -0
  220. package/src/workspace-protocol/validation.js +66 -0
@@ -0,0 +1,479 @@
1
+ import {
2
+ arraySchema,
3
+ booleanSchema,
4
+ enumSchema,
5
+ integerSchema,
6
+ nullableStringSchema,
7
+ numberSchema,
8
+ objectSchema,
9
+ stringSchema
10
+ } from './schema.js';
11
+
12
+ const id = boundedString(256);
13
+ const label = boundedString(512);
14
+ const shortText = boundedString(2048);
15
+ const longText = boundedString(8192);
16
+ const dateTime = { ...boundedString(64), format: 'date-time' };
17
+ const attributes = { type: 'object', additionalProperties: true };
18
+ const epistemicQuadrant = enumSchema([
19
+ 'known_known', 'known_unknown', 'unknown_known', 'unknown_unknown'
20
+ ]);
21
+ const epistemicStatus = enumSchema(['confirmed', 'observed', 'exploratory']);
22
+ // Agent-confirmed is a read state created only by the usage policy. Agent write
23
+ // tools may submit human/source-confirmed or pending knowledge, but cannot forge it.
24
+ const confirmationStatus = enumSchema(['confirmed', 'pending']);
25
+ const inheritanceMode = enumSchema([
26
+ 'local_only', 'descendants', 'selected_projects'
27
+ ]);
28
+ const confirmationActor = objectSchema({
29
+ kind: enumSchema(['user', 'agent', 'authoritative_source', 'import']),
30
+ label: nullableStringSchema()
31
+ }, ['kind']);
32
+ const confirmationBasis = objectSchema({
33
+ existenceReason: boundedString(4096),
34
+ quadrantReason: boundedString(4096),
35
+ proposedBy: confirmationActor,
36
+ confirmedBy: { ...confirmationActor, type: ['object', 'null'] },
37
+ confirmedAt: nullableStringSchema(),
38
+ agentPolicyVersion: nullableStringSchema()
39
+ }, ['existenceReason', 'quadrantReason', 'proposedBy']);
40
+ const knowledgeItemKind = enumSchema(['entity', 'relationship']);
41
+ const humanChangeStatus = enumSchema([
42
+ 'all', 'human_changed', 'unseen', 'viewed', 'reviewed'
43
+ ]);
44
+ const projectRelationType = enumSchema([
45
+ 'PART_OF', 'USES_KNOWLEDGE_FROM', 'DEPENDS_ON', 'PROVIDES_TO', 'SHARES_CAPABILITY_WITH',
46
+ 'SUCCESSOR_OF', 'RELATED_TO'
47
+ ]);
48
+ const knowledgeConflictResolution = enumSchema([
49
+ 'defer', 'keep_target', 'use_source', 'coexist'
50
+ ]);
51
+ const profileAspect = {
52
+ type: ['string', 'null'],
53
+ enum: ['taste', 'personality', 'judgment_preference', null]
54
+ };
55
+ const sourceApplication = {
56
+ type: ['string', 'null'],
57
+ enum: ['codex', 'claude_code', 'cursor', 'kiro', 'other', null]
58
+ };
59
+ const epistemicFields = {
60
+ originQuadrant: epistemicQuadrant,
61
+ currentQuadrant: epistemicQuadrant,
62
+ epistemicStatus,
63
+ confirmationStatus,
64
+ confirmationBasis,
65
+ reasoningSummary: nullableStringSchema(),
66
+ profileAspect,
67
+ inheritanceMode,
68
+ inheritedProjectIds: arraySchema(id, { maxItems: 32 })
69
+ };
70
+
71
+ const entity = objectSchema({
72
+ key: id,
73
+ name: label,
74
+ type: { ...boundedString(64), pattern: '^[A-Za-z][A-Za-z0-9_]*$' },
75
+ summary: boundedString(4096),
76
+ ...epistemicFields,
77
+ attributes
78
+ }, [
79
+ 'key', 'name', 'type', 'originQuadrant',
80
+ 'confirmationStatus', 'confirmationBasis'
81
+ ]);
82
+
83
+ const relationship = objectSchema({
84
+ key: id,
85
+ source: id,
86
+ target: id,
87
+ type: { ...boundedString(64), pattern: '^[A-Z][A-Z0-9_]*$' },
88
+ fact: longText,
89
+ validAt: nullableStringSchema(),
90
+ invalidAt: nullableStringSchema(),
91
+ supersedes: arraySchema(id, { maxItems: 32 }),
92
+ confidence: numberSchema({ minimum: 0, maximum: 1 }),
93
+ ...epistemicFields,
94
+ attributes
95
+ }, [
96
+ 'key', 'source', 'target', 'type', 'fact', 'originQuadrant',
97
+ 'confirmationStatus', 'confirmationBasis'
98
+ ]);
99
+
100
+ const projectSource = objectSchema({
101
+ key: boundedString(128),
102
+ kind: enumSchema([
103
+ 'prd', 'product_document', 'technical_document', 'frontend_repository',
104
+ 'backend_repository', 'repository', 'design', 'runbook', 'monitoring',
105
+ 'issue_tracker', 'other'
106
+ ]),
107
+ title: label,
108
+ uri: nullableStringSchema(),
109
+ summary: nullableStringSchema(),
110
+ sensitivity: enumSchema(['normal', 'private', 'restricted'])
111
+ }, ['key', 'kind', 'title']);
112
+
113
+ const assessmentDimension = objectSchema({
114
+ key: boundedString(128),
115
+ label: boundedString(256),
116
+ score: integerSchema({ minimum: 0, maximum: 100 }),
117
+ state: enumSchema(['confirmed', 'inferred']),
118
+ evidence: arraySchema(shortText, { maxItems: 32 })
119
+ }, ['key', 'label', 'score', 'state']);
120
+
121
+ const projectAssessment = objectSchema({
122
+ score: integerSchema({ minimum: 0, maximum: 100 }),
123
+ label: enumSchema(['needs_clarification', 'partially_documented', 'well_documented']),
124
+ confirmed: arraySchema(shortText, { maxItems: 64 }),
125
+ inferred: arraySchema(shortText, { maxItems: 64 }),
126
+ dimensions: arraySchema(assessmentDimension, { maxItems: 16 }),
127
+ analyzedAt: dateTime
128
+ }, ['score', 'label', 'confirmed', 'inferred', 'dimensions', 'analyzedAt']);
129
+
130
+ const projectProfile = objectSchema({
131
+ name: boundedString(160),
132
+ purpose: nullableStringSchema(),
133
+ scope: nullableStringSchema(),
134
+ technicalSummary: nullableStringSchema(),
135
+ lifecycle: enumSchema(['planned', 'active', 'maintenance', 'archived']),
136
+ sources: arraySchema(projectSource, { maxItems: 64 }),
137
+ boundaries: arraySchema(shortText, { maxItems: 64 }),
138
+ assessment: { ...projectAssessment, type: ['object', 'null'] }
139
+ }, ['name']);
140
+
141
+ const personalProjectActionIntent = {
142
+ personalSpaceId: id,
143
+ itemKind: enumSchema(['entity']),
144
+ itemId: id,
145
+ mode: enumSchema(['create', 'existing']),
146
+ targetProjectId: nullableStringSchema(),
147
+ newProjectId: nullableStringSchema(),
148
+ newProjectName: nullableStringSchema(),
149
+ newProjectPurpose: nullableStringSchema(),
150
+ keepSourceRelation: booleanSchema(),
151
+ relationType: projectRelationType,
152
+ conflictResolution: knowledgeConflictResolution,
153
+ reason: shortText
154
+ };
155
+ const personalProjectActionRequired = [
156
+ 'personalSpaceId', 'itemKind', 'itemId', 'mode', 'reason'
157
+ ];
158
+
159
+ export const GRAPH_TOOL_DEFINITIONS = [
160
+ {
161
+ name: 'get_collaboration_preferences',
162
+ title: 'READ FIRST · Load task collaboration preferences',
163
+ description: 'Call this exact tool name at the start of every user task before any other tool or answer; never substitute a project action tool. Pass projectPath as the current working directory; Fuli uses it transiently, never stores or returns it, and layers preferences only for one exact registered local project match. Returns effective_preferences plus deferred_conflicts intentionally queued for AI. Apply effective_preferences before answering or constructing tool arguments. Human-confirmed preferences outrank explicitly marked agent-confirmed preferences. If the current task would use a deferred conflict, call resolve_deferred_preference_conflict before applying either side; ignore unrelated deferred conflicts. For write tools, enforce preferences in the actual payload; mentioning them only in the final answer is not compliance. Personal-global preferences always apply; other conflicted, pending, invalid, ambiguous-project, and unrelated-project items never auto-apply. Automatic injection does not count as usage evidence. personalProjectId remains an explicit compatibility override.',
164
+ inputSchema: objectSchema({
165
+ projectPath: boundedString(4096),
166
+ personalProjectId: nullableStringSchema(),
167
+ limit: integerSchema({ minimum: 1, maximum: 200 })
168
+ }, ['projectPath'])
169
+ },
170
+ {
171
+ name: 'resolve_deferred_preference_conflict',
172
+ description: 'Resolve one AI-deferred collaboration-preference conflict returned by get_collaboration_preferences when, and only when, the current task needs that preference. Compare both confirmed sides against their evidence and the current context before choosing. The operation updates the effective knowledge, preserves revision history, and permanently marks the conflict as previously conflicted and resolved by AI. Use merge with a canonical item and merged instruction, keep_left or keep_right to retain one side, or split_scope to move one side to an exact personal project.',
173
+ inputSchema: objectSchema({
174
+ personalSpaceId: id,
175
+ conflictId: boundedString(1024),
176
+ resolution: enumSchema([
177
+ 'merge', 'keep_left', 'keep_right', 'split_scope'
178
+ ]),
179
+ reason: shortText,
180
+ canonicalItemId: nullableStringSchema(),
181
+ mergedInstruction: nullableStringSchema(),
182
+ splitItemId: nullableStringSchema(),
183
+ splitProjectId: nullableStringSchema()
184
+ }, ['personalSpaceId', 'conflictId', 'resolution', 'reason'])
185
+ },
186
+ {
187
+ name: 'capture_session_knowledge',
188
+ description: 'Silently batch durable session knowledge when the user-enabled capture policy allows it. Every item records its discovery quadrant, structured confirmation basis, and pending or confirmed state. A disabled policy returns capture_disabled without writing. Personal profile knowledge stays personal; only project knowledge with an auditable confirmation can enter public review.',
189
+ inputSchema: objectSchema({
190
+ targetKind: enumSchema(['personal', 'project']),
191
+ spaceId: id,
192
+ personalProjectId: nullableStringSchema(),
193
+ providerUrl: nullableStringSchema(),
194
+ idempotencyKey: id,
195
+ sessionId: id,
196
+ name: label,
197
+ sourceKind: boundedString(128),
198
+ sourceDescription: boundedString(1024),
199
+ sourceApplication,
200
+ sourceTurnId: { type: ['string', 'null'], minLength: 1, maxLength: 256 },
201
+ sourceExcerpt: { type: ['string', 'null'], minLength: 1, maxLength: 2048 },
202
+ referenceTime: dateTime,
203
+ summary: longText,
204
+ sensitivity: enumSchema(['normal', 'private', 'restricted']),
205
+ entities: arraySchema(entity, { minItems: 1, maxItems: 128 }),
206
+ relationships: arraySchema(relationship, { maxItems: 256 })
207
+ }, [
208
+ 'targetKind', 'spaceId', 'idempotencyKey', 'sessionId', 'name',
209
+ 'sourceKind', 'sourceDescription', 'referenceTime', 'entities', 'relationships'
210
+ ])
211
+ },
212
+ {
213
+ name: 'search_knowledge_graph',
214
+ title: 'READ · Search the scoped knowledge graph',
215
+ description: 'Search durable context before saying you do not know when a task may depend on remembered URLs, routes, requirements, architecture, prior decisions, runbooks, rationale, or personal preferences. Pending knowledge is searchable and explicitly marked; agent-confirmed knowledge ranks below human-confirmed knowledge. The bounded scope includes the personal-global profile, exact active local project, selectively inheritable knowledge reached through PART_OF or USES_KNOWLEDGE_FROM, explicitly selected additional personal projects, and selected subscribed team projects. Generic RELATED_TO links never expand scope. Use all_local_confirmed only after explicit user confirmation; it searches registered local personal projects for this query and never expands public projects. If that still has no support, use read-only local file search in the current repository or workspace files within a safe root. The response includes sourceMarker for supporting results, noMatchSourceMarker when returned items do not support the answer, and retrievalGuidance with the required next action.',
216
+ inputSchema: objectSchema({
217
+ personalSpaceId: id,
218
+ query: shortText,
219
+ personalProjectId: nullableStringSchema(),
220
+ contextPersonalProjectIds: arraySchema(id, { maxItems: 15 }),
221
+ personalProjectScope: enumSchema(['bounded', 'all_local_confirmed']),
222
+ projectIds: arraySchema(id, { maxItems: 32 }),
223
+ limit: integerSchema({ minimum: 1, maximum: 100 }),
224
+ includeHistorical: booleanSchema(),
225
+ includePending: booleanSchema()
226
+ }, ['personalSpaceId', 'query'])
227
+ },
228
+ {
229
+ name: 'record_knowledge_usage',
230
+ description: 'Record that personal knowledge materially affected the Agent final answer or completed action. Call only after the use actually occurs: cited means the answer explicitly relies on the item; applied means the item changed an implementation or decision. Retrieval, inspection, automatic preference injection, and merely appearing in context do not qualify. taskId must be the caller-stable identifier for the current user task and must be reused on retries; do not generate a fresh ID to recount the same task. Events are idempotent per knowledge item, task, use kind, and content generation. Repeated qualified use may promote pending knowledge to agent_confirmed, never to human-confirmed or public-eligible.',
231
+ inputSchema: objectSchema({
232
+ personalSpaceId: id,
233
+ taskId: id,
234
+ sessionId: nullableStringSchema(),
235
+ toolName: nullableStringSchema(),
236
+ items: arraySchema(objectSchema({
237
+ itemId: id,
238
+ itemKind: knowledgeItemKind,
239
+ useKind: enumSchema(['cited', 'applied'])
240
+ }, ['itemId', 'itemKind', 'useKind']), { minItems: 1, maxItems: 200 })
241
+ }, ['personalSpaceId', 'taskId', 'items'])
242
+ },
243
+ {
244
+ name: 'get_knowledge_graph',
245
+ description: 'Return a bounded node-edge graph for a personal space, one exact local personal project, or a team-shared project.',
246
+ inputSchema: objectSchema({
247
+ spaceId: id,
248
+ providerUrl: nullableStringSchema(),
249
+ personalProjectId: nullableStringSchema(),
250
+ limit: integerSchema({ minimum: 1, maximum: 2000 })
251
+ }, ['spaceId'])
252
+ },
253
+ {
254
+ name: 'search_human_knowledge_changes',
255
+ description: 'Search the permanent audit trail for personal knowledge changed by a human. Filter by unseen (not yet read by an Agent), viewed (read but still awaiting conflict and classification review), or reviewed. Returning an item records this Agent view but does not clear its pending marker.',
256
+ inputSchema: objectSchema({
257
+ personalSpaceId: id,
258
+ query: shortText,
259
+ status: humanChangeStatus,
260
+ limit: integerSchema({ minimum: 1, maximum: 200 })
261
+ }, ['personalSpaceId'])
262
+ },
263
+ {
264
+ name: 'review_human_knowledge_change',
265
+ description: 'Submit an explicit review for the current version of one human-edited personal knowledge item. The pending marker clears only when the Agent reports both no conflict and reasonable classification. Stale versions and items not read by an Agent are rejected; every review remains in the audit trail.',
266
+ inputSchema: objectSchema({
267
+ personalSpaceId: id,
268
+ itemKind: knowledgeItemKind,
269
+ itemId: id,
270
+ humanChangeVersion: integerSchema({ minimum: 1 }),
271
+ conflictCheck: enumSchema(['no_conflict', 'conflict']),
272
+ classificationCheck: enumSchema(['reasonable', 'needs_change']),
273
+ note: boundedString(2000)
274
+ }, [
275
+ 'personalSpaceId', 'itemKind', 'itemId', 'humanChangeVersion',
276
+ 'conflictCheck', 'classificationCheck', 'note'
277
+ ])
278
+ },
279
+ {
280
+ name: 'list_knowledge_spaces',
281
+ description: 'List the active personal space, accessible team-shared projects, and current subscriptions.',
282
+ inputSchema: objectSchema({})
283
+ },
284
+ {
285
+ name: 'upsert_personal_project',
286
+ description: 'Create or update a local personal project profile, including evidence sources and an explainable display-only summary of evidence already present. Do not generate missing-item claims. This never publishes the project.',
287
+ inputSchema: objectSchema({
288
+ personalSpaceId: id,
289
+ projectId: boundedString(128),
290
+ profile: projectProfile
291
+ }, ['personalSpaceId', 'projectId', 'profile'])
292
+ },
293
+ {
294
+ name: 'list_personal_projects',
295
+ description: 'List local personal project profiles and their current evidence-backed coverage summaries.',
296
+ inputSchema: objectSchema({ personalSpaceId: id })
297
+ },
298
+ {
299
+ name: 'revise_personal_knowledge',
300
+ description: 'Correct, invalidate, or restore one personal entity or relationship while preserving revision history and original evidence. This is the same personal-only operation used by the management UI.',
301
+ inputSchema: objectSchema({
302
+ personalSpaceId: id,
303
+ personalProjectId: nullableStringSchema(),
304
+ itemKind: knowledgeItemKind,
305
+ itemId: id,
306
+ action: enumSchema(['update', 'invalidate', 'restore']),
307
+ reason: shortText,
308
+ name: nullableStringSchema(),
309
+ summary: nullableStringSchema(),
310
+ fact: nullableStringSchema(),
311
+ originQuadrant: epistemicQuadrant,
312
+ currentQuadrant: epistemicQuadrant,
313
+ epistemicStatus,
314
+ confirmationStatus,
315
+ confirmationBasis,
316
+ reasoningSummary: nullableStringSchema(),
317
+ profileAspect,
318
+ inheritanceMode,
319
+ inheritedProjectIds: arraySchema(id, { maxItems: 32 })
320
+ }, ['personalSpaceId', 'itemKind', 'itemId', 'action', 'reason'])
321
+ },
322
+ {
323
+ name: 'reassign_personal_knowledge',
324
+ description: 'Move the primary project assignment of one personal knowledge item without rewriting its source episode or evidence. Personal-profile knowledge cannot be assigned to a project.',
325
+ inputSchema: objectSchema({
326
+ personalSpaceId: id,
327
+ itemKind: knowledgeItemKind,
328
+ itemId: id,
329
+ targetProjectId: boundedString(128),
330
+ reason: shortText
331
+ }, ['personalSpaceId', 'itemKind', 'itemId', 'targetProjectId', 'reason'])
332
+ },
333
+ {
334
+ name: 'set_personal_preference_scope',
335
+ description: 'Change one taste, personality, or judgment preference between the default personal-global scope and one exact personal project. The source evidence remains unchanged and the scope change is recorded in revision history.',
336
+ inputSchema: objectSchema({
337
+ personalSpaceId: id,
338
+ itemKind: knowledgeItemKind,
339
+ itemId: id,
340
+ scope: enumSchema(['global', 'project']),
341
+ projectId: nullableStringSchema(),
342
+ reason: shortText
343
+ }, ['personalSpaceId', 'itemKind', 'itemId', 'scope', 'reason'])
344
+ },
345
+ {
346
+ name: 'preview_personal_project_action',
347
+ title: 'PREVIEW · Authorize a personal project write',
348
+ description: 'Required authorization step before apply_personal_project_action. Submit the complete intended write. For an existing project, it reports exact duplicates and confirmed same-name conflicts. A successful preview returns a short-lived, one-time previewToken bound to the exact action.',
349
+ inputSchema: objectSchema(
350
+ personalProjectActionIntent,
351
+ personalProjectActionRequired
352
+ )
353
+ },
354
+ {
355
+ name: 'apply_personal_project_action',
356
+ title: 'WRITE · Apply an authorized personal project action',
357
+ description: 'WRITE operation. Never call during preference loading, project detection, knowledge retrieval, or any read-only task. Create a private personal project from one entity or add the entity to an existing personal project only after preview_personal_project_action. Requires the matching, short-lived, one-time previewToken and rejects changed or replayed actions. Existing ownership and evidence are preserved through references; duplicates are reused and conflicts follow the explicit resolution.',
358
+ inputSchema: objectSchema({
359
+ ...personalProjectActionIntent,
360
+ previewToken: boundedString(256)
361
+ }, [...personalProjectActionRequired, 'previewToken'])
362
+ },
363
+ {
364
+ name: 'publish_personal_project',
365
+ description: 'Publish or synchronize one local personal project to a configured public Provider with an immutable release version and update summary. The Provider records the authenticated publisher and server publication time. The first publisher becomes Owner and Maintainer and is automatically subscribed. Evidence coverage never blocks publication.',
366
+ inputSchema: objectSchema({
367
+ personalSpaceId: id,
368
+ localProjectId: boundedString(128),
369
+ providerUrl: shortText,
370
+ releaseVersion: { ...boundedString(64), pattern: '^[0-9A-Za-z][0-9A-Za-z._-]*$' },
371
+ updateSummary: boundedString(4096)
372
+ }, [
373
+ 'personalSpaceId', 'localProjectId', 'providerUrl',
374
+ 'releaseVersion', 'updateSummary'
375
+ ])
376
+ },
377
+ {
378
+ name: 'list_project_releases',
379
+ description: 'List immutable public project releases including version, publisher, publication time, and update summary.',
380
+ inputSchema: objectSchema({
381
+ projectId: id,
382
+ providerUrl: shortText
383
+ }, ['projectId', 'providerUrl'])
384
+ },
385
+ {
386
+ name: 'create_project_relation',
387
+ description: 'Create a canonical relationship between two public projects. PART_OF waits for target/parent Maintainer confirmation; other relation types become active immediately. This never subscribes either project.',
388
+ inputSchema: objectSchema({
389
+ sourceProjectId: id,
390
+ targetProjectId: id,
391
+ providerUrl: shortText,
392
+ relationType: projectRelationType,
393
+ note: nullableStringSchema()
394
+ }, ['sourceProjectId', 'targetProjectId', 'providerUrl', 'relationType'])
395
+ },
396
+ {
397
+ name: 'list_project_relations',
398
+ description: 'List canonical incoming and outgoing project relationships. Related projects are suggestions only and never expand the active subscription set.',
399
+ inputSchema: objectSchema({
400
+ projectId: id,
401
+ providerUrl: shortText
402
+ }, ['projectId', 'providerUrl'])
403
+ },
404
+ {
405
+ name: 'review_project_relation',
406
+ description: 'Confirm or reject a pending PART_OF relationship as a Maintainer of the target parent project.',
407
+ inputSchema: objectSchema({
408
+ targetProjectId: id,
409
+ relationId: id,
410
+ providerUrl: shortText,
411
+ decision: enumSchema(['confirm', 'reject']),
412
+ note: nullableStringSchema()
413
+ }, ['targetProjectId', 'relationId', 'providerUrl', 'decision'])
414
+ },
415
+ {
416
+ name: 'list_personal_review_queue',
417
+ description: 'List full structured knowledge drafts waiting in the local personal pre-review queue before any public submission.',
418
+ inputSchema: objectSchema({
419
+ personalSpaceId: id,
420
+ status: enumSchema(['pending', 'submitted', 'kept_personal', 'ignored'])
421
+ }, ['personalSpaceId'])
422
+ },
423
+ {
424
+ name: 'review_personal_draft',
425
+ description: 'Decide a local pre-review draft: submit it to shared Maintainer review, keep it only in the personal graph, or ignore it. No background upload occurs.',
426
+ inputSchema: objectSchema({
427
+ draftId: id,
428
+ decision: enumSchema(['submit_public', 'keep_personal', 'ignore'])
429
+ }, ['draftId', 'decision'])
430
+ },
431
+ {
432
+ name: 'subscribe_public_project',
433
+ description: 'Subscribe the active personal graph to an accessible team-shared project.',
434
+ inputSchema: objectSchema({
435
+ personalSpaceId: id,
436
+ projectId: id,
437
+ providerUrl: shortText,
438
+ projectName: label
439
+ }, ['personalSpaceId', 'projectId', 'providerUrl', 'projectName'])
440
+ },
441
+ {
442
+ name: 'unsubscribe_public_project',
443
+ description: 'Stop including one subscribed team-shared project in the active personal context. This removes only the local subscription and never deletes the public project.',
444
+ inputSchema: objectSchema({
445
+ personalSpaceId: id,
446
+ projectId: id,
447
+ providerUrl: shortText
448
+ }, ['personalSpaceId', 'projectId', 'providerUrl'])
449
+ },
450
+ {
451
+ name: 'list_project_review_queue',
452
+ description: 'List pending structured knowledge proposals for a team-shared project. Maintainer role is required.',
453
+ inputSchema: objectSchema({
454
+ projectId: id,
455
+ providerUrl: shortText,
456
+ status: enumSchema(['pending', 'approved', 'rejected'])
457
+ }, ['projectId', 'providerUrl'])
458
+ },
459
+ {
460
+ name: 'review_project_proposal',
461
+ description: 'Approve or reject one team-shared project knowledge proposal. Maintainer role is required.',
462
+ inputSchema: objectSchema({
463
+ projectId: id,
464
+ providerUrl: shortText,
465
+ proposalId: id,
466
+ decision: enumSchema(['approve', 'reject']),
467
+ note: boundedString(2000)
468
+ }, ['projectId', 'providerUrl', 'proposalId', 'decision'])
469
+ },
470
+ {
471
+ name: 'get_graphiti_status',
472
+ description: 'Return health and storage mode for the personal and configured workspace providers.',
473
+ inputSchema: objectSchema({})
474
+ }
475
+ ];
476
+
477
+ function boundedString(maxLength) {
478
+ return { ...stringSchema(), minLength: 1, maxLength };
479
+ }
@@ -0,0 +1,96 @@
1
+ import { ApplicationError } from '../app/application-error.js';
2
+
3
+ const HANDLERS = Object.freeze({
4
+ get_collaboration_preferences: (app, input) => app.getCollaborationPreferences({
5
+ ...input,
6
+ agentInvocation: true,
7
+ agentToolName: 'get_collaboration_preferences'
8
+ }),
9
+ resolve_deferred_preference_conflict: (app, input) =>
10
+ app.resolveDeferredPreferenceConflict({
11
+ ...input,
12
+ operationActor: 'agent'
13
+ }),
14
+ capture_session_knowledge: (app, input) => app.captureSessionKnowledge(input),
15
+ search_knowledge_graph: (app, input) => app.searchKnowledge({
16
+ ...input,
17
+ agentInvocation: true,
18
+ agentToolName: 'search_knowledge_graph'
19
+ }),
20
+ record_knowledge_usage: (app, input) => app.recordKnowledgeUsage(input),
21
+ get_knowledge_graph: (app, input) => app.getKnowledgeGraph({
22
+ ...input,
23
+ agentInvocation: true,
24
+ agentToolName: 'get_knowledge_graph'
25
+ }),
26
+ search_human_knowledge_changes: (app, input) => app.searchHumanChanges({
27
+ ...input,
28
+ agentInvocation: true,
29
+ agentToolName: 'search_human_knowledge_changes'
30
+ }),
31
+ review_human_knowledge_change: (app, input) => app.reviewHumanChange(input),
32
+ list_knowledge_spaces: (app) => app.listKnowledgeSpaces(),
33
+ upsert_personal_project: (app, input) => captureGuard(app, () =>
34
+ app.upsertPersonalProject(input)
35
+ ),
36
+ list_personal_projects: (app, input) => app.listPersonalProjects(input),
37
+ revise_personal_knowledge: (app, input) => app.reviseKnowledgeItem({
38
+ ...input,
39
+ operationActor: 'agent'
40
+ }),
41
+ reassign_personal_knowledge: (app, input) => app.reassignKnowledgeItem({
42
+ ...input,
43
+ operationActor: 'agent'
44
+ }),
45
+ set_personal_preference_scope: (app, input) => app.setPersonalPreferenceScope({
46
+ ...input,
47
+ operationActor: 'agent'
48
+ }),
49
+ preview_personal_project_action: (app, input) =>
50
+ app.previewKnowledgeProjectAction(input),
51
+ apply_personal_project_action: (app, input) => {
52
+ const { previewToken: _previewToken, ...action } = input;
53
+ return app.applyKnowledgeProjectAction({
54
+ ...action,
55
+ operationActor: 'agent'
56
+ });
57
+ },
58
+ publish_personal_project: (app, input) => app.publishPersonalProject(input),
59
+ list_project_releases: (app, input) => app.listProjectReleases(input),
60
+ create_project_relation: (app, input) => app.createProjectRelation(input),
61
+ list_project_relations: (app, input) => app.listProjectRelations(input),
62
+ review_project_relation: (app, input) => app.reviewProjectRelation(input),
63
+ list_personal_review_queue: (app, input) => app.listPersonalReviewQueue(input),
64
+ review_personal_draft: (app, input) => app.reviewPersonalDraft(input),
65
+ subscribe_public_project: (app, input) => app.subscribePublicProject(input),
66
+ unsubscribe_public_project: (app, input) => app.unsubscribePublicProject(input),
67
+ list_project_review_queue: (app, input) => app.listReviewQueue(input),
68
+ review_project_proposal: (app, input) => app.reviewProposal(input),
69
+ get_graphiti_status: (app) => app.getGraphitiStatus()
70
+ });
71
+
72
+ export function dispatchGraphTool(app, name, input) {
73
+ const handler = HANDLERS[name];
74
+ if (!handler) throw new Error(`Unknown agent tool: ${name}`);
75
+ const agentAccessPolicy = app.getAgentAccessPolicy?.() ?? {
76
+ enabled: true,
77
+ updatedAt: null
78
+ };
79
+ if (!agentAccessPolicy.enabled) {
80
+ throw new ApplicationError(
81
+ 'agent_access_disabled',
82
+ 'FULI Agent access is disabled in the local console'
83
+ );
84
+ }
85
+ return handler(app, input);
86
+ }
87
+
88
+ function captureGuard(app, operation) {
89
+ const capturePolicy = app.getCapturePolicy?.() ?? { enabled: true, updatedAt: null };
90
+ if (capturePolicy.enabled) return operation();
91
+ return {
92
+ route: 'disabled',
93
+ status: 'capture_disabled',
94
+ capturePolicy
95
+ };
96
+ }
@@ -0,0 +1,37 @@
1
+ export function objectSchema(properties, required = []) {
2
+ const schema = {
3
+ type: 'object',
4
+ properties,
5
+ additionalProperties: false
6
+ };
7
+ if (required.length) schema.required = required;
8
+ return schema;
9
+ }
10
+
11
+ export function stringSchema() {
12
+ return { type: 'string' };
13
+ }
14
+
15
+ export function nullableStringSchema() {
16
+ return { type: ['string', 'null'] };
17
+ }
18
+
19
+ export function booleanSchema() {
20
+ return { type: 'boolean' };
21
+ }
22
+
23
+ export function numberSchema(options = {}) {
24
+ return { type: 'number', ...options };
25
+ }
26
+
27
+ export function integerSchema(options = {}) {
28
+ return { type: 'integer', ...options };
29
+ }
30
+
31
+ export function arraySchema(items, options = {}) {
32
+ return { type: 'array', items, ...options };
33
+ }
34
+
35
+ export function enumSchema(values) {
36
+ return { type: 'string', enum: [...values] };
37
+ }
@@ -0,0 +1,16 @@
1
+ import { GRAPH_TOOL_DEFINITIONS } from './agent-tools/graph-definitions.js';
2
+ import { dispatchGraphTool } from './agent-tools/graph-handlers.js';
3
+
4
+ const TOOL_DEFINITIONS = [...GRAPH_TOOL_DEFINITIONS];
5
+
6
+ export function listAgentTools() {
7
+ return clone(TOOL_DEFINITIONS);
8
+ }
9
+
10
+ export function callAgentTool(app, name, input = {}) {
11
+ return dispatchGraphTool(app, name, input);
12
+ }
13
+
14
+ function clone(value) {
15
+ return JSON.parse(JSON.stringify(value));
16
+ }
@@ -0,0 +1,13 @@
1
+ export const ApplicationErrorCode = Object.freeze({
2
+ AGENT_ACCESS_DISABLED: 'agent_access_disabled',
3
+ NOT_FOUND: 'not_found',
4
+ VALIDATION: 'validation'
5
+ });
6
+
7
+ export class ApplicationError extends Error {
8
+ constructor(code, message) {
9
+ super(message);
10
+ this.name = 'ApplicationError';
11
+ this.code = code;
12
+ }
13
+ }
@@ -0,0 +1,8 @@
1
+ import { SpaceKind } from '../models.js';
2
+
3
+ export function bootstrapStarterSpaces(store) {
4
+ const personal = store.createSpace('我', SpaceKind.PERSONAL);
5
+ const space = store.createSpace('工作', SpaceKind.PUBLIC);
6
+ const subscription = store.subscribe(personal.id, space.id);
7
+ return { personal, space, subscription };
8
+ }