fuli-context 0.3.0 → 0.5.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.en.md +336 -0
- package/README.md +220 -174
- 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-CZAF_OlN.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-DlvcLwlL.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-Devf7glZ.css → index-CpLDx3of.css} +1 -1
- package/dist/web/assets/index-DAzeFJSc.js +2 -0
- 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 +3 -1
- package/skills/capturing-session-knowledge/SKILL.md +83 -13
- package/src/agent-tools/graph-definitions.js +181 -13
- package/src/agent-tools/graph-handlers.js +19 -0
- package/src/cli/command-registry.js +3 -58
- package/src/cli/local-runtime-command.js +16 -2
- package/src/cli/local-runtime-options.js +6 -2
- package/src/cli.js +2 -37
- 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-uri.js +29 -0
- package/src/http/request-policy.js +37 -2
- package/src/local-runtime/lifecycle.js +11 -1
- package/src/mcp/common-knowledge-preview-tokens.js +67 -0
- package/src/mcp/create-mcp-server.js +43 -7
- package/src/mcp/instructions.js +6 -7
- 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/server/lan-access.js +52 -0
- package/src/server.js +67 -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/graph-runtime.js +78 -9
- 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
|
|
@@ -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,3 +1,5 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
|
|
1
3
|
import { sendJson } from './response.js';
|
|
2
4
|
|
|
3
5
|
export function localServerAuthority(address) {
|
|
@@ -9,18 +11,38 @@ export function localServerAuthority(address) {
|
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export function rejectDisallowedRequest({ request, response, authority }) {
|
|
14
|
+
return rejectRequestOutsidePolicy({ request, response, authority });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function rejectRequestOutsidePolicy({
|
|
18
|
+
request,
|
|
19
|
+
response,
|
|
20
|
+
authority,
|
|
21
|
+
lanAuthorities = [],
|
|
22
|
+
lanAccessToken = null
|
|
23
|
+
}) {
|
|
12
24
|
const origin = request.headers.origin;
|
|
13
25
|
const fetchSite = request.headers['sec-fetch-site'];
|
|
26
|
+
const requestAuthority = request.headers.host;
|
|
27
|
+
const lanRequest = lanAuthorities.includes(requestAuthority);
|
|
28
|
+
const loopbackRequest = requestAuthority === authority;
|
|
14
29
|
if (
|
|
15
30
|
!authority ||
|
|
16
|
-
|
|
17
|
-
(
|
|
31
|
+
(requestAuthority !== authority && !lanRequest) ||
|
|
32
|
+
(lanAuthorities.length > 0 && loopbackRequest && !isLoopbackAddress(request.socket?.remoteAddress)) ||
|
|
33
|
+
(origin !== undefined && origin !== `http://${requestAuthority}`) ||
|
|
18
34
|
(typeof fetchSite === 'string' && fetchSite.toLowerCase() === 'cross-site')
|
|
19
35
|
) {
|
|
20
36
|
sendJson(response, 403, { error: 'Forbidden' });
|
|
21
37
|
return true;
|
|
22
38
|
}
|
|
23
39
|
|
|
40
|
+
if (lanRequest && !hasLanAccess(request.headers.authorization, lanAccessToken)) {
|
|
41
|
+
response.setHeader('www-authenticate', 'Basic realm="FULI LAN", charset="UTF-8"');
|
|
42
|
+
sendJson(response, 401, { error: 'LAN access code required' });
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
if (requiresJson(request) && !isJson(request.headers['content-type'])) {
|
|
25
47
|
sendJson(response, 415, { error: 'Unsupported media type' });
|
|
26
48
|
return true;
|
|
@@ -29,6 +51,19 @@ export function rejectDisallowedRequest({ request, response, authority }) {
|
|
|
29
51
|
return false;
|
|
30
52
|
}
|
|
31
53
|
|
|
54
|
+
function hasLanAccess(authorization, accessToken) {
|
|
55
|
+
if (typeof accessToken !== 'string' || !accessToken) return false;
|
|
56
|
+
const expected = `Basic ${Buffer.from(`fuli:${accessToken}`).toString('base64')}`;
|
|
57
|
+
if (typeof authorization !== 'string' || authorization.length !== expected.length) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return timingSafeEqual(Buffer.from(authorization), Buffer.from(expected));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isLoopbackAddress(address) {
|
|
64
|
+
return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
function requiresJson(request) {
|
|
33
68
|
if (request.method !== 'POST') return false;
|
|
34
69
|
const pathname = new URL(request.url, 'http://127.0.0.1').pathname;
|
|
@@ -20,6 +20,7 @@ export async function startLocalRuntime(input, dependencies = {}) {
|
|
|
20
20
|
paths: input.paths,
|
|
21
21
|
personalSpaceName: input.personalSpaceName,
|
|
22
22
|
port: input.port,
|
|
23
|
+
lan: input.lan === true,
|
|
23
24
|
personalOnly: !managesDevelopmentWorkspace,
|
|
24
25
|
buildProviders,
|
|
25
26
|
noStart: false,
|
|
@@ -99,7 +100,10 @@ export async function inspectLocalRuntime(input, dependencies = {}) {
|
|
|
99
100
|
console: {
|
|
100
101
|
status: consoleReady ? 'ready' : processAlive ? 'unverified' : 'stopped',
|
|
101
102
|
url: stateLooksOwned ? state.url : `http://127.0.0.1:${input.port}`,
|
|
102
|
-
pid: processAlive ? state.pid : null
|
|
103
|
+
pid: processAlive ? state.pid : null,
|
|
104
|
+
...(stateLooksOwned && state.lan === true
|
|
105
|
+
? { lan: true, lanUrls: safeLanUrls(state.lanUrls) }
|
|
106
|
+
: {})
|
|
103
107
|
},
|
|
104
108
|
personal,
|
|
105
109
|
public: {
|
|
@@ -237,6 +241,12 @@ function isRuntimeState(state) {
|
|
|
237
241
|
Number.isInteger(state.pid) && typeof state.url === 'string';
|
|
238
242
|
}
|
|
239
243
|
|
|
244
|
+
function safeLanUrls(value) {
|
|
245
|
+
return Array.isArray(value)
|
|
246
|
+
? value.filter((url) => typeof url === 'string' && url.startsWith('http://'))
|
|
247
|
+
: [];
|
|
248
|
+
}
|
|
249
|
+
|
|
240
250
|
function isProcessAlive(pid) {
|
|
241
251
|
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
242
252
|
}
|
|
@@ -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
|
}
|