fuli-context 0.2.0 → 0.4.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/README.md +94 -25
- package/dist/web/assets/ConnectionsPage-CCKWm-6p.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BZkB2wJn.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CrGYxEob.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CJCNI2Wd.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-PXH-TlGt.js +1 -0
- package/dist/web/assets/KnowledgePage-CNQXd15y.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-CXPE4GCD.js +4 -0
- package/dist/web/assets/OverviewPage-ul7g7HXh.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CC40Pw2L.js +1 -0
- package/dist/web/assets/PersonalProfilePage-S8ZcG2LU.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-CPIEcShD.js +1 -0
- package/dist/web/assets/PublicProjectsPage-C700sR7z.js +1 -0
- package/dist/web/assets/ReviewPage-7MDDUDgA.js +1 -0
- package/dist/web/assets/console-B_Ena2DS.js +5 -0
- package/dist/web/assets/identity-CjBwYSMX.js +1 -0
- package/dist/web/assets/index-BQRkOwPx.js +2 -0
- package/dist/web/assets/{index-Devf7glZ.css → index-CpLDx3of.css} +1 -1
- package/dist/web/assets/model-C-5RVIJ2.js +1 -0
- package/dist/web/assets/{paths-B790iniO.js → paths-BkULqmGI.js} +1 -1
- package/dist/web/assets/{vue-router-BYTAoWT3.js → vue-router-DnD3hUm7.js} +1 -1
- package/dist/web/index.html +5 -6
- package/graph-provider/fuli_graph/app.py +48 -0
- package/graph-provider/fuli_graph/common_knowledge.py +443 -0
- package/graph-provider/fuli_graph/common_knowledge_models.py +54 -0
- package/graph-provider/fuli_graph/graph_models.py +9 -0
- package/graph-provider/fuli_graph/graph_query.py +25 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +211 -0
- package/graph-provider/fuli_graph/knowledge_feedback_models.py +69 -0
- package/graph-provider/fuli_graph/knowledge_management.py +37 -0
- package/graph-provider/fuli_graph/knowledge_search.py +24 -2
- package/graph-provider/fuli_graph/models.py +69 -0
- package/graph-provider/fuli_graph/search_projection.py +59 -0
- package/graph-provider/fuli_graph/store_knowledge.py +2 -0
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +97 -2
- package/package.json +2 -1
- package/skills/capturing-session-knowledge/SKILL.md +86 -15
- package/src/agent-tools/graph-definitions.js +181 -13
- package/src/agent-tools/graph-handlers.js +19 -0
- package/src/cli/command-registry.js +6 -59
- package/src/cli/update-command.js +293 -0
- package/src/cli.js +7 -34
- package/src/graphiti/agent-knowledge-workflows.js +543 -0
- package/src/graphiti/federated-application.js +85 -104
- package/src/graphiti/knowledge-provider-mapping.js +91 -0
- package/src/graphiti/provider-client.js +15 -0
- package/src/graphiti/source-marker.js +1 -7
- package/src/graphiti/source-uri.js +29 -0
- package/src/http/static-handler.js +29 -3
- package/src/mcp/common-knowledge-preview-tokens.js +67 -0
- package/src/mcp/create-mcp-server.js +43 -7
- package/src/mcp/instructions.js +7 -8
- package/src/mcp/lifecycle-audit.js +27 -0
- package/src/mcp/task-context-registry.js +93 -0
- package/src/mcp/tool-annotations.js +16 -1
- package/src/mcp/tool-result.js +24 -0
- package/src/runtime-options.js +7 -8
- package/src/setup/agent-installation-status.js +14 -2
- package/src/setup/agents.js +12 -1
- package/src/setup/claude-code-config.js +194 -0
- package/src/setup/config-backup.js +20 -5
- package/src/setup/options.js +9 -1
- package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +0 -1
- package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +0 -1
- package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +0 -1
- package/dist/web/assets/KnowledgePage-Khuo3cNs.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +0 -4
- package/dist/web/assets/OverviewPage-DC4C4Fhn.js +0 -1
- package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +0 -1
- package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +0 -1
- package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +0 -1
- package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +0 -1
- package/dist/web/assets/ReviewPage-Dztk2jDG.js +0 -1
- package/dist/web/assets/console-tbvTUi6p.js +0 -1
- package/dist/web/assets/identity-B3s5CXhv.js +0 -1
- package/dist/web/assets/index-DwUDhQKc.js +0 -6
- package/dist/web/assets/model-DVT8sjy9.js +0 -1
- package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +0 -1
- package/src/cli/commands/candidates.js +0 -17
- package/src/cli/commands/ingestion.js +0 -32
- package/src/cli/commands/reads.js +0 -50
- package/src/cli/commands/spaces.js +0 -20
- package/src/cli/invocation.js +0 -47
- package/src/cli/migrate-command.js +0 -71
- package/src/cli/migration-destination.js +0 -117
- package/src/storage/import-json.js +0 -70
- package/src/storage/legacy-record-validation.js +0 -152
- package/src/storage/legacy-snapshot-validation.js +0 -32
|
@@ -23,6 +23,23 @@ import {
|
|
|
23
23
|
sourceConsoleUrl
|
|
24
24
|
} from './source-marker.js';
|
|
25
25
|
import { resolvePersonalProjectPath } from './project-path-context.js';
|
|
26
|
+
import { TaskContextRegistry } from '../mcp/task-context-registry.js';
|
|
27
|
+
import {
|
|
28
|
+
agentProjectResolution,
|
|
29
|
+
beginTaskContext as beginTaskContextWorkflow,
|
|
30
|
+
checkpointTaskKnowledge as checkpointTaskKnowledgeWorkflow,
|
|
31
|
+
discoverCommonKnowledgeCandidates as discoverCommonKnowledgeCandidatesWorkflow,
|
|
32
|
+
providerCommonKnowledgePromotion,
|
|
33
|
+
recordDecisionTrace as recordDecisionTraceWorkflow,
|
|
34
|
+
recordKnowledgeFeedback as recordKnowledgeFeedbackWorkflow,
|
|
35
|
+
searchCurrentProjectKnowledge as searchCurrentProjectKnowledgeWorkflow
|
|
36
|
+
} from './agent-knowledge-workflows.js';
|
|
37
|
+
import {
|
|
38
|
+
assertPublicKnowledgeEligible,
|
|
39
|
+
providerConfirmationActor,
|
|
40
|
+
providerConfirmationBasis,
|
|
41
|
+
providerEpisode
|
|
42
|
+
} from './knowledge-provider-mapping.js';
|
|
26
43
|
|
|
27
44
|
export function openFederatedGraphApplication({
|
|
28
45
|
runtimeConfigPath,
|
|
@@ -51,7 +68,9 @@ export class FederatedGraphApplication {
|
|
|
51
68
|
capturePolicyStore = new CapturePolicyStore(),
|
|
52
69
|
agentAccessPolicyStore = new AgentAccessPolicyStore(),
|
|
53
70
|
consoleUrl = sourceConsoleUrl(null),
|
|
54
|
-
projectPathResolver = resolvePersonalProjectPath
|
|
71
|
+
projectPathResolver = resolvePersonalProjectPath,
|
|
72
|
+
taskContextRegistry = new TaskContextRegistry(),
|
|
73
|
+
providerRequestTimeoutMs = undefined
|
|
55
74
|
} = {}) {
|
|
56
75
|
this.graphiti = true;
|
|
57
76
|
this.config = config;
|
|
@@ -59,10 +78,12 @@ export class FederatedGraphApplication {
|
|
|
59
78
|
this.agentAccessPolicyStore = agentAccessPolicyStore;
|
|
60
79
|
this.consoleUrl = consoleUrl;
|
|
61
80
|
this.projectPathResolver = projectPathResolver;
|
|
81
|
+
this.taskContextRegistry = taskContextRegistry;
|
|
62
82
|
this.personal = new GraphitiProviderClient({
|
|
63
83
|
baseUrl: config.personal.providerUrl,
|
|
64
84
|
accessToken: config.personal.accessToken,
|
|
65
|
-
fetchImpl
|
|
85
|
+
fetchImpl,
|
|
86
|
+
requestTimeoutMs: providerRequestTimeoutMs
|
|
66
87
|
});
|
|
67
88
|
this.workspaces = new Map(
|
|
68
89
|
config.workspaces.map((workspace) => {
|
|
@@ -72,13 +93,31 @@ export class FederatedGraphApplication {
|
|
|
72
93
|
client: new GraphitiProviderClient({
|
|
73
94
|
baseUrl: url,
|
|
74
95
|
accessToken: workspace.accessToken,
|
|
75
|
-
fetchImpl
|
|
96
|
+
fetchImpl,
|
|
97
|
+
requestTimeoutMs: providerRequestTimeoutMs
|
|
76
98
|
})
|
|
77
99
|
}];
|
|
78
100
|
})
|
|
79
101
|
);
|
|
80
102
|
}
|
|
81
103
|
|
|
104
|
+
async beginTaskContext(input) {
|
|
105
|
+
return beginTaskContextWorkflow(this, input);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async checkpointTaskKnowledge(input) {
|
|
109
|
+
return checkpointTaskKnowledgeWorkflow(this, input);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
verifyTaskCheckpoint({ sessionId }) {
|
|
113
|
+
return this.taskContextRegistry.verify(sessionId);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async recordDecisionTrace(input) {
|
|
117
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
118
|
+
return recordDecisionTraceWorkflow(this, input);
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
async captureSessionKnowledge(input) {
|
|
83
122
|
const capturePolicy = this.getCapturePolicy();
|
|
84
123
|
if (!capturePolicy.enabled) {
|
|
@@ -377,6 +416,36 @@ export class FederatedGraphApplication {
|
|
|
377
416
|
};
|
|
378
417
|
}
|
|
379
418
|
|
|
419
|
+
async searchCurrentProjectKnowledge(input) {
|
|
420
|
+
const resolution = await this.#resolvePreferenceProject({
|
|
421
|
+
personalProjectId: null,
|
|
422
|
+
projectPath: input.projectPath
|
|
423
|
+
});
|
|
424
|
+
return searchCurrentProjectKnowledgeWorkflow(this, resolution, input);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async discoverCommonKnowledgeCandidates(input) {
|
|
428
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
429
|
+
return discoverCommonKnowledgeCandidatesWorkflow(this, input);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async previewCommonKnowledgePromotion(input) {
|
|
433
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
434
|
+
assertSafeKnowledgeMutation(input);
|
|
435
|
+
return this.personal.previewCommonKnowledgePromotion(
|
|
436
|
+
providerCommonKnowledgePromotion(input)
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async applyCommonKnowledgePromotion(input) {
|
|
441
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
442
|
+
assertSafeKnowledgeMutation(input);
|
|
443
|
+
return this.personal.applyCommonKnowledgePromotion({
|
|
444
|
+
...providerCommonKnowledgePromotion(input),
|
|
445
|
+
operation_actor: input.operationActor ?? 'agent'
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
380
449
|
async getKnowledgeGraph({
|
|
381
450
|
spaceId,
|
|
382
451
|
providerUrl = null,
|
|
@@ -425,6 +494,17 @@ export class FederatedGraphApplication {
|
|
|
425
494
|
});
|
|
426
495
|
}
|
|
427
496
|
|
|
497
|
+
async recordKnowledgeFeedback(input) {
|
|
498
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
499
|
+
assertSafeKnowledgeMutation({
|
|
500
|
+
items: input.items.map((item) => ({
|
|
501
|
+
existenceReason: item.reason,
|
|
502
|
+
quadrantReason: item.evidenceSummary
|
|
503
|
+
}))
|
|
504
|
+
});
|
|
505
|
+
return recordKnowledgeFeedbackWorkflow(this, input);
|
|
506
|
+
}
|
|
507
|
+
|
|
428
508
|
async reviseKnowledgeItem(input) {
|
|
429
509
|
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
430
510
|
assertSafeKnowledgeMutation(input);
|
|
@@ -1039,22 +1119,11 @@ function preferenceConflictPairKey(leftId, rightId) {
|
|
|
1039
1119
|
return [leftId, rightId].sort().join('\u0000');
|
|
1040
1120
|
}
|
|
1041
1121
|
|
|
1042
|
-
function agentProjectResolution(resolution) {
|
|
1043
|
-
const result = {
|
|
1044
|
-
status: resolution.status,
|
|
1045
|
-
basis: resolution.basis,
|
|
1046
|
-
personal_project_id: resolution.personalProjectId
|
|
1047
|
-
};
|
|
1048
|
-
if (resolution.candidateCount !== undefined) {
|
|
1049
|
-
result.candidate_count = resolution.candidateCount;
|
|
1050
|
-
}
|
|
1051
|
-
return result;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
1122
|
function assertNoCredentials(input) {
|
|
1055
1123
|
const content = JSON.stringify({
|
|
1056
1124
|
name: input.name,
|
|
1057
1125
|
sourceDescription: input.sourceDescription,
|
|
1126
|
+
sourceUri: input.sourceUri,
|
|
1058
1127
|
sourceExcerpt: input.sourceExcerpt,
|
|
1059
1128
|
summary: input.summary,
|
|
1060
1129
|
entities: input.entities,
|
|
@@ -1120,6 +1189,7 @@ function assertSafeKnowledgeMutation(input) {
|
|
|
1120
1189
|
note: input.note,
|
|
1121
1190
|
newProjectName: input.newProjectName,
|
|
1122
1191
|
newProjectPurpose: input.newProjectPurpose,
|
|
1192
|
+
humanConfirmationReason: input.humanConfirmationReason,
|
|
1123
1193
|
batchItems: input.items?.map((item) => ({
|
|
1124
1194
|
existenceReason: item.existenceReason,
|
|
1125
1195
|
quadrantReason: item.quadrantReason
|
|
@@ -1133,95 +1203,6 @@ function assertSafeKnowledgeMutation(input) {
|
|
|
1133
1203
|
}
|
|
1134
1204
|
}
|
|
1135
1205
|
|
|
1136
|
-
function providerEpisode(input) {
|
|
1137
|
-
return {
|
|
1138
|
-
idempotency_key: input.idempotencyKey,
|
|
1139
|
-
session_id: input.sessionId,
|
|
1140
|
-
name: input.name,
|
|
1141
|
-
source_kind: input.sourceKind,
|
|
1142
|
-
source_description: input.sourceDescription,
|
|
1143
|
-
source_application: input.sourceApplication ?? null,
|
|
1144
|
-
source_turn_id: input.sourceTurnId ?? null,
|
|
1145
|
-
source_excerpt: input.sourceExcerpt ?? null,
|
|
1146
|
-
reference_time: input.referenceTime,
|
|
1147
|
-
summary: input.summary ?? '',
|
|
1148
|
-
sensitivity: input.sensitivity ?? 'normal',
|
|
1149
|
-
entities: input.entities.map((entity) => ({
|
|
1150
|
-
key: entity.key,
|
|
1151
|
-
name: entity.name,
|
|
1152
|
-
type: entity.type,
|
|
1153
|
-
summary: entity.summary ?? '',
|
|
1154
|
-
origin_quadrant: entity.originQuadrant ?? 'known_known',
|
|
1155
|
-
current_quadrant: entity.currentQuadrant ?? entity.originQuadrant ?? 'known_known',
|
|
1156
|
-
epistemic_status: entity.epistemicStatus ?? 'confirmed',
|
|
1157
|
-
confirmation_status: entity.confirmationStatus,
|
|
1158
|
-
confirmation_basis: providerConfirmationBasis(entity.confirmationBasis),
|
|
1159
|
-
reasoning_summary: entity.reasoningSummary ?? null,
|
|
1160
|
-
profile_aspect: entity.profileAspect ?? null,
|
|
1161
|
-
inheritance_mode: entity.inheritanceMode ?? 'local_only',
|
|
1162
|
-
inherited_project_ids: entity.inheritedProjectIds ?? [],
|
|
1163
|
-
attributes: entity.attributes ?? {}
|
|
1164
|
-
})),
|
|
1165
|
-
relationships: input.relationships.map((relationship) => ({
|
|
1166
|
-
key: relationship.key,
|
|
1167
|
-
source: relationship.source,
|
|
1168
|
-
target: relationship.target,
|
|
1169
|
-
type: relationship.type,
|
|
1170
|
-
fact: relationship.fact,
|
|
1171
|
-
valid_at: relationship.validAt ?? null,
|
|
1172
|
-
invalid_at: relationship.invalidAt ?? null,
|
|
1173
|
-
supersedes: relationship.supersedes ?? [],
|
|
1174
|
-
confidence: relationship.confidence ?? 1,
|
|
1175
|
-
origin_quadrant: relationship.originQuadrant ?? 'known_known',
|
|
1176
|
-
current_quadrant: relationship.currentQuadrant ??
|
|
1177
|
-
relationship.originQuadrant ?? 'known_known',
|
|
1178
|
-
epistemic_status: relationship.epistemicStatus ?? 'confirmed',
|
|
1179
|
-
confirmation_status: relationship.confirmationStatus,
|
|
1180
|
-
confirmation_basis: providerConfirmationBasis(relationship.confirmationBasis),
|
|
1181
|
-
reasoning_summary: relationship.reasoningSummary ?? null,
|
|
1182
|
-
profile_aspect: relationship.profileAspect ?? null,
|
|
1183
|
-
inheritance_mode: relationship.inheritanceMode ?? 'local_only',
|
|
1184
|
-
inherited_project_ids: relationship.inheritedProjectIds ?? [],
|
|
1185
|
-
attributes: relationship.attributes ?? {}
|
|
1186
|
-
}))
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
function assertPublicKnowledgeEligible(episode) {
|
|
1191
|
-
const blocked = [...episode.entities, ...episode.relationships].find((item) =>
|
|
1192
|
-
item.profile_aspect ||
|
|
1193
|
-
item.confirmation_status !== 'confirmed' ||
|
|
1194
|
-
!item.confirmation_basis?.confirmed_by ||
|
|
1195
|
-
!item.confirmation_basis?.confirmed_at ||
|
|
1196
|
-
!['user', 'authoritative_source'].includes(item.confirmation_basis.confirmed_by.kind)
|
|
1197
|
-
);
|
|
1198
|
-
if (blocked) {
|
|
1199
|
-
throw new TypeError(
|
|
1200
|
-
'Only knowledge with an auditable confirmation can enter public review'
|
|
1201
|
-
);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
function providerConfirmationBasis(basis) {
|
|
1206
|
-
if (!basis) return undefined;
|
|
1207
|
-
return {
|
|
1208
|
-
existence_reason: basis.existenceReason,
|
|
1209
|
-
quadrant_reason: basis.quadrantReason,
|
|
1210
|
-
proposed_by: providerConfirmationActor(basis.proposedBy),
|
|
1211
|
-
confirmed_by: providerConfirmationActor(basis.confirmedBy),
|
|
1212
|
-
confirmed_at: basis.confirmedAt ?? null,
|
|
1213
|
-
agent_policy_version: basis.agentPolicyVersion ?? null
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
function providerConfirmationActor(actor) {
|
|
1218
|
-
if (!actor) return null;
|
|
1219
|
-
return {
|
|
1220
|
-
kind: actor.kind,
|
|
1221
|
-
label: actor.label ?? null
|
|
1222
|
-
};
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
1206
|
function providerProjectProfile(profile) {
|
|
1226
1207
|
if (!profile || typeof profile !== 'object') throw new TypeError('Project profile is required');
|
|
1227
1208
|
const assessment = profile.assessment ? sanitizeProjectAssessment(profile.assessment) : null;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { onlineSourceUri } from './source-uri.js';
|
|
2
|
+
|
|
3
|
+
export function providerEpisode(input) {
|
|
4
|
+
return {
|
|
5
|
+
idempotency_key: input.idempotencyKey,
|
|
6
|
+
session_id: input.sessionId,
|
|
7
|
+
name: input.name,
|
|
8
|
+
source_kind: input.sourceKind,
|
|
9
|
+
source_description: input.sourceDescription,
|
|
10
|
+
source_uri: onlineSourceUri(input.sourceUri),
|
|
11
|
+
source_application: input.sourceApplication ?? null,
|
|
12
|
+
source_turn_id: input.sourceTurnId ?? null,
|
|
13
|
+
source_excerpt: input.sourceExcerpt ?? null,
|
|
14
|
+
reference_time: input.referenceTime,
|
|
15
|
+
summary: input.summary ?? '',
|
|
16
|
+
sensitivity: input.sensitivity ?? 'normal',
|
|
17
|
+
entities: input.entities.map((entity) => ({
|
|
18
|
+
key: entity.key,
|
|
19
|
+
name: entity.name,
|
|
20
|
+
type: entity.type,
|
|
21
|
+
summary: entity.summary ?? '',
|
|
22
|
+
origin_quadrant: entity.originQuadrant ?? 'known_known',
|
|
23
|
+
current_quadrant: entity.currentQuadrant ?? entity.originQuadrant ?? 'known_known',
|
|
24
|
+
epistemic_status: entity.epistemicStatus ?? 'confirmed',
|
|
25
|
+
confirmation_status: entity.confirmationStatus,
|
|
26
|
+
confirmation_basis: providerConfirmationBasis(entity.confirmationBasis),
|
|
27
|
+
reasoning_summary: entity.reasoningSummary ?? null,
|
|
28
|
+
profile_aspect: entity.profileAspect ?? null,
|
|
29
|
+
inheritance_mode: entity.inheritanceMode ?? 'local_only',
|
|
30
|
+
inherited_project_ids: entity.inheritedProjectIds ?? [],
|
|
31
|
+
attributes: entity.attributes ?? {}
|
|
32
|
+
})),
|
|
33
|
+
relationships: input.relationships.map((relationship) => ({
|
|
34
|
+
key: relationship.key,
|
|
35
|
+
source: relationship.source,
|
|
36
|
+
target: relationship.target,
|
|
37
|
+
type: relationship.type,
|
|
38
|
+
fact: relationship.fact,
|
|
39
|
+
valid_at: relationship.validAt ?? null,
|
|
40
|
+
invalid_at: relationship.invalidAt ?? null,
|
|
41
|
+
supersedes: relationship.supersedes ?? [],
|
|
42
|
+
confidence: relationship.confidence ?? 1,
|
|
43
|
+
origin_quadrant: relationship.originQuadrant ?? 'known_known',
|
|
44
|
+
current_quadrant: relationship.currentQuadrant ??
|
|
45
|
+
relationship.originQuadrant ?? 'known_known',
|
|
46
|
+
epistemic_status: relationship.epistemicStatus ?? 'confirmed',
|
|
47
|
+
confirmation_status: relationship.confirmationStatus,
|
|
48
|
+
confirmation_basis: providerConfirmationBasis(relationship.confirmationBasis),
|
|
49
|
+
reasoning_summary: relationship.reasoningSummary ?? null,
|
|
50
|
+
profile_aspect: relationship.profileAspect ?? null,
|
|
51
|
+
inheritance_mode: relationship.inheritanceMode ?? 'local_only',
|
|
52
|
+
inherited_project_ids: relationship.inheritedProjectIds ?? [],
|
|
53
|
+
attributes: relationship.attributes ?? {}
|
|
54
|
+
}))
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function assertPublicKnowledgeEligible(episode) {
|
|
59
|
+
const blocked = [...episode.entities, ...episode.relationships].find((item) =>
|
|
60
|
+
item.profile_aspect ||
|
|
61
|
+
item.confirmation_status !== 'confirmed' ||
|
|
62
|
+
!item.confirmation_basis?.confirmed_by ||
|
|
63
|
+
!item.confirmation_basis?.confirmed_at ||
|
|
64
|
+
!['user', 'authoritative_source'].includes(item.confirmation_basis.confirmed_by.kind)
|
|
65
|
+
);
|
|
66
|
+
if (blocked) {
|
|
67
|
+
throw new TypeError(
|
|
68
|
+
'Only knowledge with an auditable confirmation can enter public review'
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function providerConfirmationBasis(basis) {
|
|
74
|
+
if (!basis) return undefined;
|
|
75
|
+
return {
|
|
76
|
+
existence_reason: basis.existenceReason,
|
|
77
|
+
quadrant_reason: basis.quadrantReason,
|
|
78
|
+
proposed_by: providerConfirmationActor(basis.proposedBy),
|
|
79
|
+
confirmed_by: providerConfirmationActor(basis.confirmedBy),
|
|
80
|
+
confirmed_at: basis.confirmedAt ?? null,
|
|
81
|
+
agent_policy_version: basis.agentPolicyVersion ?? null
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function providerConfirmationActor(actor) {
|
|
86
|
+
if (!actor) return null;
|
|
87
|
+
return {
|
|
88
|
+
kind: actor.kind,
|
|
89
|
+
label: actor.label ?? null
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -133,6 +133,11 @@ export class GraphitiProviderClient {
|
|
|
133
133
|
method: 'POST', body: input
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
|
+
recordKnowledgeFeedback(input) {
|
|
137
|
+
return this.#request('/v1/knowledge/feedback', {
|
|
138
|
+
method: 'POST', body: input
|
|
139
|
+
});
|
|
140
|
+
}
|
|
136
141
|
reviewHumanChange(itemId, input) {
|
|
137
142
|
return this.#request(
|
|
138
143
|
`/v1/knowledge/items/${encodeURIComponent(itemId)}/agent-review`,
|
|
@@ -161,6 +166,16 @@ export class GraphitiProviderClient {
|
|
|
161
166
|
{ method: 'POST', body: input }
|
|
162
167
|
);
|
|
163
168
|
}
|
|
169
|
+
previewCommonKnowledgePromotion(input) {
|
|
170
|
+
return this.#request('/v1/knowledge/common-promotions/preview', {
|
|
171
|
+
method: 'POST', body: input
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
applyCommonKnowledgePromotion(input) {
|
|
175
|
+
return this.#request('/v1/knowledge/common-promotions', {
|
|
176
|
+
method: 'POST', body: input
|
|
177
|
+
});
|
|
178
|
+
}
|
|
164
179
|
deferPreferenceConflict(input) {
|
|
165
180
|
return this.#request('/v1/preference-conflicts/defer', {
|
|
166
181
|
method: 'POST', body: input
|
|
@@ -90,13 +90,7 @@ function sourceLeadMarkdown({ consoleUrl, citations }) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function sourceMarkerMarkdown({ consoleUrl, citations }) {
|
|
93
|
-
if (!citations.length)
|
|
94
|
-
return [
|
|
95
|
-
'**FULI 来源 · 未命中**',
|
|
96
|
-
'',
|
|
97
|
-
`- [打开 Fuli](${safeLoopbackOrigin(consoleUrl) ?? DEFAULT_CONSOLE_URL}/)`
|
|
98
|
-
].join('\n');
|
|
99
|
-
}
|
|
93
|
+
if (!citations.length) return '';
|
|
100
94
|
const shown = citations.slice(0, MAX_CITATIONS);
|
|
101
95
|
const hidden = citations.length - shown.length;
|
|
102
96
|
return [
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const SOURCE_URI_ERROR =
|
|
2
|
+
'Source URI must be an absolute HTTP(S) URI without credentials';
|
|
3
|
+
|
|
4
|
+
export function onlineSourceUri(value) {
|
|
5
|
+
if (value === undefined || value === null) return null;
|
|
6
|
+
if (
|
|
7
|
+
typeof value !== 'string'
|
|
8
|
+
|| value.length < 1
|
|
9
|
+
|| value.length > 2048
|
|
10
|
+
|| /(?:\s|\p{C})/u.test(value)
|
|
11
|
+
) {
|
|
12
|
+
throw new TypeError(SOURCE_URI_ERROR);
|
|
13
|
+
}
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = new URL(value);
|
|
17
|
+
} catch {
|
|
18
|
+
throw new TypeError(SOURCE_URI_ERROR);
|
|
19
|
+
}
|
|
20
|
+
if (
|
|
21
|
+
!['http:', 'https:'].includes(parsed.protocol)
|
|
22
|
+
|| !parsed.hostname
|
|
23
|
+
|| parsed.username
|
|
24
|
+
|| parsed.password
|
|
25
|
+
) {
|
|
26
|
+
throw new TypeError(SOURCE_URI_ERROR);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { extname, isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
5
|
const PROJECT_ROOT = resolve(fileURLToPath(new URL('../..', import.meta.url)));
|
|
6
6
|
export const WEB_ROOT = join(PROJECT_ROOT, 'dist', 'web');
|
|
7
|
+
const FULI_LOGO_PATTERN = /^fuli-logo(?:-[A-Za-z0-9_-]+)?\.png$/;
|
|
7
8
|
const MIME_TYPES = {
|
|
8
9
|
'.html': 'text/html; charset=utf-8',
|
|
9
10
|
'.css': 'text/css; charset=utf-8',
|
|
@@ -18,8 +19,17 @@ const MIME_TYPES = {
|
|
|
18
19
|
|
|
19
20
|
export function serveStatic(pathname, response) {
|
|
20
21
|
if (pathname === '/favicon.ico') {
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
const filePath = faviconFilePath();
|
|
23
|
+
if (!filePath) {
|
|
24
|
+
response.writeHead(204);
|
|
25
|
+
response.end();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
response.writeHead(200, {
|
|
29
|
+
'cache-control': 'public, max-age=86400',
|
|
30
|
+
'content-type': 'image/png'
|
|
31
|
+
});
|
|
32
|
+
response.end(readFileSync(filePath));
|
|
23
33
|
return;
|
|
24
34
|
}
|
|
25
35
|
|
|
@@ -50,6 +60,22 @@ export function serveStatic(pathname, response) {
|
|
|
50
60
|
response.end(readFileSync(filePath));
|
|
51
61
|
}
|
|
52
62
|
|
|
63
|
+
function faviconFilePath() {
|
|
64
|
+
const directPath = join(WEB_ROOT, 'favicon.png');
|
|
65
|
+
if (existsSync(directPath)) return directPath;
|
|
66
|
+
|
|
67
|
+
const assetsPath = join(WEB_ROOT, 'assets');
|
|
68
|
+
if (!existsSync(assetsPath)) return null;
|
|
69
|
+
try {
|
|
70
|
+
const filename = readdirSync(assetsPath)
|
|
71
|
+
.sort()
|
|
72
|
+
.find((candidate) => FULI_LOGO_PATTERN.test(candidate));
|
|
73
|
+
return filename ? join(assetsPath, filename) : null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
53
79
|
function shouldServeApplication(pathname) {
|
|
54
80
|
return extname(pathname) === '' && !pathname.startsWith('/api/');
|
|
55
81
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { ApplicationError } from '../app/application-error.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
export function createCommonKnowledgePreviewTokens({
|
|
8
|
+
now = Date.now,
|
|
9
|
+
createToken = randomUUID,
|
|
10
|
+
ttlMs = DEFAULT_TTL_MS
|
|
11
|
+
} = {}) {
|
|
12
|
+
const previews = new Map();
|
|
13
|
+
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
issue(input) {
|
|
16
|
+
pruneExpired(previews, now());
|
|
17
|
+
const token = createToken();
|
|
18
|
+
previews.set(token, {
|
|
19
|
+
expiresAt: now() + ttlMs,
|
|
20
|
+
intent: promotionIntent(input)
|
|
21
|
+
});
|
|
22
|
+
return token;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
consume(token, input) {
|
|
26
|
+
if (!token) {
|
|
27
|
+
throw new ApplicationError(
|
|
28
|
+
'preview_required',
|
|
29
|
+
'Preview this exact common-knowledge promotion before applying it'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const preview = previews.get(token);
|
|
33
|
+
if (!preview || preview.expiresAt <= now()) {
|
|
34
|
+
previews.delete(token);
|
|
35
|
+
throw new ApplicationError(
|
|
36
|
+
'preview_expired',
|
|
37
|
+
'Common-knowledge preview authorization is missing or expired'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (preview.intent !== promotionIntent(input)) {
|
|
41
|
+
throw new ApplicationError(
|
|
42
|
+
'preview_mismatch',
|
|
43
|
+
'Common-knowledge promotion no longer matches its preview'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
previews.delete(token);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function promotionIntent(input) {
|
|
52
|
+
return JSON.stringify({
|
|
53
|
+
personalSpaceId: input.personalSpaceId,
|
|
54
|
+
parentProjectId: input.parentProjectId,
|
|
55
|
+
itemKind: input.itemKind,
|
|
56
|
+
canonicalItemId: input.canonicalItemId,
|
|
57
|
+
duplicateItemIds: [...(input.duplicateItemIds ?? [])].sort(),
|
|
58
|
+
reason: input.reason,
|
|
59
|
+
humanConfirmationReason: input.humanConfirmationReason
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function pruneExpired(previews, currentTime) {
|
|
64
|
+
for (const [token, preview] of previews) {
|
|
65
|
+
if (preview.expiresAt <= currentTime) previews.delete(token);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -5,13 +5,23 @@ import { callAgentTool, listAgentTools } from '../agent-tools.js';
|
|
|
5
5
|
import { FULI_VERSION } from '../package-metadata.js';
|
|
6
6
|
import { MCP_INSTRUCTIONS } from './instructions.js';
|
|
7
7
|
import { createProjectActionPreviewTokens } from './project-action-preview-tokens.js';
|
|
8
|
+
import { createCommonKnowledgePreviewTokens } from './common-knowledge-preview-tokens.js';
|
|
9
|
+
import { auditLifecycleTool } from './lifecycle-audit.js';
|
|
8
10
|
import { annotationsFor } from './tool-annotations.js';
|
|
9
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
errorToolResult,
|
|
13
|
+
hookAdditionalContextToolResult,
|
|
14
|
+
protocolErrorResult,
|
|
15
|
+
successToolResult
|
|
16
|
+
} from './tool-result.js';
|
|
10
17
|
import { jsonSchemaToZod, openObjectSchema } from './tool-schema.js';
|
|
11
18
|
|
|
12
19
|
const TOOL_RESULT_LIMIT_BYTES = Object.freeze({
|
|
20
|
+
begin_task_context: 16 * 1024,
|
|
13
21
|
get_collaboration_preferences: 16 * 1024,
|
|
14
|
-
search_knowledge_graph: 32 * 1024
|
|
22
|
+
search_knowledge_graph: 32 * 1024,
|
|
23
|
+
search_current_project_knowledge: 64 * 1024,
|
|
24
|
+
discover_common_knowledge_candidates: 32 * 1024
|
|
15
25
|
});
|
|
16
26
|
|
|
17
27
|
export function createMcpServer(app) {
|
|
@@ -27,6 +37,7 @@ export function createMcpServer(app) {
|
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
function createToolMap(app, projectActionPreviews) {
|
|
40
|
+
const commonKnowledgePreviews = createCommonKnowledgePreviewTokens();
|
|
30
41
|
return new Map(listAgentTools().map((definition) => [definition.name, {
|
|
31
42
|
definition,
|
|
32
43
|
schema: jsonSchemaToZod(definition.inputSchema),
|
|
@@ -34,7 +45,8 @@ function createToolMap(app, projectActionPreviews) {
|
|
|
34
45
|
app,
|
|
35
46
|
definition.name,
|
|
36
47
|
input,
|
|
37
|
-
projectActionPreviews
|
|
48
|
+
projectActionPreviews,
|
|
49
|
+
commonKnowledgePreviews
|
|
38
50
|
)
|
|
39
51
|
}]));
|
|
40
52
|
}
|
|
@@ -62,15 +74,29 @@ function registerCallHandler(server, tools) {
|
|
|
62
74
|
|
|
63
75
|
async function invokeTool(tool, input) {
|
|
64
76
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
const value = await tool.invoke(input);
|
|
78
|
+
auditLifecycleTool(tool.definition.name);
|
|
79
|
+
const limitBytes = TOOL_RESULT_LIMIT_BYTES[tool.definition.name];
|
|
80
|
+
if (tool.definition.name === 'begin_task_context') {
|
|
81
|
+
return hookAdditionalContextToolResult(value, {
|
|
82
|
+
hookEventName: 'UserPromptSubmit',
|
|
83
|
+
label: 'Fuli task context. Apply effective_preferences and use taskContextToken for the final checkpoint.',
|
|
84
|
+
limitBytes
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return successToolResult(value, { limitBytes });
|
|
68
88
|
} catch (error) {
|
|
69
89
|
return errorToolResult(error);
|
|
70
90
|
}
|
|
71
91
|
}
|
|
72
92
|
|
|
73
|
-
async function invokeAgentTool(
|
|
93
|
+
async function invokeAgentTool(
|
|
94
|
+
app,
|
|
95
|
+
name,
|
|
96
|
+
input,
|
|
97
|
+
projectActionPreviews,
|
|
98
|
+
commonKnowledgePreviews
|
|
99
|
+
) {
|
|
74
100
|
if (name === 'preview_personal_project_action') {
|
|
75
101
|
const result = await callAgentTool(app, name, input);
|
|
76
102
|
return {
|
|
@@ -81,5 +107,15 @@ async function invokeAgentTool(app, name, input, projectActionPreviews) {
|
|
|
81
107
|
if (name === 'apply_personal_project_action') {
|
|
82
108
|
projectActionPreviews.consume(input.previewToken, input);
|
|
83
109
|
}
|
|
110
|
+
if (name === 'preview_common_knowledge_promotion') {
|
|
111
|
+
const result = await callAgentTool(app, name, input);
|
|
112
|
+
return {
|
|
113
|
+
...result,
|
|
114
|
+
previewToken: commonKnowledgePreviews.issue(input)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (name === 'apply_common_knowledge_promotion') {
|
|
118
|
+
commonKnowledgePreviews.consume(input.previewToken, input);
|
|
119
|
+
}
|
|
84
120
|
return callAgentTool(app, name, input);
|
|
85
121
|
}
|
package/src/mcp/instructions.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
export const MCP_INSTRUCTIONS = [
|
|
2
|
-
"Each user task: call exactly get_collaboration_preferences before other tools/answer
|
|
3
|
-
'
|
|
4
|
-
'
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'Searches return terminal-safe Markdown. Final MUST begin with sourceMarker.leadMarkdown and append sourceMarker.markdown unchanged; without support use noMatchSourceMarker. Never wrap markers in HTML, rewrite them, or cite writes as reads.'
|
|
2
|
+
"Each user task: if begin_task_context supplied hook-provided task context, apply. Otherwise call exactly get_collaboration_preferences with projectPath=cwd before other tools/answer as the fallback; never substitute a project action or Fuli tool. Apply all effective_preferences: personal-global everywhere plus only Fuli's matched project. Resolve relevant deferred_conflict first. Never guess personalProjectId. Writes enforce preferences in the actual payload; final text is not compliance.",
|
|
3
|
+
'For current-project facts use search_current_project_knowledge: exact active child first, then inheritable parent knowledge via outgoing PART_OF/USES_KNOWLEDGE_FROM within two hops. Child stable keys override. Never inherit project preferences or traverse RELATED_TO.',
|
|
4
|
+
'Before claiming facts unknown, use search_knowledge_graph. Use exact IDs for extra projects; mark pending. Widen a location miss to all_local_confirmed only after consent, then use rg only in the safe current repo/workspace. Never scan outside it. Live URLs need deployment evidence; changing state needs monitoring/Git MCP.',
|
|
5
|
+
'Use record_decision_trace for confirmed choices/rationale/validation. Use record_knowledge_feedback when retained knowledge is rejected, contradicted, outdated, or fails validation; preserve history.',
|
|
6
|
+
'Batch durable confirmed knowledge via capture_session_knowledge in exact scope. Never store secrets, transcripts, or guesses. Only human/source confirmation permits public review. Finish a hook-managed task with checkpoint_task_knowledge once: capture_candidates or retain_nothing; fallback Agents likewise flush or retain nothing.',
|
|
7
|
+
'When personal knowledge materially affects the answer/action, call record_knowledge_usage. Retrieval/injection never counts; it creates at most agent_confirmed and is never public-eligible.',
|
|
8
|
+
'Terminal-safe Markdown. Final MUST begin with sourceMarker.leadMarkdown; append sourceMarker.markdown unchanged only on match. On miss use noMatchSourceMarker leadMarkdown only (empty markdown). Never wrap markers in HTML or cite writes.'
|
|
10
9
|
].join(' ');
|