fuli-context 0.7.3 → 0.7.4-beta.2
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 +170 -18
- package/README.zh-CN.md +146 -12
- package/compose.graphiti.yml +2 -0
- package/dist/web/assets/{AboutPage-qeO_vm0T.js → AboutPage-CftRexSY.js} +1 -1
- package/dist/web/assets/{ConnectionsPage-DO0GR9Xg.js → ConnectionsPage-D5J91x6d.js} +1 -1
- package/dist/web/assets/{useMinimumLoadingDisplay-BpCyBeko.css → KnowledgeEditDialog-DWUV_NPp.css} +1 -1
- package/dist/web/assets/KnowledgeEditDialog-wotVahvr.js +1 -0
- package/dist/web/assets/{KnowledgeInspector-Bjhdu2mf.css → KnowledgeInspector--1NujtXW.css} +1 -1
- package/dist/web/assets/KnowledgeInspector-BzkW0mVs.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BebUDiHt.js +1 -0
- package/dist/web/assets/KnowledgePage-OUT07aKH.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-1pHJ0MNZ.js +4 -0
- package/dist/web/assets/{OverviewPage-CmCcDCAs.js → OverviewPage-CHE6XhdS.js} +1 -1
- package/dist/web/assets/PersonalProfilePage-B7T09bow.css +1 -0
- package/dist/web/assets/PersonalProfilePage-CXXiabUy.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-d2wYGW8n.js +1 -0
- package/dist/web/assets/PublicProjectsPage-hxLShtnZ.js +1 -0
- package/dist/web/assets/{ReviewPage-B3vnZ6gn.js → ReviewPage-DUeyu7E8.js} +1 -1
- package/dist/web/assets/{SearchableSelect-ih69qbcS.js → SearchableSelect-C5RiB5F8.js} +1 -1
- package/dist/web/assets/SettingsPage-BMuASSzQ.js +1 -0
- package/dist/web/assets/SettingsPage-CAfz4qiV.css +1 -0
- package/dist/web/assets/WritingTastePage-B9BCYIwQ.js +1 -0
- package/dist/web/assets/WritingTastePage-DSgvTH6x.css +1 -0
- package/dist/web/assets/{console-DE40hXGC.js → console-DMvHxKf9.js} +1 -1
- package/dist/web/assets/conversation-launcher-settings-Dnlcw7Rj.js +1 -0
- package/dist/web/assets/{i18n-CDxBs1sz.js → i18n-CWfd8Dtt.js} +1 -1
- package/dist/web/assets/{identity-BCxb1mcy.js → identity-C9mLdR8L.js} +1 -1
- package/dist/web/assets/{index-CCOGfZzE.css → index-X2j29j8i.css} +1 -1
- package/dist/web/assets/index-nP5p5OaY.js +2 -0
- package/dist/web/assets/{model-CCMdt10Q.js → model-CsYAR8O3.js} +1 -1
- package/dist/web/assets/paths-CDREouhi.js +1 -0
- package/dist/web/assets/useApi-CROJJdhE-BLQiPVw0.js +1 -0
- package/dist/web/assets/vue-router-DN8x6iY1.js +1 -0
- package/dist/web/index.html +7 -6
- package/graph-provider/fuli_graph/app.py +238 -2
- package/graph-provider/fuli_graph/auth.py +18 -0
- package/graph-provider/fuli_graph/collaboration_context.py +253 -17
- package/graph-provider/fuli_graph/common_knowledge.py +6 -2
- package/graph-provider/fuli_graph/config.py +5 -0
- package/graph-provider/fuli_graph/graph_models.py +6 -0
- package/graph-provider/fuli_graph/graph_projection.py +14 -0
- package/graph-provider/fuli_graph/graph_query.py +27 -3
- package/graph-provider/fuli_graph/knowledge_management.py +19 -1
- package/graph-provider/fuli_graph/knowledge_search.py +11 -0
- package/graph-provider/fuli_graph/models.py +15 -2
- package/graph-provider/fuli_graph/personal_global_preference_models.py +234 -0
- package/graph-provider/fuli_graph/personal_global_preferences.py +1247 -0
- package/graph-provider/fuli_graph/project_action_models.py +22 -0
- package/graph-provider/fuli_graph/project_knowledge.py +121 -2
- package/graph-provider/fuli_graph/provider_values.py +24 -0
- package/graph-provider/fuli_graph/runtime.py +78 -0
- package/graph-provider/fuli_graph/search_projection.py +14 -1
- package/graph-provider/fuli_graph/store_knowledge.py +116 -2
- package/graph-provider/fuli_graph/workflow_candidate_models.py +168 -0
- package/graph-provider/fuli_graph/workflow_candidates.py +996 -0
- package/graph-provider/fuli_graph/workflow_observation_models.py +30 -0
- package/graph-provider/fuli_graph/workflow_observations.py +84 -0
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +21 -21
- package/package.json +4 -4
- package/skills/capturing-session-knowledge/SKILL.md +32 -5
- package/skills/flreview/SKILL.md +4 -3
- package/src/agent-tools/graph-definitions.js +136 -20
- package/src/agent-tools/graph-handlers.js +10 -4
- package/src/cli/command-registry.js +1 -1
- package/src/cli/local-runtime-command.js +69 -2
- package/src/graphiti/agent-knowledge-workflows.js +497 -16
- package/src/graphiti/collaboration-preference-projection.js +162 -0
- package/src/graphiti/federated-application.js +155 -161
- package/src/graphiti/federated-search-support.js +102 -0
- package/src/graphiti/knowledge-provider-mapping.js +15 -0
- package/src/graphiti/provider-client.js +162 -10
- package/src/graphiti/related-project-suggestions.js +109 -0
- package/src/graphiti/runtime-config.js +14 -0
- package/src/graphiti/source-marker.js +2 -2
- package/src/graphiti/task-knowledge-recall.js +66 -15
- package/src/graphiti/user-taste-skill.js +241 -0
- package/src/graphiti/workflow-candidate-mapping.js +87 -0
- package/src/graphiti/workflow-observation.js +82 -0
- package/src/graphiti/writing-taste-profile-workflow.js +25 -0
- package/src/graphiti/writing-taste-profile.js +490 -0
- package/src/http/graph-api-router.js +15 -2
- package/src/mcp/context-resources.js +158 -0
- package/src/mcp/create-mcp-server.js +39 -5
- package/src/mcp/instructions.js +5 -7
- package/src/mcp/session-id.js +60 -0
- package/src/mcp/task-context-registry.js +15 -3
- package/src/mcp/tool-annotations.js +8 -1
- package/src/mcp/tool-result.js +28 -4
- package/src/server.js +0 -1
- package/src/setup/claude-code-config.js +17 -6
- package/src/setup/graph-runtime.js +20 -2
- package/src/system/runtime-settings.js +121 -2
- package/src/system/system-service.js +5 -1
- package/dist/web/assets/KnowledgeInspector-DEW5lA1m.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-BkM568Ld.js +0 -1
- package/dist/web/assets/KnowledgePage-BAT4aZIm.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-DyGtBz--.js +0 -4
- package/dist/web/assets/PersonalProfilePage-C42mwKN8.js +0 -1
- package/dist/web/assets/PersonalProfilePage-CdmEzyvZ.css +0 -1
- package/dist/web/assets/PersonalProjectsPage-BwaXR62K.js +0 -1
- package/dist/web/assets/PublicProjectsPage-egKBulOq.js +0 -1
- package/dist/web/assets/SettingsPage-C6ouU0sR.css +0 -1
- package/dist/web/assets/SettingsPage-oLE8mNs5.js +0 -1
- package/dist/web/assets/index-BzhNfzU_.js +0 -2
- package/dist/web/assets/paths-DwHeiGMT.js +0 -1
- package/dist/web/assets/useMinimumLoadingDisplay-HdI7XAmu.js +0 -1
- package/dist/web/assets/vue-router-Drfl1F8i.js +0 -1
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
startLocalRuntime,
|
|
6
6
|
stopLocalRuntime
|
|
7
7
|
} from '../local-runtime/lifecycle.js';
|
|
8
|
+
import { discoverAgents } from '../setup/agents.js';
|
|
9
|
+
import { inspectAgentInstallations } from '../setup/agent-installation-status.js';
|
|
8
10
|
import { resolveSetupPaths } from '../setup/paths.js';
|
|
9
11
|
import {
|
|
10
12
|
DEFAULT_RUNTIME_SETTINGS,
|
|
@@ -43,15 +45,28 @@ export async function runLocalRuntimeCommand(command, args, dependencies = {}) {
|
|
|
43
45
|
status: dependencies.inspect ?? inspectLocalRuntime,
|
|
44
46
|
open: dependencies.openConsole ?? openLocalConsole
|
|
45
47
|
};
|
|
48
|
+
const agentSetupCheck = command === 'start'
|
|
49
|
+
? checkAgentSetup({
|
|
50
|
+
paths,
|
|
51
|
+
env,
|
|
52
|
+
nodePath: dependencies.nodePath ?? process.execPath,
|
|
53
|
+
discover: dependencies.discoverAgents ?? discoverAgents,
|
|
54
|
+
inspect: dependencies.inspectAgentInstallations ?? inspectAgentInstallations
|
|
55
|
+
})
|
|
56
|
+
: null;
|
|
46
57
|
const result = await handlers[command](input, dependencies.lifecycleDependencies);
|
|
58
|
+
const outputResult = agentSetupCheck
|
|
59
|
+
? { ...result, agentSetupCheck }
|
|
60
|
+
: result;
|
|
47
61
|
|
|
48
62
|
if (command === 'status' && options.json) {
|
|
49
|
-
write(JSON.stringify(
|
|
63
|
+
write(JSON.stringify(outputResult, null, 2));
|
|
50
64
|
} else {
|
|
51
|
-
write(formatLocalRuntimeResult(command,
|
|
65
|
+
write(formatLocalRuntimeResult(command, outputResult));
|
|
52
66
|
}
|
|
53
67
|
return {
|
|
54
68
|
...result,
|
|
69
|
+
...(agentSetupCheck ? { agentSetupCheck } : {}),
|
|
55
70
|
exitCode: command === 'status' && result.status !== 'running'
|
|
56
71
|
? 1
|
|
57
72
|
: result.status === 'partial' ? 1 : 0
|
|
@@ -86,6 +101,8 @@ export function formatLocalRuntimeResult(command, result) {
|
|
|
86
101
|
|
|
87
102
|
function formatStartedRuntime(title, result) {
|
|
88
103
|
const lines = [title, `Management UI: ${result.url}`];
|
|
104
|
+
const agentSetupNotice = formatAgentSetupNotice(result.agentSetupCheck);
|
|
105
|
+
if (agentSetupNotice) lines.push(agentSetupNotice);
|
|
89
106
|
if (result.lan === true) {
|
|
90
107
|
lines.push(
|
|
91
108
|
'LAN URLs:',
|
|
@@ -98,6 +115,56 @@ function formatStartedRuntime(title, result) {
|
|
|
98
115
|
return lines.join('\n');
|
|
99
116
|
}
|
|
100
117
|
|
|
118
|
+
function checkAgentSetup({ paths, env, nodePath, discover, inspect }) {
|
|
119
|
+
try {
|
|
120
|
+
const agents = discover({ env }).filter(({ available }) => available);
|
|
121
|
+
if (!agents.length) return { status: 'checked', agents: [] };
|
|
122
|
+
const inspected = inspect(agents, {
|
|
123
|
+
nodePath,
|
|
124
|
+
mcpServerPath: paths.mcpServerPath,
|
|
125
|
+
runtimeConfigPath: paths.graphRuntimeConfigPath,
|
|
126
|
+
sessionSkillPath: paths.sessionSkillPath,
|
|
127
|
+
projectSkillPath: paths.projectSkillPath,
|
|
128
|
+
reviewSkillPath: paths.reviewSkillPath
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
status: 'checked',
|
|
132
|
+
agents: inspected.filter(({ integrationStatus }) => integrationStatus !== 'connected')
|
|
133
|
+
};
|
|
134
|
+
} catch {
|
|
135
|
+
return { status: 'unavailable', agents: [] };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function formatAgentSetupNotice(check) {
|
|
140
|
+
if (!check) return null;
|
|
141
|
+
if (check.status === 'unavailable') {
|
|
142
|
+
return [
|
|
143
|
+
'Could not verify Agent integrations.',
|
|
144
|
+
'Run `fuli setup` to verify and update them.'
|
|
145
|
+
].join('\n');
|
|
146
|
+
}
|
|
147
|
+
if (!check.agents.length) return null;
|
|
148
|
+
return [
|
|
149
|
+
'Agent setup required:',
|
|
150
|
+
...check.agents.map((agent) => ` ${agent.label}: ${agentNeedsSetup(agent)}`),
|
|
151
|
+
'Run `fuli setup` to install or update them.'
|
|
152
|
+
].join('\n');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function agentNeedsSetup(agent) {
|
|
156
|
+
const detailLabels = {
|
|
157
|
+
mcp: 'MCP',
|
|
158
|
+
skills: 'Skills',
|
|
159
|
+
bootstrap: 'Bootstrap',
|
|
160
|
+
lifecycleHooks: 'Lifecycle hooks'
|
|
161
|
+
};
|
|
162
|
+
const outdated = Object.entries(agent.integrationDetails ?? {})
|
|
163
|
+
.filter(([, status]) => status !== 'current')
|
|
164
|
+
.map(([key, status]) => `${detailLabels[key] ?? key}: ${status}`);
|
|
165
|
+
return outdated.join(', ') || 'integration update available';
|
|
166
|
+
}
|
|
167
|
+
|
|
101
168
|
function formatStatus(result) {
|
|
102
169
|
const labels = {
|
|
103
170
|
running: 'running',
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
|
|
3
3
|
import { onlineSourceUri } from './source-uri.js';
|
|
4
|
+
import {
|
|
5
|
+
relatedProjectGuidance,
|
|
6
|
+
relatedProjectSuggestions
|
|
7
|
+
} from './related-project-suggestions.js';
|
|
4
8
|
|
|
5
9
|
export async function beginTaskContext(application, {
|
|
6
10
|
sessionId,
|
|
@@ -116,18 +120,21 @@ export async function searchCurrentProjectKnowledge(
|
|
|
116
120
|
};
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
const results = await Promise.all(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
const [results, relatedProjects] = await Promise.all([
|
|
124
|
+
Promise.all(queries.map((query) =>
|
|
125
|
+
application.searchKnowledge({
|
|
126
|
+
personalSpaceId: application.config.personal.spaceId,
|
|
127
|
+
personalProjectId: resolution.personalProjectId,
|
|
128
|
+
query,
|
|
129
|
+
limit: limitPerQuery,
|
|
130
|
+
includeHistorical,
|
|
131
|
+
includePending,
|
|
132
|
+
agentInvocation: true,
|
|
133
|
+
agentToolName: 'search_current_project_knowledge'
|
|
134
|
+
})
|
|
135
|
+
)),
|
|
136
|
+
loadRelatedProjectSuggestions(application, resolution.personalProjectId)
|
|
137
|
+
]);
|
|
131
138
|
return {
|
|
132
139
|
status: 'searched',
|
|
133
140
|
personal_space_id: application.config.personal.spaceId,
|
|
@@ -138,12 +145,32 @@ export async function searchCurrentProjectKnowledge(
|
|
|
138
145
|
inherited_relation_types: ['PART_OF', 'USES_KNOWLEDGE_FROM'],
|
|
139
146
|
max_inheritance_hops: 2,
|
|
140
147
|
local_same_key_overrides_parent: true,
|
|
141
|
-
unrelated_relations_expand_scope: false
|
|
148
|
+
unrelated_relations_expand_scope: false,
|
|
149
|
+
related_project_expansion_requires_confirmation: true
|
|
142
150
|
},
|
|
143
|
-
results
|
|
151
|
+
results,
|
|
152
|
+
related_project_suggestions: relatedProjects.suggestions,
|
|
153
|
+
related_project_suggestions_status: relatedProjects.status,
|
|
154
|
+
related_project_guidance: relatedProjectGuidance(relatedProjects.suggestions)
|
|
144
155
|
};
|
|
145
156
|
}
|
|
146
157
|
|
|
158
|
+
async function loadRelatedProjectSuggestions(application, personalProjectId) {
|
|
159
|
+
try {
|
|
160
|
+
const graph = await application.personal.graph(
|
|
161
|
+
application.config.personal.spaceId,
|
|
162
|
+
2000,
|
|
163
|
+
personalProjectId
|
|
164
|
+
);
|
|
165
|
+
return {
|
|
166
|
+
status: graph?.truncated ? 'partial' : 'available',
|
|
167
|
+
suggestions: relatedProjectSuggestions(graph, personalProjectId)
|
|
168
|
+
};
|
|
169
|
+
} catch {
|
|
170
|
+
return { status: 'unavailable', suggestions: [] };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
147
174
|
export async function discoverCommonKnowledgeCandidates(application, {
|
|
148
175
|
personalSpaceId,
|
|
149
176
|
parentProjectId,
|
|
@@ -175,8 +202,13 @@ export async function discoverCommonKnowledgeCandidates(application, {
|
|
|
175
202
|
}
|
|
176
203
|
const childProjectIds = [...new Set(
|
|
177
204
|
(graph.edges ?? [])
|
|
178
|
-
.filter(({ type, source, target }) =>
|
|
205
|
+
.filter(({ type, source, target, attributes = {} }) =>
|
|
179
206
|
type === 'PART_OF'
|
|
207
|
+
&& attributes.status === 'active'
|
|
208
|
+
&& (
|
|
209
|
+
attributes.confirmationAuthority
|
|
210
|
+
?? attributes.confirmation_authority
|
|
211
|
+
) === 'human_review'
|
|
180
212
|
&& projectIdByNodeId.get(target) === parentProjectId
|
|
181
213
|
&& projectIdByNodeId.has(source)
|
|
182
214
|
)
|
|
@@ -216,7 +248,7 @@ export async function discoverCommonKnowledgeCandidates(application, {
|
|
|
216
248
|
...(result.facts ?? []).map((item) =>
|
|
217
249
|
commonKnowledgeItem(item, 'relationship', projectId)
|
|
218
250
|
)
|
|
219
|
-
]);
|
|
251
|
+
]).filter((item) => !item.profile_aspect);
|
|
220
252
|
const candidates = clusterCommonKnowledgeItems(
|
|
221
253
|
items,
|
|
222
254
|
minChildProjects,
|
|
@@ -235,6 +267,149 @@ export async function discoverCommonKnowledgeCandidates(application, {
|
|
|
235
267
|
});
|
|
236
268
|
}
|
|
237
269
|
|
|
270
|
+
export async function discoverPersonalGlobalPreferenceCandidates(application, {
|
|
271
|
+
personalSpaceId,
|
|
272
|
+
personalProjectIds,
|
|
273
|
+
query,
|
|
274
|
+
minProjects = 2,
|
|
275
|
+
similarityThreshold = 0.72,
|
|
276
|
+
limitPerProject = 12
|
|
277
|
+
}) {
|
|
278
|
+
if (!Array.isArray(personalProjectIds) || personalProjectIds.length < 2) {
|
|
279
|
+
throw new TypeError('Personal-global candidates need at least two explicit projects');
|
|
280
|
+
}
|
|
281
|
+
if (!Number.isInteger(minProjects) || minProjects < 2) {
|
|
282
|
+
throw new TypeError('Personal-global candidates need evidence from at least two projects');
|
|
283
|
+
}
|
|
284
|
+
if (
|
|
285
|
+
typeof similarityThreshold !== 'number'
|
|
286
|
+
|| similarityThreshold < 0
|
|
287
|
+
|| similarityThreshold > 1
|
|
288
|
+
) {
|
|
289
|
+
throw new TypeError('Similarity threshold must be between 0 and 1');
|
|
290
|
+
}
|
|
291
|
+
const projectIds = [...new Set(personalProjectIds)].sort();
|
|
292
|
+
if (projectIds.length < 2 || minProjects > projectIds.length) {
|
|
293
|
+
throw new TypeError('The selected project set cannot satisfy minProjects');
|
|
294
|
+
}
|
|
295
|
+
const knownProjectIds = new Set(
|
|
296
|
+
(await application.personal.listPersonalProjects(personalSpaceId))
|
|
297
|
+
.map(({ project_id: projectId }) => projectId)
|
|
298
|
+
);
|
|
299
|
+
if (projectIds.some((projectId) => !knownProjectIds.has(projectId))) {
|
|
300
|
+
throw new TypeError('Every selected project must exist in the active personal space');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const projectResults = await Promise.all(projectIds.map(async (projectId) => ({
|
|
304
|
+
projectId,
|
|
305
|
+
result: await application.personal.search({
|
|
306
|
+
space_ids: [personalSpaceId],
|
|
307
|
+
query,
|
|
308
|
+
limit: limitPerProject,
|
|
309
|
+
include_historical: false,
|
|
310
|
+
include_exploratory: false,
|
|
311
|
+
personal_project_ids: [projectId],
|
|
312
|
+
active_personal_project_id: projectId,
|
|
313
|
+
inherit_project_knowledge: false,
|
|
314
|
+
include_personal_global: false
|
|
315
|
+
})
|
|
316
|
+
})));
|
|
317
|
+
const items = projectResults.flatMap(({ projectId, result }) => [
|
|
318
|
+
...(result.entities ?? []).map((item) =>
|
|
319
|
+
commonKnowledgeItem(item, 'entity', projectId)
|
|
320
|
+
),
|
|
321
|
+
...(result.facts ?? []).map((item) =>
|
|
322
|
+
commonKnowledgeItem(item, 'relationship', projectId)
|
|
323
|
+
)
|
|
324
|
+
]).filter((item) =>
|
|
325
|
+
item.profile_aspect
|
|
326
|
+
&& item.preference_scope === 'project'
|
|
327
|
+
&& item.preference_project_id === item.defined_project_id
|
|
328
|
+
&& ['confirmed', 'agent_confirmed'].includes(item.confirmation_status)
|
|
329
|
+
&& item.requires_attention !== true
|
|
330
|
+
);
|
|
331
|
+
const clusteredCandidates = clusterPersonalGlobalPreferenceItems(
|
|
332
|
+
items,
|
|
333
|
+
minProjects,
|
|
334
|
+
similarityThreshold,
|
|
335
|
+
query,
|
|
336
|
+
projectIds.length
|
|
337
|
+
);
|
|
338
|
+
const candidates = await Promise.all(clusteredCandidates.map(async (candidate) => {
|
|
339
|
+
const options = await application.personal.personalGlobalPreferenceScopeOptions(
|
|
340
|
+
candidate.candidate_id,
|
|
341
|
+
{
|
|
342
|
+
personal_space_id: personalSpaceId,
|
|
343
|
+
source_items: candidate.source_items.map((item) => ({
|
|
344
|
+
item_id: item.id,
|
|
345
|
+
item_kind: item.item_kind,
|
|
346
|
+
project_id: item.defined_project_id
|
|
347
|
+
})),
|
|
348
|
+
preference_key: candidate.preference_key
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
return {
|
|
352
|
+
...candidate,
|
|
353
|
+
candidate_version: options.candidate_version,
|
|
354
|
+
target_scope: 'human_selected',
|
|
355
|
+
eligible_target_scopes: options.eligible_target_scopes,
|
|
356
|
+
source_snapshots: options.source_snapshots
|
|
357
|
+
};
|
|
358
|
+
}));
|
|
359
|
+
const decisionState = await personalGlobalCandidateDecisionState(
|
|
360
|
+
application,
|
|
361
|
+
personalSpaceId,
|
|
362
|
+
candidates
|
|
363
|
+
);
|
|
364
|
+
const visibleCandidates = candidates
|
|
365
|
+
.filter(({ candidate_id: id, candidate_version: version }) =>
|
|
366
|
+
!decisionState.decisions.has(`${id}:${version}`)
|
|
367
|
+
)
|
|
368
|
+
.map((candidate) => personalGlobalCandidateWithDecisionState(
|
|
369
|
+
candidate,
|
|
370
|
+
decisionState.revisions.get(candidate.candidate_id)
|
|
371
|
+
));
|
|
372
|
+
const suppressedCandidates = candidates
|
|
373
|
+
.filter(({ candidate_id: id, candidate_version: version }) =>
|
|
374
|
+
decisionState.decisions.has(`${id}:${version}`)
|
|
375
|
+
)
|
|
376
|
+
.map(({ candidate_id: candidateId, candidate_version: version }) => ({
|
|
377
|
+
candidate_id: candidateId,
|
|
378
|
+
candidate_version: version,
|
|
379
|
+
...decisionState.decisions.get(`${candidateId}:${version}`)
|
|
380
|
+
}));
|
|
381
|
+
return {
|
|
382
|
+
status: visibleCandidates.length > 0
|
|
383
|
+
? 'candidates_found'
|
|
384
|
+
: suppressedCandidates.length > 0
|
|
385
|
+
? 'candidates_suppressed'
|
|
386
|
+
: 'no_candidates',
|
|
387
|
+
personal_space_id: personalSpaceId,
|
|
388
|
+
target_scope: 'human_selected',
|
|
389
|
+
selected_project_ids: projectIds,
|
|
390
|
+
query,
|
|
391
|
+
candidates: visibleCandidates,
|
|
392
|
+
suppressed_candidates: suppressedCandidates,
|
|
393
|
+
policy: personalGlobalCandidatePolicy()
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function previewPersonalGlobalPreferenceDecision(application, input) {
|
|
398
|
+
return application.personal.inspectPersonalGlobalPreferenceDecision(
|
|
399
|
+
input.candidateId,
|
|
400
|
+
providerPersonalGlobalPreferenceDecision(input)
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export async function applyPersonalGlobalPreferenceDecision(application, input) {
|
|
405
|
+
return application.personal.applyPersonalGlobalPreferenceDecision(
|
|
406
|
+
input.candidateId,
|
|
407
|
+
providerPersonalGlobalPreferenceDecision(input, {
|
|
408
|
+
approvalToken: input.previewToken
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
238
413
|
export async function recordKnowledgeFeedback(application, {
|
|
239
414
|
personalSpaceId,
|
|
240
415
|
taskId,
|
|
@@ -388,6 +563,188 @@ function clusterCommonKnowledgeItems(
|
|
|
388
563
|
);
|
|
389
564
|
}
|
|
390
565
|
|
|
566
|
+
function clusterPersonalGlobalPreferenceItems(
|
|
567
|
+
items,
|
|
568
|
+
minProjects,
|
|
569
|
+
similarityThreshold,
|
|
570
|
+
query,
|
|
571
|
+
selectedProjectCount
|
|
572
|
+
) {
|
|
573
|
+
const groups = [];
|
|
574
|
+
for (const item of items) {
|
|
575
|
+
const preferenceKey = personalGlobalItemPreferenceKey(item);
|
|
576
|
+
const matching = groups
|
|
577
|
+
.map((group) => ({
|
|
578
|
+
group,
|
|
579
|
+
similarity: Math.min(
|
|
580
|
+
...group.items.map((member) => commonKnowledgeSimilarity(item, member))
|
|
581
|
+
)
|
|
582
|
+
}))
|
|
583
|
+
.filter(({ group, similarity }) =>
|
|
584
|
+
group.itemKind === item.item_kind
|
|
585
|
+
&& group.preferenceKey === preferenceKey
|
|
586
|
+
&& similarity >= similarityThreshold
|
|
587
|
+
&& !group.projectIds.has(item.defined_project_id)
|
|
588
|
+
)
|
|
589
|
+
.sort((left, right) => right.similarity - left.similarity)[0];
|
|
590
|
+
if (matching) {
|
|
591
|
+
matching.group.items.push(item);
|
|
592
|
+
matching.group.projectIds.add(item.defined_project_id);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
groups.push({
|
|
596
|
+
itemKind: item.item_kind,
|
|
597
|
+
preferenceKey,
|
|
598
|
+
projectIds: new Set([item.defined_project_id]),
|
|
599
|
+
items: [item]
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return groups
|
|
604
|
+
.filter(({ projectIds }) => projectIds.size >= minProjects)
|
|
605
|
+
.map((group) => {
|
|
606
|
+
const sourceItems = [...group.items].sort((left, right) =>
|
|
607
|
+
left.defined_project_id.localeCompare(right.defined_project_id)
|
|
608
|
+
);
|
|
609
|
+
const itemIds = sourceItems.map(({ id }) => id).sort();
|
|
610
|
+
const commonTerms = sharedPreferenceTerms(sourceItems);
|
|
611
|
+
const pairScores = [];
|
|
612
|
+
for (let left = 0; left < sourceItems.length; left += 1) {
|
|
613
|
+
for (let right = left + 1; right < sourceItems.length; right += 1) {
|
|
614
|
+
pairScores.push(commonKnowledgeSimilarity(
|
|
615
|
+
sourceItems[left],
|
|
616
|
+
sourceItems[right]
|
|
617
|
+
));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const averageSimilarity = pairScores.length === 0
|
|
621
|
+
? 1
|
|
622
|
+
: pairScores.reduce((sum, score) => sum + score, 0) / pairScores.length;
|
|
623
|
+
const ranking = personalGlobalCandidateRanking({
|
|
624
|
+
sourceItems,
|
|
625
|
+
projectCount: group.projectIds.size,
|
|
626
|
+
selectedProjectCount,
|
|
627
|
+
lexicalSimilarity: averageSimilarity
|
|
628
|
+
});
|
|
629
|
+
return {
|
|
630
|
+
candidate_id: personalGlobalPreferenceCandidateId(itemIds),
|
|
631
|
+
candidate_version: null,
|
|
632
|
+
target_scope: 'human_selected',
|
|
633
|
+
source_project_ids: [...group.projectIds].sort(),
|
|
634
|
+
item_kind: group.itemKind,
|
|
635
|
+
preference_key: group.preferenceKey,
|
|
636
|
+
query,
|
|
637
|
+
similarity_score: Number(averageSimilarity.toFixed(4)),
|
|
638
|
+
similarity_basis: 'lexical_overlap_across_explicit_personal_projects',
|
|
639
|
+
candidate_score: ranking.score,
|
|
640
|
+
ranking,
|
|
641
|
+
source_items: sourceItems,
|
|
642
|
+
derived_common_core: {
|
|
643
|
+
terms: commonTerms,
|
|
644
|
+
basis: 'lexical_intersection_of_preserved_source_text',
|
|
645
|
+
authoritative: false,
|
|
646
|
+
source_item_ids: itemIds
|
|
647
|
+
},
|
|
648
|
+
source_specific_terms: sourceItems.map((item) => ({
|
|
649
|
+
item_id: item.id,
|
|
650
|
+
project_id: item.defined_project_id,
|
|
651
|
+
terms: [...preferenceContentTokens(item)]
|
|
652
|
+
.filter((term) => !commonTerms.includes(term))
|
|
653
|
+
.sort()
|
|
654
|
+
})),
|
|
655
|
+
requires_human_scope_judgment: true,
|
|
656
|
+
scope_apply_performed: false
|
|
657
|
+
};
|
|
658
|
+
})
|
|
659
|
+
.sort((left, right) =>
|
|
660
|
+
right.candidate_score - left.candidate_score
|
|
661
|
+
|| right.source_project_ids.length - left.source_project_ids.length
|
|
662
|
+
|| right.similarity_score - left.similarity_score
|
|
663
|
+
|| left.candidate_id.localeCompare(right.candidate_id)
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function personalGlobalItemPreferenceKey(item) {
|
|
668
|
+
return item.preference_key
|
|
669
|
+
?? item.attributes?.preferenceKey
|
|
670
|
+
?? item.attributes?.preference_key
|
|
671
|
+
?? item.key
|
|
672
|
+
?? item.id;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const PERSONAL_GLOBAL_RANKING_WEIGHTS = Object.freeze({
|
|
676
|
+
distinct_projects: 0.25,
|
|
677
|
+
lexical_similarity: 0.35,
|
|
678
|
+
confirmation_authority: 0.2,
|
|
679
|
+
recency: 0.1,
|
|
680
|
+
negative_evidence: 0.1
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
function personalGlobalCandidateRanking({
|
|
684
|
+
sourceItems,
|
|
685
|
+
projectCount,
|
|
686
|
+
selectedProjectCount,
|
|
687
|
+
lexicalSimilarity
|
|
688
|
+
}) {
|
|
689
|
+
const signals = {
|
|
690
|
+
distinct_projects: boundedScore(
|
|
691
|
+
projectCount / Math.max(selectedProjectCount, 1)
|
|
692
|
+
),
|
|
693
|
+
lexical_similarity: boundedScore(lexicalSimilarity),
|
|
694
|
+
confirmation_authority: averageScore(sourceItems.map((item) =>
|
|
695
|
+
item.confirmation_status === 'confirmed' ? 1 : 0.65
|
|
696
|
+
)),
|
|
697
|
+
recency: averageScore(sourceItems.map(personalGlobalItemRecency)),
|
|
698
|
+
negative_evidence: averageScore(sourceItems.map((item) =>
|
|
699
|
+
item.requires_attention === true
|
|
700
|
+
? 0
|
|
701
|
+
: 1 / (1 + Math.max(0, Number(item.negative_evidence_count ?? 0)))
|
|
702
|
+
))
|
|
703
|
+
};
|
|
704
|
+
const details = Object.fromEntries(
|
|
705
|
+
Object.entries(signals).map(([key, value]) => {
|
|
706
|
+
const weight = PERSONAL_GLOBAL_RANKING_WEIGHTS[key];
|
|
707
|
+
return [key, {
|
|
708
|
+
value: Number(value.toFixed(4)),
|
|
709
|
+
weight,
|
|
710
|
+
contribution: Number((value * weight).toFixed(4))
|
|
711
|
+
}];
|
|
712
|
+
})
|
|
713
|
+
);
|
|
714
|
+
const score = Object.values(details)
|
|
715
|
+
.reduce((sum, signal) => sum + signal.contribution, 0);
|
|
716
|
+
return {
|
|
717
|
+
score: Number(score.toFixed(4)),
|
|
718
|
+
signals: details,
|
|
719
|
+
source_project_count: projectCount,
|
|
720
|
+
selected_project_count: selectedProjectCount,
|
|
721
|
+
policy: 'ranking_only_never_changes_scope_or_confirmation_authority'
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function personalGlobalItemRecency(item) {
|
|
726
|
+
const timestamp = [
|
|
727
|
+
item.last_used_at,
|
|
728
|
+
item.last_human_changed_at,
|
|
729
|
+
item.created_at
|
|
730
|
+
].map((value) => Date.parse(value ?? ''))
|
|
731
|
+
.find(Number.isFinite);
|
|
732
|
+
if (!Number.isFinite(timestamp)) return 0.5;
|
|
733
|
+
const ageDays = Math.max(0, (Date.now() - timestamp) / 86_400_000);
|
|
734
|
+
return Math.exp(-ageDays / 180);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function averageScore(values) {
|
|
738
|
+
if (values.length === 0) return 0;
|
|
739
|
+
return boundedScore(
|
|
740
|
+
values.reduce((sum, value) => sum + value, 0) / values.length
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function boundedScore(value) {
|
|
745
|
+
return Math.max(0, Math.min(1, Number(value) || 0));
|
|
746
|
+
}
|
|
747
|
+
|
|
391
748
|
function commonKnowledgeSimilarity(left, right) {
|
|
392
749
|
if (left.key && right.key && left.key === right.key) return 1;
|
|
393
750
|
const leftTokens = commonKnowledgeTokens(left);
|
|
@@ -407,6 +764,27 @@ function commonKnowledgeTokens(item) {
|
|
|
407
764
|
item.fact,
|
|
408
765
|
item.type
|
|
409
766
|
].filter(Boolean).join(' ').toLowerCase();
|
|
767
|
+
return lexicalTokens(text);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function preferenceContentTokens(item) {
|
|
771
|
+
const text = [
|
|
772
|
+
item.instruction,
|
|
773
|
+
item.summary,
|
|
774
|
+
item.fact
|
|
775
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
776
|
+
return lexicalTokens(text || String(item.name ?? '').toLowerCase());
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function sharedPreferenceTerms(items) {
|
|
780
|
+
const tokenSets = items.map(preferenceContentTokens);
|
|
781
|
+
if (tokenSets.length === 0) return [];
|
|
782
|
+
return [...tokenSets[0]]
|
|
783
|
+
.filter((term) => tokenSets.slice(1).every((tokens) => tokens.has(term)))
|
|
784
|
+
.sort();
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function lexicalTokens(text) {
|
|
410
788
|
const tokens = new Set(
|
|
411
789
|
text.match(/[a-z0-9]+(?:[._-][a-z0-9]+)*/g) ?? []
|
|
412
790
|
);
|
|
@@ -423,6 +801,8 @@ function commonKnowledgeCandidatePolicy() {
|
|
|
423
801
|
return {
|
|
424
802
|
read_only: true,
|
|
425
803
|
direct_part_of_children_only: true,
|
|
804
|
+
active_human_authorized_relations_only: true,
|
|
805
|
+
personal_preferences_excluded: true,
|
|
426
806
|
inherited_knowledge_excluded: true,
|
|
427
807
|
personal_global_excluded: true,
|
|
428
808
|
automatic_promotion: false,
|
|
@@ -431,6 +811,107 @@ function commonKnowledgeCandidatePolicy() {
|
|
|
431
811
|
};
|
|
432
812
|
}
|
|
433
813
|
|
|
814
|
+
function personalGlobalCandidatePolicy() {
|
|
815
|
+
return {
|
|
816
|
+
read_only: true,
|
|
817
|
+
exact_projects_only: true,
|
|
818
|
+
inherited_knowledge_excluded: true,
|
|
819
|
+
personal_global_excluded: true,
|
|
820
|
+
original_text_and_sources_preserved: true,
|
|
821
|
+
derived_core_is_non_authoritative: true,
|
|
822
|
+
human_scope_judgment_required: true,
|
|
823
|
+
automatic_scope_apply: false,
|
|
824
|
+
ranking_scores_are_non_authoritative: true,
|
|
825
|
+
stale_decisions_do_not_suppress_changed_candidates: true
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
async function personalGlobalCandidateDecisionState(
|
|
830
|
+
application,
|
|
831
|
+
personalSpaceId,
|
|
832
|
+
candidates
|
|
833
|
+
) {
|
|
834
|
+
if (candidates.length === 0) {
|
|
835
|
+
return { decisions: new Map(), revisions: new Map() };
|
|
836
|
+
}
|
|
837
|
+
const result = await application.personal
|
|
838
|
+
.personalGlobalPreferenceDecisionStatus({
|
|
839
|
+
personal_space_id: personalSpaceId,
|
|
840
|
+
candidates: candidates.map((candidate) => ({
|
|
841
|
+
candidate_id: candidate.candidate_id,
|
|
842
|
+
candidate_version: candidate.candidate_version
|
|
843
|
+
}))
|
|
844
|
+
});
|
|
845
|
+
return {
|
|
846
|
+
decisions: new Map((result.decisions ?? []).map((decision) => [
|
|
847
|
+
`${decision.candidate_id}:${decision.candidate_version}`,
|
|
848
|
+
{
|
|
849
|
+
decision: decision.decision,
|
|
850
|
+
decision_event_id: decision.decision_event_id,
|
|
851
|
+
decision_revision: decision.decision_revision,
|
|
852
|
+
target_scope: decision.target_scope,
|
|
853
|
+
target_project_id: decision.target_project_id ?? null,
|
|
854
|
+
global_assertion_id: decision.global_assertion_id,
|
|
855
|
+
global_assertion_active: decision.global_assertion_active
|
|
856
|
+
}
|
|
857
|
+
])),
|
|
858
|
+
revisions: new Map((result.revisions ?? []).map((revision) => [
|
|
859
|
+
revision.candidate_id,
|
|
860
|
+
revision
|
|
861
|
+
]))
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function personalGlobalCandidateWithDecisionState(candidate, revision) {
|
|
866
|
+
const decisionRevision = revision?.decision_revision ?? 0;
|
|
867
|
+
const previousVersion = revision?.current_candidate_version ?? null;
|
|
868
|
+
const sourceDrift = Boolean(
|
|
869
|
+
previousVersion && previousVersion !== candidate.candidate_version
|
|
870
|
+
);
|
|
871
|
+
return {
|
|
872
|
+
...candidate,
|
|
873
|
+
decision_revision: decisionRevision,
|
|
874
|
+
prior_decision_source_drift: sourceDrift,
|
|
875
|
+
prior_decision_candidate_version: sourceDrift ? previousVersion : null,
|
|
876
|
+
fresh_human_review_required: true
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function providerPersonalGlobalPreferenceDecision(input, {
|
|
881
|
+
approvalToken = null
|
|
882
|
+
} = {}) {
|
|
883
|
+
const result = {
|
|
884
|
+
personal_space_id: input.personalSpaceId,
|
|
885
|
+
candidate_version: input.candidateVersion,
|
|
886
|
+
decision_revision: input.decisionRevision,
|
|
887
|
+
source_items: input.sourceItems.map((item) => ({
|
|
888
|
+
item_id: item.itemId,
|
|
889
|
+
item_kind: item.itemKind,
|
|
890
|
+
project_id: item.projectId
|
|
891
|
+
})),
|
|
892
|
+
preference_key: input.preferenceKey,
|
|
893
|
+
target_scope: input.targetScope,
|
|
894
|
+
target_project_id: input.targetProjectId ?? null,
|
|
895
|
+
decision: input.decision,
|
|
896
|
+
global_title: input.globalTitle ?? null,
|
|
897
|
+
global_instruction: input.globalInstruction ?? null,
|
|
898
|
+
profile_aspect: input.profileAspect ?? null,
|
|
899
|
+
human_confirmation_reason: input.humanConfirmationReason,
|
|
900
|
+
confirmed_at: input.confirmedAt,
|
|
901
|
+
session_id: input.sessionId,
|
|
902
|
+
idempotency_key: input.idempotencyKey
|
|
903
|
+
};
|
|
904
|
+
if (approvalToken) result.approval_token = approvalToken;
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function personalGlobalPreferenceCandidateId(itemIds) {
|
|
909
|
+
return `personal-global-${createHash('sha256')
|
|
910
|
+
.update([...itemIds].sort().join('\n'))
|
|
911
|
+
.digest('hex')
|
|
912
|
+
.slice(0, 20)}`;
|
|
913
|
+
}
|
|
914
|
+
|
|
434
915
|
function decisionTraceKnowledge(input) {
|
|
435
916
|
const decisionKey = `decision:${input.decisionKey}`;
|
|
436
917
|
const selectedKey = `${decisionKey}:option:${input.selectedOption.key}`;
|