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
package/src/cli.js
CHANGED
|
@@ -3,70 +3,43 @@ import { realpathSync } from 'node:fs';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
dispatchCommand,
|
|
7
6
|
isLocalRuntimeCommand,
|
|
8
|
-
isRegisteredCommand,
|
|
9
7
|
printHelp
|
|
10
8
|
} from './cli/command-registry.js';
|
|
11
|
-
import { parseCliInvocation } from './cli/invocation.js';
|
|
12
9
|
import { runLocalRuntimeCommand } from './cli/local-runtime-command.js';
|
|
13
|
-
import { migrateLegacyJson } from './cli/migrate-command.js';
|
|
14
10
|
import { runSetupCommand } from './cli/setup-command.js';
|
|
15
11
|
import { runUninstallCommand } from './cli/uninstall-command.js';
|
|
12
|
+
import { runUpdateCommand } from './cli/update-command.js';
|
|
16
13
|
import { FULI_VERSION } from './package-metadata.js';
|
|
17
14
|
import { assertSupportedNodeVersion } from './setup/node-runtime.js';
|
|
18
|
-
import {
|
|
19
|
-
openLocalApplication,
|
|
20
|
-
resolveRuntimeOptions
|
|
21
|
-
} from './runtime-options.js';
|
|
22
15
|
|
|
23
16
|
export async function main(argv = process.argv.slice(2), env = process.env) {
|
|
24
|
-
const
|
|
17
|
+
const [command, ...commandArgs] = argv;
|
|
25
18
|
if (!command || command === '--help' || command === '-h') {
|
|
26
|
-
if (runtimeArgs.length) resolveRuntimeOptions(runtimeArgs, {});
|
|
27
19
|
printHelp();
|
|
28
20
|
return;
|
|
29
21
|
}
|
|
30
22
|
if (command === '--version' || command === '-v') {
|
|
31
|
-
if (runtimeArgs.length) resolveRuntimeOptions(runtimeArgs, {});
|
|
32
23
|
console.log(FULI_VERSION);
|
|
33
24
|
return;
|
|
34
25
|
}
|
|
35
|
-
if (command === 'migrate') {
|
|
36
|
-
if (runtimeArgs.length) resolveRuntimeOptions(runtimeArgs, {});
|
|
37
|
-
console.log(JSON.stringify(migrateLegacyJson(commandArgs)));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
26
|
if (command === 'setup') {
|
|
41
|
-
if (runtimeArgs.length) {
|
|
42
|
-
throw new TypeError('Setup options must appear after the setup command');
|
|
43
|
-
}
|
|
44
27
|
assertSupportedNodeVersion();
|
|
45
28
|
await runSetupCommand(commandArgs, { env });
|
|
46
29
|
return;
|
|
47
30
|
}
|
|
48
31
|
if (command === 'uninstall') {
|
|
49
|
-
if (runtimeArgs.length) {
|
|
50
|
-
throw new TypeError('Uninstall options must appear after the uninstall command');
|
|
51
|
-
}
|
|
52
32
|
await runUninstallCommand(commandArgs, { env });
|
|
53
33
|
return;
|
|
54
34
|
}
|
|
35
|
+
if (command === 'update') {
|
|
36
|
+
assertSupportedNodeVersion();
|
|
37
|
+
return runUpdateCommand(commandArgs, { env });
|
|
38
|
+
}
|
|
55
39
|
if (isLocalRuntimeCommand(command)) {
|
|
56
|
-
if (runtimeArgs.length) {
|
|
57
|
-
throw new TypeError(`${command} options must appear after the command`);
|
|
58
|
-
}
|
|
59
40
|
return runLocalRuntimeCommand(command, commandArgs, { env });
|
|
60
41
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const options = resolveRuntimeOptions(runtimeArgs, env);
|
|
64
|
-
const app = openLocalApplication(options);
|
|
65
|
-
try {
|
|
66
|
-
dispatchCommand(app, command, commandArgs);
|
|
67
|
-
} finally {
|
|
68
|
-
app.close();
|
|
69
|
-
}
|
|
42
|
+
throw new Error(`Unknown command: ${command}`);
|
|
70
43
|
}
|
|
71
44
|
|
|
72
45
|
if (isMainModule(import.meta.url, process.argv[1])) {
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { onlineSourceUri } from './source-uri.js';
|
|
4
|
+
|
|
5
|
+
export async function beginTaskContext(application, { sessionId, projectPath }) {
|
|
6
|
+
const preferences = await application.getCollaborationPreferences({
|
|
7
|
+
projectPath,
|
|
8
|
+
agentInvocation: true,
|
|
9
|
+
agentToolName: 'begin_task_context'
|
|
10
|
+
});
|
|
11
|
+
const task = application.taskContextRegistry.begin({
|
|
12
|
+
sessionId,
|
|
13
|
+
personalProjectId: preferences.context.personal_project_id
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
taskContextToken: task.token,
|
|
17
|
+
task_context_token: task.token,
|
|
18
|
+
checkpoint_required: true,
|
|
19
|
+
previous_checkpoint_missing: task.previousCheckpointMissing,
|
|
20
|
+
...preferences,
|
|
21
|
+
task_guidance: {
|
|
22
|
+
retrieval: 'Use search_current_project_knowledge for project work; it resolves the current project and inherited parent knowledge without copying IDs.',
|
|
23
|
+
checkpoint: 'Before finishing, call checkpoint_task_knowledge with capture_candidates or retain_nothing.'
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function checkpointTaskKnowledge(application, {
|
|
29
|
+
taskContextToken,
|
|
30
|
+
disposition,
|
|
31
|
+
reason,
|
|
32
|
+
capture = null
|
|
33
|
+
}) {
|
|
34
|
+
if (!['capture_candidates', 'retain_nothing'].includes(disposition)) {
|
|
35
|
+
throw new TypeError('Unknown task knowledge checkpoint disposition');
|
|
36
|
+
}
|
|
37
|
+
const task = application.taskContextRegistry.context(taskContextToken);
|
|
38
|
+
|
|
39
|
+
let captureResult = null;
|
|
40
|
+
if (disposition === 'capture_candidates') {
|
|
41
|
+
if (!capture) {
|
|
42
|
+
throw new TypeError('capture_candidates requires a bounded capture payload');
|
|
43
|
+
}
|
|
44
|
+
captureResult = await application.captureSessionKnowledge({
|
|
45
|
+
targetKind: 'personal',
|
|
46
|
+
spaceId: application.config.personal.spaceId,
|
|
47
|
+
personalProjectId: task.personalProjectId,
|
|
48
|
+
sessionId: task.sessionId,
|
|
49
|
+
...capture
|
|
50
|
+
});
|
|
51
|
+
} else if (capture) {
|
|
52
|
+
throw new TypeError('retain_nothing cannot include a capture payload');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
application.taskContextRegistry.checkpoint(taskContextToken, {
|
|
56
|
+
disposition,
|
|
57
|
+
reason,
|
|
58
|
+
captureStatus: captureResult?.status ?? null
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
status: 'checkpointed',
|
|
62
|
+
disposition,
|
|
63
|
+
reason,
|
|
64
|
+
personal_project_id: task.personalProjectId,
|
|
65
|
+
capture: captureResult
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function recordDecisionTrace(application, input) {
|
|
70
|
+
const trace = decisionTraceKnowledge(input);
|
|
71
|
+
return application.captureSessionKnowledge({
|
|
72
|
+
targetKind: 'personal',
|
|
73
|
+
spaceId: input.personalSpaceId,
|
|
74
|
+
personalProjectId: input.personalProjectId ?? null,
|
|
75
|
+
sessionId: input.sessionId,
|
|
76
|
+
idempotencyKey: input.idempotencyKey,
|
|
77
|
+
name: `Decision · ${input.title}`,
|
|
78
|
+
sourceKind: input.sourceKind,
|
|
79
|
+
sourceDescription: input.sourceDescription,
|
|
80
|
+
sourceUri: input.sourceUri ?? null,
|
|
81
|
+
sourceApplication: input.sourceApplication ?? null,
|
|
82
|
+
sourceTurnId: input.sourceTurnId ?? null,
|
|
83
|
+
referenceTime: input.referenceTime,
|
|
84
|
+
summary: `${input.question}\nSelected: ${input.selectedOption.label}`,
|
|
85
|
+
sensitivity: input.sensitivity ?? 'normal',
|
|
86
|
+
...trace
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function searchCurrentProjectKnowledge(
|
|
91
|
+
application,
|
|
92
|
+
resolution,
|
|
93
|
+
{
|
|
94
|
+
queries,
|
|
95
|
+
limitPerQuery = 12,
|
|
96
|
+
includeHistorical = false,
|
|
97
|
+
includePending = false
|
|
98
|
+
}
|
|
99
|
+
) {
|
|
100
|
+
const projectResolution = agentProjectResolution(resolution);
|
|
101
|
+
if (!resolution.personalProjectId) {
|
|
102
|
+
return {
|
|
103
|
+
status: 'project_unresolved',
|
|
104
|
+
personal_space_id: application.config.personal.spaceId,
|
|
105
|
+
personal_project_id: null,
|
|
106
|
+
project_resolution: projectResolution,
|
|
107
|
+
results: [],
|
|
108
|
+
required_action: resolution.status === 'ambiguous'
|
|
109
|
+
? 'Run from the exact project directory or explicitly choose one project; Fuli will not guess among workspace children.'
|
|
110
|
+
: 'Register this repository or directory as one local personal project before project-scoped search.'
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const results = await Promise.all(queries.map((query) =>
|
|
115
|
+
application.searchKnowledge({
|
|
116
|
+
personalSpaceId: application.config.personal.spaceId,
|
|
117
|
+
personalProjectId: resolution.personalProjectId,
|
|
118
|
+
query,
|
|
119
|
+
limit: limitPerQuery,
|
|
120
|
+
includeHistorical,
|
|
121
|
+
includePending,
|
|
122
|
+
agentInvocation: true,
|
|
123
|
+
agentToolName: 'search_current_project_knowledge'
|
|
124
|
+
})
|
|
125
|
+
));
|
|
126
|
+
return {
|
|
127
|
+
status: 'searched',
|
|
128
|
+
personal_space_id: application.config.personal.spaceId,
|
|
129
|
+
personal_project_id: resolution.personalProjectId,
|
|
130
|
+
project_resolution: projectResolution,
|
|
131
|
+
scope_policy: {
|
|
132
|
+
local_project_first: true,
|
|
133
|
+
inherited_relation_types: ['PART_OF', 'USES_KNOWLEDGE_FROM'],
|
|
134
|
+
max_inheritance_hops: 2,
|
|
135
|
+
local_same_key_overrides_parent: true,
|
|
136
|
+
unrelated_relations_expand_scope: false
|
|
137
|
+
},
|
|
138
|
+
results
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function discoverCommonKnowledgeCandidates(application, {
|
|
143
|
+
personalSpaceId,
|
|
144
|
+
parentProjectId,
|
|
145
|
+
query,
|
|
146
|
+
minChildProjects = 2,
|
|
147
|
+
similarityThreshold = 0.72,
|
|
148
|
+
limitPerProject = 12
|
|
149
|
+
}) {
|
|
150
|
+
if (!Number.isInteger(minChildProjects) || minChildProjects < 2) {
|
|
151
|
+
throw new TypeError('Common knowledge needs evidence from at least two child projects');
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
typeof similarityThreshold !== 'number'
|
|
155
|
+
|| similarityThreshold < 0
|
|
156
|
+
|| similarityThreshold > 1
|
|
157
|
+
) {
|
|
158
|
+
throw new TypeError('Similarity threshold must be between 0 and 1');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const graph = await application.personal.graph(personalSpaceId, 2000);
|
|
162
|
+
const projectIdByNodeId = new Map(
|
|
163
|
+
(graph.nodes ?? [])
|
|
164
|
+
.filter(({ type }) => type === 'PersonalProject')
|
|
165
|
+
.map((node) => [node.id, node.attributes?.projectId])
|
|
166
|
+
.filter(([, projectId]) => typeof projectId === 'string' && projectId)
|
|
167
|
+
);
|
|
168
|
+
if (![...projectIdByNodeId.values()].includes(parentProjectId)) {
|
|
169
|
+
throw new TypeError('Parent project is not present in the active personal graph');
|
|
170
|
+
}
|
|
171
|
+
const childProjectIds = [...new Set(
|
|
172
|
+
(graph.edges ?? [])
|
|
173
|
+
.filter(({ type, source, target }) =>
|
|
174
|
+
type === 'PART_OF'
|
|
175
|
+
&& projectIdByNodeId.get(target) === parentProjectId
|
|
176
|
+
&& projectIdByNodeId.has(source)
|
|
177
|
+
)
|
|
178
|
+
.map(({ source }) => projectIdByNodeId.get(source))
|
|
179
|
+
)].sort();
|
|
180
|
+
|
|
181
|
+
if (childProjectIds.length < minChildProjects) {
|
|
182
|
+
return commonCandidateResult({
|
|
183
|
+
status: childProjectIds.length === 0 ? 'no_children' : 'insufficient_children',
|
|
184
|
+
personalSpaceId,
|
|
185
|
+
parentProjectId,
|
|
186
|
+
childProjectIds,
|
|
187
|
+
query,
|
|
188
|
+
candidates: [],
|
|
189
|
+
graph
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const childResults = await Promise.all(childProjectIds.map(async (projectId) => ({
|
|
194
|
+
projectId,
|
|
195
|
+
result: await application.personal.search({
|
|
196
|
+
space_ids: [personalSpaceId],
|
|
197
|
+
query,
|
|
198
|
+
limit: limitPerProject,
|
|
199
|
+
include_historical: false,
|
|
200
|
+
include_exploratory: true,
|
|
201
|
+
personal_project_ids: [projectId],
|
|
202
|
+
active_personal_project_id: projectId,
|
|
203
|
+
inherit_project_knowledge: false,
|
|
204
|
+
include_personal_global: false
|
|
205
|
+
})
|
|
206
|
+
})));
|
|
207
|
+
const items = childResults.flatMap(({ projectId, result }) => [
|
|
208
|
+
...(result.entities ?? []).map((item) =>
|
|
209
|
+
commonKnowledgeItem(item, 'entity', projectId)
|
|
210
|
+
),
|
|
211
|
+
...(result.facts ?? []).map((item) =>
|
|
212
|
+
commonKnowledgeItem(item, 'relationship', projectId)
|
|
213
|
+
)
|
|
214
|
+
]);
|
|
215
|
+
const candidates = clusterCommonKnowledgeItems(
|
|
216
|
+
items,
|
|
217
|
+
minChildProjects,
|
|
218
|
+
similarityThreshold,
|
|
219
|
+
parentProjectId,
|
|
220
|
+
query
|
|
221
|
+
);
|
|
222
|
+
return commonCandidateResult({
|
|
223
|
+
status: candidates.length > 0 ? 'candidates_found' : 'no_candidates',
|
|
224
|
+
personalSpaceId,
|
|
225
|
+
parentProjectId,
|
|
226
|
+
childProjectIds,
|
|
227
|
+
query,
|
|
228
|
+
candidates,
|
|
229
|
+
graph
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function recordKnowledgeFeedback(application, {
|
|
234
|
+
personalSpaceId,
|
|
235
|
+
taskId,
|
|
236
|
+
sessionId = null,
|
|
237
|
+
toolName = null,
|
|
238
|
+
items
|
|
239
|
+
}) {
|
|
240
|
+
return application.personal.recordKnowledgeFeedback({
|
|
241
|
+
personal_space_id: personalSpaceId,
|
|
242
|
+
task_id: taskId,
|
|
243
|
+
session_id: sessionId,
|
|
244
|
+
tool_name: toolName,
|
|
245
|
+
items: items.map((item) => ({
|
|
246
|
+
item_id: item.itemId,
|
|
247
|
+
item_kind: item.itemKind,
|
|
248
|
+
feedback_kind: item.feedbackKind,
|
|
249
|
+
reason: item.reason,
|
|
250
|
+
evidence_summary: item.evidenceSummary,
|
|
251
|
+
reported_by_kind: item.reportedByKind,
|
|
252
|
+
source_uri: onlineSourceUri(item.sourceUri)
|
|
253
|
+
}))
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function providerCommonKnowledgePromotion(input) {
|
|
258
|
+
return {
|
|
259
|
+
personal_space_id: input.personalSpaceId,
|
|
260
|
+
parent_project_id: input.parentProjectId,
|
|
261
|
+
item_kind: input.itemKind,
|
|
262
|
+
canonical_item_id: input.canonicalItemId,
|
|
263
|
+
duplicate_item_ids: input.duplicateItemIds,
|
|
264
|
+
reason: input.reason,
|
|
265
|
+
human_confirmation_reason: input.humanConfirmationReason
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function agentProjectResolution(resolution) {
|
|
270
|
+
const result = {
|
|
271
|
+
status: resolution.status,
|
|
272
|
+
basis: resolution.basis,
|
|
273
|
+
personal_project_id: resolution.personalProjectId
|
|
274
|
+
};
|
|
275
|
+
if (resolution.candidateCount !== undefined) {
|
|
276
|
+
result.candidate_count = resolution.candidateCount;
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function commonCandidateResult({
|
|
282
|
+
status,
|
|
283
|
+
personalSpaceId,
|
|
284
|
+
parentProjectId,
|
|
285
|
+
childProjectIds,
|
|
286
|
+
query,
|
|
287
|
+
candidates,
|
|
288
|
+
graph
|
|
289
|
+
}) {
|
|
290
|
+
return {
|
|
291
|
+
status,
|
|
292
|
+
personal_space_id: personalSpaceId,
|
|
293
|
+
parent_project_id: parentProjectId,
|
|
294
|
+
child_project_ids: childProjectIds,
|
|
295
|
+
query,
|
|
296
|
+
candidates,
|
|
297
|
+
graph_truncated: graph.truncated === true,
|
|
298
|
+
policy: commonKnowledgeCandidatePolicy()
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function commonKnowledgeItem(item, itemKind, projectId) {
|
|
303
|
+
return {
|
|
304
|
+
...item,
|
|
305
|
+
item_kind: itemKind,
|
|
306
|
+
defined_project_id: projectId
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function clusterCommonKnowledgeItems(
|
|
311
|
+
items,
|
|
312
|
+
minChildProjects,
|
|
313
|
+
similarityThreshold,
|
|
314
|
+
parentProjectId,
|
|
315
|
+
query
|
|
316
|
+
) {
|
|
317
|
+
const groups = [];
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
const matching = groups
|
|
320
|
+
.map((group) => ({
|
|
321
|
+
group,
|
|
322
|
+
similarity: Math.max(
|
|
323
|
+
...group.items.map((member) => commonKnowledgeSimilarity(item, member))
|
|
324
|
+
)
|
|
325
|
+
}))
|
|
326
|
+
.filter(({ group, similarity }) =>
|
|
327
|
+
group.itemKind === item.item_kind
|
|
328
|
+
&& similarity >= similarityThreshold
|
|
329
|
+
&& !group.projectIds.has(item.defined_project_id)
|
|
330
|
+
)
|
|
331
|
+
.sort((left, right) => right.similarity - left.similarity)[0];
|
|
332
|
+
if (matching) {
|
|
333
|
+
matching.group.items.push(item);
|
|
334
|
+
matching.group.projectIds.add(item.defined_project_id);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
groups.push({
|
|
338
|
+
itemKind: item.item_kind,
|
|
339
|
+
projectIds: new Set([item.defined_project_id]),
|
|
340
|
+
items: [item]
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return groups
|
|
345
|
+
.filter(({ projectIds }) => projectIds.size >= minChildProjects)
|
|
346
|
+
.map((group) => {
|
|
347
|
+
const orderedItems = [...group.items].sort((left, right) =>
|
|
348
|
+
left.defined_project_id.localeCompare(right.defined_project_id)
|
|
349
|
+
);
|
|
350
|
+
const pairScores = [];
|
|
351
|
+
for (let left = 0; left < orderedItems.length; left += 1) {
|
|
352
|
+
for (let right = left + 1; right < orderedItems.length; right += 1) {
|
|
353
|
+
pairScores.push(commonKnowledgeSimilarity(
|
|
354
|
+
orderedItems[left],
|
|
355
|
+
orderedItems[right]
|
|
356
|
+
));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const averageSimilarity = pairScores.length === 0
|
|
360
|
+
? 1
|
|
361
|
+
: pairScores.reduce((sum, score) => sum + score, 0) / pairScores.length;
|
|
362
|
+
const itemIds = orderedItems.map(({ id }) => id).sort();
|
|
363
|
+
return {
|
|
364
|
+
candidate_id: `common-${createHash('sha256')
|
|
365
|
+
.update(itemIds.join('\n'))
|
|
366
|
+
.digest('hex')
|
|
367
|
+
.slice(0, 20)}`,
|
|
368
|
+
parent_project_id: parentProjectId,
|
|
369
|
+
child_project_ids: [...group.projectIds].sort(),
|
|
370
|
+
item_kind: group.itemKind,
|
|
371
|
+
query,
|
|
372
|
+
similarity_score: Number(averageSimilarity.toFixed(4)),
|
|
373
|
+
similarity_basis: 'lexical_overlap_across_direct_child_projects',
|
|
374
|
+
items: orderedItems,
|
|
375
|
+
requires_human_confirmation: true,
|
|
376
|
+
promotion_performed: false
|
|
377
|
+
};
|
|
378
|
+
})
|
|
379
|
+
.sort((left, right) =>
|
|
380
|
+
right.child_project_ids.length - left.child_project_ids.length
|
|
381
|
+
|| right.similarity_score - left.similarity_score
|
|
382
|
+
|| left.candidate_id.localeCompare(right.candidate_id)
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function commonKnowledgeSimilarity(left, right) {
|
|
387
|
+
if (left.key && right.key && left.key === right.key) return 1;
|
|
388
|
+
const leftTokens = commonKnowledgeTokens(left);
|
|
389
|
+
const rightTokens = commonKnowledgeTokens(right);
|
|
390
|
+
if (leftTokens.size === 0 || rightTokens.size === 0) return 0;
|
|
391
|
+
let intersection = 0;
|
|
392
|
+
for (const token of leftTokens) {
|
|
393
|
+
if (rightTokens.has(token)) intersection += 1;
|
|
394
|
+
}
|
|
395
|
+
return intersection / new Set([...leftTokens, ...rightTokens]).size;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function commonKnowledgeTokens(item) {
|
|
399
|
+
const text = [
|
|
400
|
+
item.name,
|
|
401
|
+
item.summary,
|
|
402
|
+
item.fact,
|
|
403
|
+
item.type
|
|
404
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
405
|
+
const tokens = new Set(
|
|
406
|
+
text.match(/[a-z0-9]+(?:[._-][a-z0-9]+)*/g) ?? []
|
|
407
|
+
);
|
|
408
|
+
for (const sequence of text.match(/[\u3400-\u9fff]+/g) ?? []) {
|
|
409
|
+
if (sequence.length === 1) tokens.add(sequence);
|
|
410
|
+
for (let index = 0; index < sequence.length - 1; index += 1) {
|
|
411
|
+
tokens.add(sequence.slice(index, index + 2));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return tokens;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function commonKnowledgeCandidatePolicy() {
|
|
418
|
+
return {
|
|
419
|
+
read_only: true,
|
|
420
|
+
direct_part_of_children_only: true,
|
|
421
|
+
inherited_knowledge_excluded: true,
|
|
422
|
+
personal_global_excluded: true,
|
|
423
|
+
automatic_promotion: false,
|
|
424
|
+
human_confirmation_required: true,
|
|
425
|
+
similarity_is_inferred_not_authoritative: true
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function decisionTraceKnowledge(input) {
|
|
430
|
+
const decisionKey = `decision:${input.decisionKey}`;
|
|
431
|
+
const selectedKey = `${decisionKey}:option:${input.selectedOption.key}`;
|
|
432
|
+
const rationaleKey = `${decisionKey}:rationale`;
|
|
433
|
+
const rejected = input.rejectedOptions ?? [];
|
|
434
|
+
const validations = input.validationResults ?? [];
|
|
435
|
+
const confirmed = ['user', 'authoritative_source'].includes(
|
|
436
|
+
input.decidedBy.kind
|
|
437
|
+
);
|
|
438
|
+
const confirmationStatus = confirmed ? 'confirmed' : 'pending';
|
|
439
|
+
const epistemicStatus = confirmed ? 'confirmed' : 'observed';
|
|
440
|
+
const confirmationBasis = {
|
|
441
|
+
existenceReason: input.sourceDescription,
|
|
442
|
+
quadrantReason: input.reason,
|
|
443
|
+
proposedBy: input.decidedBy,
|
|
444
|
+
confirmedBy: confirmed ? input.decidedBy : null,
|
|
445
|
+
confirmedAt: confirmed ? input.referenceTime : null,
|
|
446
|
+
agentPolicyVersion: null
|
|
447
|
+
};
|
|
448
|
+
const epistemic = {
|
|
449
|
+
originQuadrant: 'known_known',
|
|
450
|
+
currentQuadrant: 'known_known',
|
|
451
|
+
epistemicStatus,
|
|
452
|
+
confirmationStatus,
|
|
453
|
+
confirmationBasis,
|
|
454
|
+
reasoningSummary: input.reason,
|
|
455
|
+
inheritanceMode: 'local_only',
|
|
456
|
+
inheritedProjectIds: []
|
|
457
|
+
};
|
|
458
|
+
const entities = [
|
|
459
|
+
{
|
|
460
|
+
key: decisionKey,
|
|
461
|
+
name: input.title,
|
|
462
|
+
type: 'Decision',
|
|
463
|
+
summary: `${input.question}\nSelected: ${input.selectedOption.label}`,
|
|
464
|
+
attributes: {
|
|
465
|
+
decisionKey: input.decisionKey,
|
|
466
|
+
selectedOptionKey: input.selectedOption.key,
|
|
467
|
+
decidedByKind: input.decidedBy.kind
|
|
468
|
+
},
|
|
469
|
+
...epistemic
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
key: selectedKey,
|
|
473
|
+
name: input.selectedOption.label,
|
|
474
|
+
type: 'DecisionOption',
|
|
475
|
+
summary: input.selectedOption.summary ?? '',
|
|
476
|
+
attributes: { disposition: 'selected' },
|
|
477
|
+
...epistemic
|
|
478
|
+
},
|
|
479
|
+
...rejected.map((option) => ({
|
|
480
|
+
key: `${decisionKey}:option:${option.key}`,
|
|
481
|
+
name: option.label,
|
|
482
|
+
type: 'DecisionOption',
|
|
483
|
+
summary: option.summary ?? '',
|
|
484
|
+
attributes: { disposition: 'rejected' },
|
|
485
|
+
...epistemic
|
|
486
|
+
})),
|
|
487
|
+
{
|
|
488
|
+
key: rationaleKey,
|
|
489
|
+
name: `${input.title} · rationale`,
|
|
490
|
+
type: 'DecisionRationale',
|
|
491
|
+
summary: input.reason,
|
|
492
|
+
attributes: {},
|
|
493
|
+
...epistemic
|
|
494
|
+
},
|
|
495
|
+
...validations.map((validation) => ({
|
|
496
|
+
key: `${decisionKey}:validation:${validation.key}`,
|
|
497
|
+
name: `${input.title} · ${validation.key}`,
|
|
498
|
+
type: 'ValidationResult',
|
|
499
|
+
summary: validation.summary,
|
|
500
|
+
attributes: { outcome: validation.outcome },
|
|
501
|
+
...epistemic
|
|
502
|
+
}))
|
|
503
|
+
];
|
|
504
|
+
const relationship = (key, target, type, fact) => ({
|
|
505
|
+
key,
|
|
506
|
+
source: decisionKey,
|
|
507
|
+
target,
|
|
508
|
+
type,
|
|
509
|
+
fact,
|
|
510
|
+
confidence: confirmed ? 1 : 0.7,
|
|
511
|
+
attributes: {},
|
|
512
|
+
...epistemic
|
|
513
|
+
});
|
|
514
|
+
return {
|
|
515
|
+
entities,
|
|
516
|
+
relationships: [
|
|
517
|
+
relationship(
|
|
518
|
+
`${decisionKey}:selected`,
|
|
519
|
+
selectedKey,
|
|
520
|
+
'SELECTED_OPTION',
|
|
521
|
+
`${input.title} selected ${input.selectedOption.label}.`
|
|
522
|
+
),
|
|
523
|
+
...rejected.map((option) => relationship(
|
|
524
|
+
`${decisionKey}:rejected:${option.key}`,
|
|
525
|
+
`${decisionKey}:option:${option.key}`,
|
|
526
|
+
'REJECTED_OPTION',
|
|
527
|
+
`${input.title} did not select ${option.label}.`
|
|
528
|
+
)),
|
|
529
|
+
relationship(
|
|
530
|
+
`${decisionKey}:motivated-by`,
|
|
531
|
+
rationaleKey,
|
|
532
|
+
'MOTIVATED_BY',
|
|
533
|
+
input.reason
|
|
534
|
+
),
|
|
535
|
+
...validations.map((validation) => relationship(
|
|
536
|
+
`${decisionKey}:validated-by:${validation.key}`,
|
|
537
|
+
`${decisionKey}:validation:${validation.key}`,
|
|
538
|
+
'VALIDATED_BY',
|
|
539
|
+
`${validation.outcome}: ${validation.summary}`
|
|
540
|
+
))
|
|
541
|
+
]
|
|
542
|
+
};
|
|
543
|
+
}
|