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.
- package/LICENSE +201 -0
- package/README.md +178 -0
- package/compose.graphiti.yml +86 -0
- package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
- package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
- package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
- package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
- package/dist/web/assets/console-tbvTUi6p.js +1 -0
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-B3s5CXhv.js +1 -0
- package/dist/web/assets/index-Devf7glZ.css +1 -0
- package/dist/web/assets/index-DwUDhQKc.js +6 -0
- package/dist/web/assets/model-DVT8sjy9.js +1 -0
- package/dist/web/assets/paths-B790iniO.js +1 -0
- package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
- package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
- package/dist/web/index.html +22 -0
- package/graph-provider/Dockerfile +16 -0
- package/graph-provider/fuli_graph/__init__.py +5 -0
- package/graph-provider/fuli_graph/app.py +547 -0
- package/graph-provider/fuli_graph/auth.py +17 -0
- package/graph-provider/fuli_graph/collaboration_context.py +290 -0
- package/graph-provider/fuli_graph/config.py +34 -0
- package/graph-provider/fuli_graph/graph_models.py +123 -0
- package/graph-provider/fuli_graph/graph_projection.py +405 -0
- package/graph-provider/fuli_graph/graph_query.py +561 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
- package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
- package/graph-provider/fuli_graph/knowledge_management.py +816 -0
- package/graph-provider/fuli_graph/knowledge_records.py +65 -0
- package/graph-provider/fuli_graph/knowledge_search.py +723 -0
- package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
- package/graph-provider/fuli_graph/model_validation.py +58 -0
- package/graph-provider/fuli_graph/models.py +1179 -0
- package/graph-provider/fuli_graph/personal_project_access.py +29 -0
- package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
- package/graph-provider/fuli_graph/project_action_models.py +142 -0
- package/graph-provider/fuli_graph/project_knowledge.py +707 -0
- package/graph-provider/fuli_graph/project_management.py +251 -0
- package/graph-provider/fuli_graph/provider_values.py +40 -0
- package/graph-provider/fuli_graph/runtime.py +350 -0
- package/graph-provider/fuli_graph/search_projection.py +156 -0
- package/graph-provider/fuli_graph/store.py +24 -0
- package/graph-provider/fuli_graph/store_identity.py +237 -0
- package/graph-provider/fuli_graph/store_knowledge.py +447 -0
- package/graph-provider/fuli_graph/store_projects.py +242 -0
- package/graph-provider/fuli_graph/store_publication.py +370 -0
- package/graph-provider/fuli_graph/store_records.py +134 -0
- package/graph-provider/pyproject.toml +35 -0
- package/npm-shrinkwrap.json +6045 -0
- package/package.json +99 -0
- package/skills/capturing-session-knowledge/SKILL.md +226 -0
- package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
- package/skills/grilling-project/SKILL.md +129 -0
- package/skills/grilling-project/agents/openai.yaml +4 -0
- package/skills/grilling-project/references/project-profile.md +105 -0
- package/src/agent/candidate-read-service.js +53 -0
- package/src/agent/context-pack-service.js +83 -0
- package/src/agent/public-read-policy.js +39 -0
- package/src/agent/public-read-service.js +69 -0
- package/src/agent/read-projection.js +107 -0
- package/src/agent/search-service.js +61 -0
- package/src/agent/space-boundary.js +33 -0
- package/src/agent-tools/graph-definitions.js +479 -0
- package/src/agent-tools/graph-handlers.js +96 -0
- package/src/agent-tools/schema.js +37 -0
- package/src/agent-tools.js +16 -0
- package/src/app/application-error.js +13 -0
- package/src/app/bootstrap.js +8 -0
- package/src/app/create-application.js +143 -0
- package/src/app/state-service.js +38 -0
- package/src/candidates.js +78 -0
- package/src/classifier.js +63 -0
- package/src/cli/command-arguments.js +14 -0
- package/src/cli/command-registry.js +78 -0
- package/src/cli/commands/candidates.js +17 -0
- package/src/cli/commands/ingestion.js +32 -0
- package/src/cli/commands/reads.js +50 -0
- package/src/cli/commands/spaces.js +20 -0
- package/src/cli/invocation.js +47 -0
- package/src/cli/local-runtime-command.js +94 -0
- package/src/cli/local-runtime-options.js +64 -0
- package/src/cli/migrate-command.js +71 -0
- package/src/cli/migration-destination.js +117 -0
- package/src/cli/setup-agent-selection.js +195 -0
- package/src/cli/setup-command.js +85 -0
- package/src/cli/uninstall-command.js +67 -0
- package/src/cli.js +90 -0
- package/src/context-pack.js +206 -0
- package/src/defaults.js +1 -0
- package/src/extractor.js +77 -0
- package/src/fact-history.js +39 -0
- package/src/facts/fact-service.js +89 -0
- package/src/graphiti/agent-access-policy.js +88 -0
- package/src/graphiti/capture-policy.js +84 -0
- package/src/graphiti/federated-application.js +1292 -0
- package/src/graphiti/personal-context.js +29 -0
- package/src/graphiti/project-path-context.js +159 -0
- package/src/graphiti/provider-client.js +288 -0
- package/src/graphiti/runtime-config.js +85 -0
- package/src/graphiti/setup.js +43 -0
- package/src/graphiti/source-marker.js +142 -0
- package/src/http/api-router.js +133 -0
- package/src/http/bounded-json-array.js +19 -0
- package/src/http/error-mapping.js +36 -0
- package/src/http/graph-api-router.js +257 -0
- package/src/http/lens-route.js +50 -0
- package/src/http/request-policy.js +41 -0
- package/src/http/response.js +29 -0
- package/src/http/static-handler.js +55 -0
- package/src/ingestion.js +161 -0
- package/src/lens/lens-correction-service.js +81 -0
- package/src/lens/lens-fact-writer.js +85 -0
- package/src/lens/lens-input.js +62 -0
- package/src/lens/lens-query-projection.js +87 -0
- package/src/lens/lens-query-ranking.js +62 -0
- package/src/lens/lens-query.js +143 -0
- package/src/lens/lens-resource-projection.js +93 -0
- package/src/lens/lens-resource-selection.js +45 -0
- package/src/lens/lens-resource-service.js +103 -0
- package/src/lens/lens-search-query.js +122 -0
- package/src/lens/lens-service.js +183 -0
- package/src/local-runtime/lifecycle.js +254 -0
- package/src/mcp/create-mcp-server.js +85 -0
- package/src/mcp/instructions.js +10 -0
- package/src/mcp/project-action-preview-tokens.js +72 -0
- package/src/mcp/runtime.js +71 -0
- package/src/mcp/tool-annotations.js +63 -0
- package/src/mcp/tool-result.js +167 -0
- package/src/mcp/tool-schema.js +60 -0
- package/src/mcp-server.js +76 -0
- package/src/models.js +44 -0
- package/src/observer.js +83 -0
- package/src/package-metadata.js +6 -0
- package/src/project-rules.js +33 -0
- package/src/publication/canonical-json.js +39 -0
- package/src/publication/outbox-service.js +46 -0
- package/src/publication/publication-envelope-schema.js +75 -0
- package/src/publication/publication-envelope.js +37 -0
- package/src/publication/publication-policy.js +82 -0
- package/src/publication/publication-service.js +48 -0
- package/src/router.js +58 -0
- package/src/runtime-options.js +139 -0
- package/src/security/episode-visibility.js +14 -0
- package/src/security/fact-visibility.js +15 -0
- package/src/security/sensitive-content.js +18 -0
- package/src/security/source-metadata-policy.js +8 -0
- package/src/server/application-lifecycle.js +34 -0
- package/src/server/listen.js +24 -0
- package/src/server.js +135 -0
- package/src/setup/agent-installation-status.js +97 -0
- package/src/setup/agents.js +123 -0
- package/src/setup/codex-bootstrap.js +118 -0
- package/src/setup/codex-config.js +122 -0
- package/src/setup/config-backup.js +22 -0
- package/src/setup/container-runtime.js +388 -0
- package/src/setup/cursor-config.js +61 -0
- package/src/setup/graph-runtime.js +360 -0
- package/src/setup/node-runtime.js +24 -0
- package/src/setup/options.js +91 -0
- package/src/setup/paths.js +47 -0
- package/src/setup/session-skill.js +91 -0
- package/src/setup/setup.js +85 -0
- package/src/setup/uninstall.js +90 -0
- package/src/storage/episode-evidence-preview.js +100 -0
- package/src/storage/fact-search-order.js +18 -0
- package/src/storage/file/episode-evidence.js +37 -0
- package/src/storage/file/episode-index.js +59 -0
- package/src/storage/file/fact-search.js +60 -0
- package/src/storage/file-store.js +437 -0
- package/src/storage/import-json.js +70 -0
- package/src/storage/json-file.js +41 -0
- package/src/storage/legacy-record-validation.js +152 -0
- package/src/storage/legacy-snapshot-validation.js +32 -0
- package/src/storage/migrate.js +128 -0
- package/src/storage/migrations/001-initial.sql +83 -0
- package/src/storage/migrations/002-storage-integrity.sql +113 -0
- package/src/storage/record-validation.js +46 -0
- package/src/storage/snapshot-defaults.js +54 -0
- package/src/storage/sqlite/candidates-repository.js +84 -0
- package/src/storage/sqlite/episode-evidence-repository.js +154 -0
- package/src/storage/sqlite/episode-privacy-sql.js +33 -0
- package/src/storage/sqlite/episodes-repository.js +52 -0
- package/src/storage/sqlite/fact-search-repository.js +162 -0
- package/src/storage/sqlite/facts-repository.js +146 -0
- package/src/storage/sqlite/imports-repository.js +39 -0
- package/src/storage/sqlite/mapper.js +84 -0
- package/src/storage/sqlite/open-database.js +15 -0
- package/src/storage/sqlite/outbox-repository.js +103 -0
- package/src/storage/sqlite/snapshot-repository.js +69 -0
- package/src/storage/sqlite/spaces-repository.js +52 -0
- package/src/storage/sqlite/subscriptions-repository.js +54 -0
- package/src/storage/sqlite-store.js +159 -0
- package/src/storage/stable-json.js +24 -0
- package/src/storage/store-port.js +20 -0
- package/src/store.js +2 -0
- package/src/workspace-protocol/array-validation.js +19 -0
- package/src/workspace-protocol/base64url-contract.js +27 -0
- package/src/workspace-protocol/constants.js +37 -0
- package/src/workspace-protocol/error-contract.js +24 -0
- package/src/workspace-protocol/fact-contract.js +110 -0
- package/src/workspace-protocol/governance-contract.js +47 -0
- package/src/workspace-protocol/index.js +53 -0
- package/src/workspace-protocol/json-budget.js +19 -0
- package/src/workspace-protocol/manifest-contract.js +186 -0
- package/src/workspace-protocol/publication-contract.js +212 -0
- package/src/workspace-protocol/query-contract.js +149 -0
- package/src/workspace-protocol/scalar-contract.js +192 -0
- package/src/workspace-protocol/sync-contract.js +184 -0
- package/src/workspace-protocol/text-contract.js +39 -0
- package/src/workspace-protocol/validation.js +66 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { isCurrentFact } from './models.js';
|
|
2
|
+
import { listCandidates } from './candidates.js';
|
|
3
|
+
import { getProjectRules } from './project-rules.js';
|
|
4
|
+
import { getFactHistory } from './fact-history.js';
|
|
5
|
+
|
|
6
|
+
export function buildContextPack(store, { personalSpaceId, spaceId, query = '' }) {
|
|
7
|
+
const personalSpace = store.getSpace(personalSpaceId);
|
|
8
|
+
const space = store.getSpace(spaceId);
|
|
9
|
+
const scopedSpaceIds = [...new Set([personalSpaceId, spaceId])];
|
|
10
|
+
const matches = store
|
|
11
|
+
.searchFacts(scopedSpaceIds, query, { includeHistorical: false })
|
|
12
|
+
.map((fact) => compactMatch(store, fact));
|
|
13
|
+
const histories = buildHistories(store, matches);
|
|
14
|
+
const rules = compactRules(getProjectRules(store, spaceId), store);
|
|
15
|
+
const candidates = listCandidates(store, personalSpaceId)
|
|
16
|
+
.filter((candidate) => !candidate.targetSpaceId || candidate.targetSpaceId === spaceId)
|
|
17
|
+
.map(compactCandidate);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
query,
|
|
21
|
+
personalSpace: compactSpace(personalSpace, personalSpaceId),
|
|
22
|
+
space: compactSpace(space, spaceId),
|
|
23
|
+
rules,
|
|
24
|
+
matches,
|
|
25
|
+
histories,
|
|
26
|
+
candidateCount: candidates.length,
|
|
27
|
+
candidates,
|
|
28
|
+
answer: formatPackAnswer(matches)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatContextPack(pack) {
|
|
33
|
+
const lines = [
|
|
34
|
+
`Context ${pack.personalSpace.name} -> ${pack.space.name}`,
|
|
35
|
+
`Query ${pack.query || '(empty)'}`,
|
|
36
|
+
'Rules',
|
|
37
|
+
...formatRules(pack.rules),
|
|
38
|
+
'Matches',
|
|
39
|
+
...formatMatches(pack.matches),
|
|
40
|
+
...formatHistories(pack.histories),
|
|
41
|
+
`Candidates ${pack.candidateCount}`
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
return lines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function compactRules(rules, store) {
|
|
48
|
+
return {
|
|
49
|
+
spaceId: rules.spaceId,
|
|
50
|
+
spaceName: rules.spaceName,
|
|
51
|
+
forbidden: rules.forbidden.map((fact) => compactFact(store, fact)),
|
|
52
|
+
parameters: rules.parameters.map((fact) => compactFact(store, fact)),
|
|
53
|
+
links: rules.links.map((fact) => compactFact(store, fact))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function compactFact(store, fact) {
|
|
58
|
+
return {
|
|
59
|
+
id: fact.id,
|
|
60
|
+
spaceId: fact.spaceId,
|
|
61
|
+
spaceName: store.getSpace(fact.spaceId)?.name ?? fact.spaceId,
|
|
62
|
+
subject: fact.subject,
|
|
63
|
+
predicate: fact.predicate,
|
|
64
|
+
object: fact.object,
|
|
65
|
+
current: isCurrentFact(fact),
|
|
66
|
+
validAt: fact.validAt,
|
|
67
|
+
invalidAt: fact.invalidAt,
|
|
68
|
+
source: compactSource(store, fact.sourceEpisodeId)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function compactMatch(store, fact) {
|
|
73
|
+
const compact = compactFact(store, fact);
|
|
74
|
+
return {
|
|
75
|
+
spaceId: compact.spaceId,
|
|
76
|
+
spaceName: compact.spaceName,
|
|
77
|
+
current: compact.current,
|
|
78
|
+
fact: {
|
|
79
|
+
id: compact.id,
|
|
80
|
+
spaceId: compact.spaceId,
|
|
81
|
+
subject: compact.subject,
|
|
82
|
+
predicate: compact.predicate,
|
|
83
|
+
object: compact.object,
|
|
84
|
+
validAt: compact.validAt,
|
|
85
|
+
invalidAt: compact.invalidAt
|
|
86
|
+
},
|
|
87
|
+
source: compact.source
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildHistories(store, matches) {
|
|
92
|
+
const keys = new Set();
|
|
93
|
+
const histories = [];
|
|
94
|
+
|
|
95
|
+
for (const match of matches) {
|
|
96
|
+
const key = `${match.fact.spaceId}:${match.fact.predicate}`;
|
|
97
|
+
if (keys.has(key)) continue;
|
|
98
|
+
keys.add(key);
|
|
99
|
+
|
|
100
|
+
const history = getFactHistory(store, {
|
|
101
|
+
spaceId: match.fact.spaceId,
|
|
102
|
+
predicate: match.fact.predicate
|
|
103
|
+
});
|
|
104
|
+
if (history.facts.length <= 1) continue;
|
|
105
|
+
|
|
106
|
+
histories.push({
|
|
107
|
+
spaceId: history.spaceId,
|
|
108
|
+
spaceName: history.spaceName,
|
|
109
|
+
predicate: history.predicate,
|
|
110
|
+
facts: history.facts.map((fact) => compactFact(store, fact))
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return histories;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function compactCandidate(candidate) {
|
|
118
|
+
return {
|
|
119
|
+
id: candidate.id,
|
|
120
|
+
reason: candidate.reason,
|
|
121
|
+
createdAt: candidate.createdAt,
|
|
122
|
+
targetSpaceId: candidate.targetSpaceId,
|
|
123
|
+
targetSpaceName: candidate.targetSpaceName,
|
|
124
|
+
source: candidate.episode
|
|
125
|
+
? {
|
|
126
|
+
id: candidate.episode.id,
|
|
127
|
+
kind: candidate.episode.sourceKind,
|
|
128
|
+
uri: candidate.episode.sourceUri,
|
|
129
|
+
createdAt: candidate.episode.createdAt,
|
|
130
|
+
preview: firstLine(candidate.episode.body)
|
|
131
|
+
}
|
|
132
|
+
: null
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function compactSource(store, episodeId) {
|
|
137
|
+
const episode = store.getEpisode(episodeId);
|
|
138
|
+
if (!episode) return null;
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
id: episode.id,
|
|
142
|
+
kind: episode.sourceKind,
|
|
143
|
+
uri: episode.sourceUri,
|
|
144
|
+
createdAt: episode.createdAt,
|
|
145
|
+
preview: firstLine(episode.body)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function compactSpace(space, fallbackId) {
|
|
150
|
+
return {
|
|
151
|
+
id: space?.id ?? fallbackId,
|
|
152
|
+
name: space?.name ?? fallbackId,
|
|
153
|
+
kind: space?.kind ?? null
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function firstLine(value) {
|
|
158
|
+
return String(value).split(/\r?\n/)[0].slice(0, 160);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function formatPackAnswer(matches) {
|
|
162
|
+
if (matches.length === 0) {
|
|
163
|
+
return 'No matching current facts found.';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return matches
|
|
167
|
+
.map((match) => `${match.fact.subject} ${match.fact.predicate} ${match.fact.object}`)
|
|
168
|
+
.join('\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function formatRules(rules) {
|
|
172
|
+
const lines = [
|
|
173
|
+
...rules.forbidden.map((fact) => `forbids ${fact.object}`),
|
|
174
|
+
...rules.parameters.map((fact) => `${humanPredicate(fact.predicate)} ${fact.object}`),
|
|
175
|
+
...rules.links.map((fact) => `url ${fact.object}`)
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
return lines.length ? lines : ['(none)'];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function formatMatches(matches) {
|
|
182
|
+
return matches.length
|
|
183
|
+
? matches.map(
|
|
184
|
+
(match) =>
|
|
185
|
+
`${match.spaceName} ${humanPredicate(match.fact.predicate)} ${match.fact.object}`
|
|
186
|
+
)
|
|
187
|
+
: ['(none)'];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function formatHistories(histories) {
|
|
191
|
+
if (!histories?.length) return [];
|
|
192
|
+
|
|
193
|
+
return [
|
|
194
|
+
'History',
|
|
195
|
+
...histories.flatMap((history) =>
|
|
196
|
+
history.facts.map(
|
|
197
|
+
(fact) =>
|
|
198
|
+
`${fact.current ? 'current' : 'historical'} ${humanPredicate(history.predicate)} ${fact.object}`
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function humanPredicate(predicate) {
|
|
205
|
+
return predicate.startsWith('has_') ? predicate.slice(4) : predicate;
|
|
206
|
+
}
|
package/src/defaults.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_FULI_PORT = 2727;
|
package/src/extractor.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const URL_PATTERN = /https?:\/\/[^\s"',。))]+/g;
|
|
2
|
+
|
|
3
|
+
export function extractFactSpecs(subject, body) {
|
|
4
|
+
const lines = body
|
|
5
|
+
.split(/\r?\n/)
|
|
6
|
+
.map((line) => line.trim())
|
|
7
|
+
.filter(Boolean);
|
|
8
|
+
const specs = [];
|
|
9
|
+
|
|
10
|
+
for (const line of lines) {
|
|
11
|
+
const replacement = parseReplacement(subject, line);
|
|
12
|
+
if (replacement) {
|
|
13
|
+
specs.push(replacement);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const forbidden = parseForbidden(subject, line);
|
|
18
|
+
if (forbidden) {
|
|
19
|
+
specs.push(forbidden);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const keyValue = parseKeyValue(subject, line);
|
|
24
|
+
if (keyValue) {
|
|
25
|
+
specs.push(keyValue);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const url of line.match(URL_PATTERN) ?? []) {
|
|
30
|
+
specs.push({ kind: 'fact', subject, predicate: 'has_url', object: url });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return specs;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseReplacement(subject, line) {
|
|
38
|
+
const match = line.match(/^替代[::]\s*(.+?)\s*=>\s*(.+)$/);
|
|
39
|
+
if (!match) return null;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
kind: 'replacement',
|
|
43
|
+
subject,
|
|
44
|
+
oldValue: match[1].trim(),
|
|
45
|
+
newValue: match[2].trim()
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseForbidden(subject, line) {
|
|
50
|
+
const match =
|
|
51
|
+
line.match(/^禁止[::]\s*(.+)$/) ??
|
|
52
|
+
line.match(/^(?:这个项目|本项目|项目)?\s*(?:禁止|不要|不允许)\s*(?:使用|采用|引入|用)?\s*(.+)$/);
|
|
53
|
+
if (!match) return null;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
kind: 'fact',
|
|
57
|
+
subject,
|
|
58
|
+
predicate: 'forbids',
|
|
59
|
+
object: cleanForbiddenObject(match[1])
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function cleanForbiddenObject(value) {
|
|
64
|
+
return value.trim().replace(/[。.!!,,;;]+$/g, '').trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseKeyValue(subject, line) {
|
|
68
|
+
const match = line.match(/^([\p{L}\p{N}_.-]+)\s*[::]\s*(.+)$/u);
|
|
69
|
+
if (!match) return null;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
kind: 'fact',
|
|
73
|
+
subject,
|
|
74
|
+
predicate: `has_${match[1].trim()}`,
|
|
75
|
+
object: match[2].trim()
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isCurrentFact } from './models.js';
|
|
2
|
+
|
|
3
|
+
export function getFactHistory(store, { spaceId, predicate }) {
|
|
4
|
+
const normalizedPredicate = normalizePredicate(predicate);
|
|
5
|
+
const facts = store.listFacts({ includeHistorical: true })
|
|
6
|
+
.filter((fact) => fact.spaceId === spaceId && fact.predicate === normalizedPredicate)
|
|
7
|
+
.sort((left, right) => left.validAt.localeCompare(right.validAt))
|
|
8
|
+
.map((fact) => enrichFact(store, fact));
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
spaceId,
|
|
12
|
+
spaceName: store.getSpace(spaceId)?.name ?? spaceId,
|
|
13
|
+
predicate: normalizedPredicate,
|
|
14
|
+
facts
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function normalizePredicate(value) {
|
|
19
|
+
if (value.startsWith('has_') || value === 'forbids') return value;
|
|
20
|
+
return `has_${value}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function enrichFact(store, fact) {
|
|
24
|
+
const episode = store.getEpisode(fact.sourceEpisodeId);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...fact,
|
|
28
|
+
current: isCurrentFact(fact),
|
|
29
|
+
source: episode
|
|
30
|
+
? {
|
|
31
|
+
id: episode.id,
|
|
32
|
+
kind: episode.sourceKind,
|
|
33
|
+
uri: episode.sourceUri,
|
|
34
|
+
body: episode.body,
|
|
35
|
+
createdAt: episode.createdAt
|
|
36
|
+
}
|
|
37
|
+
: null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { FactScope, FactStatus } from '../models.js';
|
|
2
|
+
|
|
3
|
+
export class FactService {
|
|
4
|
+
constructor(store) {
|
|
5
|
+
this.store = store;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
writeSpecs({ spaceId, subject, episodeId, specs, scope = FactScope.PERSONAL }) {
|
|
9
|
+
const written = [];
|
|
10
|
+
for (const spec of specs) {
|
|
11
|
+
if (spec.kind === 'replacement') {
|
|
12
|
+
written.push(...this.#replaceFact({ spaceId, subject, episodeId, spec, scope }));
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (this.#hasCurrentFact(spaceId, subject, spec.predicate, spec.object)) continue;
|
|
17
|
+
|
|
18
|
+
const supersededFacts = this.#supersededCurrentFacts(
|
|
19
|
+
spaceId,
|
|
20
|
+
subject,
|
|
21
|
+
spec.predicate,
|
|
22
|
+
spec.object
|
|
23
|
+
);
|
|
24
|
+
const newFact = this.store.addFact({
|
|
25
|
+
spaceId,
|
|
26
|
+
subject,
|
|
27
|
+
predicate: spec.predicate,
|
|
28
|
+
object: spec.object,
|
|
29
|
+
sourceEpisodeId: episodeId,
|
|
30
|
+
status: FactStatus.CONFIRMED,
|
|
31
|
+
scope
|
|
32
|
+
});
|
|
33
|
+
written.push(newFact);
|
|
34
|
+
|
|
35
|
+
for (const oldFact of supersededFacts) {
|
|
36
|
+
this.store.invalidateFact(oldFact.id, newFact.id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return written;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#hasCurrentFact(spaceId, subject, predicate, object) {
|
|
43
|
+
return this.store.currentFacts(spaceId).some(
|
|
44
|
+
(fact) =>
|
|
45
|
+
fact.subject === subject &&
|
|
46
|
+
fact.predicate === predicate &&
|
|
47
|
+
fact.object === object
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#supersededCurrentFacts(spaceId, subject, predicate, object) {
|
|
52
|
+
if (!isUniqueParameterPredicate(predicate)) return [];
|
|
53
|
+
|
|
54
|
+
return this.store.currentFacts(spaceId).filter(
|
|
55
|
+
(fact) =>
|
|
56
|
+
fact.subject === subject &&
|
|
57
|
+
fact.predicate === predicate &&
|
|
58
|
+
fact.object !== object
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#replaceFact({ spaceId, subject, episodeId, spec, scope }) {
|
|
63
|
+
const oldFacts = this.store.currentFacts(spaceId).filter(
|
|
64
|
+
(fact) => fact.subject === subject && fact.object === spec.oldValue
|
|
65
|
+
);
|
|
66
|
+
const predicates = oldFacts.length
|
|
67
|
+
? [...new Set(oldFacts.map((fact) => fact.predicate))]
|
|
68
|
+
: ['has_replacement'];
|
|
69
|
+
const newFacts = predicates.map((predicate) => this.store.addFact({
|
|
70
|
+
spaceId,
|
|
71
|
+
subject,
|
|
72
|
+
predicate,
|
|
73
|
+
object: spec.newValue,
|
|
74
|
+
sourceEpisodeId: episodeId,
|
|
75
|
+
status: FactStatus.CONFIRMED,
|
|
76
|
+
scope
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
for (const oldFact of oldFacts) {
|
|
80
|
+
const replacement = newFacts.find((fact) => fact.predicate === oldFact.predicate) ?? newFacts[0];
|
|
81
|
+
this.store.invalidateFact(oldFact.id, replacement.id);
|
|
82
|
+
}
|
|
83
|
+
return newFacts;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isUniqueParameterPredicate(predicate) {
|
|
88
|
+
return predicate.startsWith('has_') && predicate !== 'has_url';
|
|
89
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mkdirSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
renameSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync
|
|
7
|
+
} from 'node:fs';
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { dirname, join, resolve } from 'node:path';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_POLICY = Object.freeze({
|
|
12
|
+
enabled: true,
|
|
13
|
+
updatedAt: null
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export class AgentAccessPolicyConfigurationError extends TypeError {}
|
|
17
|
+
|
|
18
|
+
export class AgentAccessPolicyStore {
|
|
19
|
+
constructor(path = null, { now = () => new Date().toISOString() } = {}) {
|
|
20
|
+
this.path = path ? resolve(path) : null;
|
|
21
|
+
this.now = now;
|
|
22
|
+
this.memory = { ...DEFAULT_POLICY };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
read() {
|
|
26
|
+
if (!this.path) return { ...this.memory };
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error?.code === 'ENOENT') return { ...DEFAULT_POLICY };
|
|
32
|
+
throw new AgentAccessPolicyConfigurationError(
|
|
33
|
+
'Agent access policy could not be read'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return validateStoredPolicy(parsed);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
update(input) {
|
|
40
|
+
if (!input || typeof input.enabled !== 'boolean') {
|
|
41
|
+
throw new TypeError('Agent access policy enabled must be a boolean');
|
|
42
|
+
}
|
|
43
|
+
const next = {
|
|
44
|
+
enabled: input.enabled,
|
|
45
|
+
updatedAt: this.now()
|
|
46
|
+
};
|
|
47
|
+
if (!this.path) {
|
|
48
|
+
this.memory = next;
|
|
49
|
+
return { ...next };
|
|
50
|
+
}
|
|
51
|
+
writeAtomically(this.path, { version: 1, ...next });
|
|
52
|
+
return { ...next };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function agentAccessPolicyPathForRuntime(runtimeConfigPath) {
|
|
57
|
+
if (!runtimeConfigPath) return null;
|
|
58
|
+
return join(dirname(resolve(runtimeConfigPath)), 'agent-access-policy.json');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function validateStoredPolicy(value) {
|
|
62
|
+
if (value?.version !== 1 || typeof value.enabled !== 'boolean') {
|
|
63
|
+
throw new AgentAccessPolicyConfigurationError('Agent access policy is invalid');
|
|
64
|
+
}
|
|
65
|
+
if (value.updatedAt !== null && typeof value.updatedAt !== 'string') {
|
|
66
|
+
throw new AgentAccessPolicyConfigurationError(
|
|
67
|
+
'Agent access policy updatedAt is invalid'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
enabled: value.enabled,
|
|
72
|
+
updatedAt: value.updatedAt ?? null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeAtomically(path, value) {
|
|
77
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
78
|
+
const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
79
|
+
try {
|
|
80
|
+
writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
81
|
+
encoding: 'utf8',
|
|
82
|
+
mode: 0o600
|
|
83
|
+
});
|
|
84
|
+
renameSync(temporaryPath, path);
|
|
85
|
+
} finally {
|
|
86
|
+
rmSync(temporaryPath, { force: true });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mkdirSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
renameSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync
|
|
7
|
+
} from 'node:fs';
|
|
8
|
+
import { dirname, join, resolve } from 'node:path';
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_POLICY = Object.freeze({
|
|
12
|
+
enabled: true,
|
|
13
|
+
updatedAt: null
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export class CapturePolicyConfigurationError extends TypeError {}
|
|
17
|
+
|
|
18
|
+
export class CapturePolicyStore {
|
|
19
|
+
constructor(path = null, { now = () => new Date().toISOString() } = {}) {
|
|
20
|
+
this.path = path ? resolve(path) : null;
|
|
21
|
+
this.now = now;
|
|
22
|
+
this.memory = { ...DEFAULT_POLICY };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
read() {
|
|
26
|
+
if (!this.path) return { ...this.memory };
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error?.code === 'ENOENT') return { ...DEFAULT_POLICY };
|
|
32
|
+
throw new CapturePolicyConfigurationError('Capture policy could not be read');
|
|
33
|
+
}
|
|
34
|
+
return validateStoredPolicy(parsed);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
update(input) {
|
|
38
|
+
if (!input || typeof input.enabled !== 'boolean') {
|
|
39
|
+
throw new TypeError('Capture policy enabled must be a boolean');
|
|
40
|
+
}
|
|
41
|
+
const next = {
|
|
42
|
+
enabled: input.enabled,
|
|
43
|
+
updatedAt: this.now()
|
|
44
|
+
};
|
|
45
|
+
if (!this.path) {
|
|
46
|
+
this.memory = next;
|
|
47
|
+
return { ...next };
|
|
48
|
+
}
|
|
49
|
+
writeAtomically(this.path, { version: 1, ...next });
|
|
50
|
+
return { ...next };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function capturePolicyPathForRuntime(runtimeConfigPath) {
|
|
55
|
+
if (!runtimeConfigPath) return null;
|
|
56
|
+
return join(dirname(resolve(runtimeConfigPath)), 'capture-policy.json');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateStoredPolicy(value) {
|
|
60
|
+
if (value?.version !== 1 || typeof value.enabled !== 'boolean') {
|
|
61
|
+
throw new CapturePolicyConfigurationError('Capture policy is invalid');
|
|
62
|
+
}
|
|
63
|
+
if (value.updatedAt !== null && typeof value.updatedAt !== 'string') {
|
|
64
|
+
throw new CapturePolicyConfigurationError('Capture policy updatedAt is invalid');
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
enabled: value.enabled,
|
|
68
|
+
updatedAt: value.updatedAt ?? null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function writeAtomically(path, value) {
|
|
73
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
74
|
+
const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
75
|
+
try {
|
|
76
|
+
writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
mode: 0o600
|
|
79
|
+
});
|
|
80
|
+
renameSync(temporaryPath, path);
|
|
81
|
+
} finally {
|
|
82
|
+
rmSync(temporaryPath, { force: true });
|
|
83
|
+
}
|
|
84
|
+
}
|