fuli-context 0.7.3 → 0.7.4-beta.1
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-F5e2sQ7p.js +2 -0
- package/dist/web/assets/{index-CCOGfZzE.css → index-X2j29j8i.css} +1 -1
- 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 +18 -18
- 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/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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export function agentCollaborationPreference(item) {
|
|
2
|
+
const preference = {
|
|
3
|
+
instruction: item.instruction ?? '',
|
|
4
|
+
preference_key: item.preference_key ?? item.key ?? item.id,
|
|
5
|
+
title: item.title ?? item.preference_key ?? item.key ?? item.id,
|
|
6
|
+
profile_aspect: item.profile_aspect ?? null,
|
|
7
|
+
preference_scope: item.preference_scope ?? 'global',
|
|
8
|
+
confirmation_status: item.confirmation_status ?? 'confirmed'
|
|
9
|
+
};
|
|
10
|
+
if (item.preference_project_id) {
|
|
11
|
+
preference.preference_project_id = item.preference_project_id;
|
|
12
|
+
}
|
|
13
|
+
for (const field of [
|
|
14
|
+
'attributes',
|
|
15
|
+
'weight',
|
|
16
|
+
'reason',
|
|
17
|
+
'confirmation_basis',
|
|
18
|
+
'reasoning_summary',
|
|
19
|
+
'inheritance_mode',
|
|
20
|
+
'inherited_project_ids',
|
|
21
|
+
'inherited_from_project_id',
|
|
22
|
+
'scope_distance',
|
|
23
|
+
'scope_path'
|
|
24
|
+
]) {
|
|
25
|
+
if (item[field] !== undefined) preference[field] = item[field];
|
|
26
|
+
}
|
|
27
|
+
return preference;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function deferredPreferenceConflicts(result, queuedConflicts) {
|
|
31
|
+
const items = [
|
|
32
|
+
...(result.global_preferences ?? []),
|
|
33
|
+
...(result.project_preferences ?? [])
|
|
34
|
+
];
|
|
35
|
+
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
36
|
+
const activeConflictPairs = new Set(
|
|
37
|
+
(result.conflicts ?? []).flatMap((conflict) => {
|
|
38
|
+
const ids = conflict.item_ids ?? [];
|
|
39
|
+
const pairs = [];
|
|
40
|
+
for (let left = 0; left < ids.length; left += 1) {
|
|
41
|
+
for (let right = left + 1; right < ids.length; right += 1) {
|
|
42
|
+
pairs.push(preferenceConflictPairKey(ids[left], ids[right]));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return pairs;
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
const queued = (queuedConflicts ?? [])
|
|
49
|
+
.filter((conflict) =>
|
|
50
|
+
conflict.status === 'ai_pending' &&
|
|
51
|
+
activeConflictPairs.has(preferenceConflictPairKey(
|
|
52
|
+
conflict.left_item_id,
|
|
53
|
+
conflict.right_item_id
|
|
54
|
+
)) &&
|
|
55
|
+
itemById.has(conflict.left_item_id) &&
|
|
56
|
+
itemById.has(conflict.right_item_id)
|
|
57
|
+
)
|
|
58
|
+
.map((conflict) => ({
|
|
59
|
+
...conflict,
|
|
60
|
+
left: itemById.get(conflict.left_item_id),
|
|
61
|
+
right: itemById.get(conflict.right_item_id)
|
|
62
|
+
}));
|
|
63
|
+
const queuedPairs = new Set(queued.map((conflict) => preferenceConflictPairKey(
|
|
64
|
+
conflict.left_item_id,
|
|
65
|
+
conflict.right_item_id
|
|
66
|
+
)));
|
|
67
|
+
const inheritedScopeConflicts = (result.conflicts ?? []).flatMap((conflict) => {
|
|
68
|
+
const alternatives = (conflict.item_ids ?? [])
|
|
69
|
+
.map((id) => itemById.get(id))
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
const projectIds = [...new Set(alternatives
|
|
72
|
+
.map(({ inherited_from_project_id: projectId }) => projectId)
|
|
73
|
+
.filter(Boolean))]
|
|
74
|
+
.sort();
|
|
75
|
+
const pairAlreadyQueued = alternatives.length === 2 && queuedPairs.has(
|
|
76
|
+
preferenceConflictPairKey(alternatives[0].id, alternatives[1].id)
|
|
77
|
+
);
|
|
78
|
+
if (
|
|
79
|
+
pairAlreadyQueued ||
|
|
80
|
+
alternatives.length < 2 ||
|
|
81
|
+
projectIds.length < 2 ||
|
|
82
|
+
alternatives.some(({ inherited_from_project_id: projectId }) => !projectId)
|
|
83
|
+
) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
return [{
|
|
87
|
+
id: `inherited-scope:${conflict.preference_key}:` +
|
|
88
|
+
alternatives.map(({ id }) => id).sort().join(','),
|
|
89
|
+
preference_key: conflict.preference_key,
|
|
90
|
+
preference_scope: 'project',
|
|
91
|
+
preference_project_ids: projectIds,
|
|
92
|
+
status: 'human_scope_judgment_required',
|
|
93
|
+
reason: 'Equally near parent projects define conflicting preferences for the active child.',
|
|
94
|
+
inherited_scope_conflict: true,
|
|
95
|
+
alternatives
|
|
96
|
+
}];
|
|
97
|
+
});
|
|
98
|
+
return [...queued, ...inheritedScopeConflicts];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function agentDeferredPreferenceConflict(conflict) {
|
|
102
|
+
if (conflict.inherited_scope_conflict) {
|
|
103
|
+
return {
|
|
104
|
+
id: conflict.id,
|
|
105
|
+
preference_key: conflict.preference_key,
|
|
106
|
+
preference_scope: conflict.preference_scope,
|
|
107
|
+
preference_project_ids: conflict.preference_project_ids,
|
|
108
|
+
status: conflict.status,
|
|
109
|
+
reason: conflict.reason,
|
|
110
|
+
alternatives: conflict.alternatives.map(agentConflictPreference),
|
|
111
|
+
automatic_resolution: false,
|
|
112
|
+
requires_human_scope_judgment: true,
|
|
113
|
+
required_action: 'Ask a human to choose or narrow the applicable project scope; do not use weight to select a winner.',
|
|
114
|
+
resolution_options: []
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
id: conflict.id,
|
|
119
|
+
preference_key: conflict.preference_key,
|
|
120
|
+
preference_scope: conflict.preference_scope,
|
|
121
|
+
...(conflict.preference_project_id
|
|
122
|
+
? { preference_project_id: conflict.preference_project_id }
|
|
123
|
+
: {}),
|
|
124
|
+
status: conflict.status,
|
|
125
|
+
deferred_at: conflict.deferred_at,
|
|
126
|
+
reason: conflict.reason,
|
|
127
|
+
left: agentConflictPreference(conflict.left),
|
|
128
|
+
right: agentConflictPreference(conflict.right),
|
|
129
|
+
required_action: 'Resolve this conflict before using either side when it is relevant to the current task.',
|
|
130
|
+
resolution_options: ['merge', 'keep_left', 'keep_right', 'split_scope']
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function agentConflictPreference(item) {
|
|
135
|
+
const preference = {
|
|
136
|
+
item_id: item.id,
|
|
137
|
+
item_kind: item.item_kind,
|
|
138
|
+
title: item.title,
|
|
139
|
+
instruction: item.instruction,
|
|
140
|
+
confirmed_at: item.confirmed_at,
|
|
141
|
+
attributes: item.attributes ?? {}
|
|
142
|
+
};
|
|
143
|
+
for (const field of [
|
|
144
|
+
'preference_project_id',
|
|
145
|
+
'inherited_from_project_id',
|
|
146
|
+
'scope_distance',
|
|
147
|
+
'scope_path',
|
|
148
|
+
'weight',
|
|
149
|
+
'reason',
|
|
150
|
+
'confirmation_basis',
|
|
151
|
+
'reasoning_summary',
|
|
152
|
+
'inheritance_mode',
|
|
153
|
+
'inherited_project_ids'
|
|
154
|
+
]) {
|
|
155
|
+
if (item[field] !== undefined) preference[field] = item[field];
|
|
156
|
+
}
|
|
157
|
+
return preference;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function preferenceConflictPairKey(leftId, rightId) {
|
|
161
|
+
return [leftId, rightId].sort().join('\u0000');
|
|
162
|
+
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
canonicalProviderUrl,
|
|
6
6
|
readGraphRuntimeConfig
|
|
7
7
|
} from './runtime-config.js';
|
|
8
|
-
import { ApplicationError } from '../app/application-error.js';
|
|
8
|
+
import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
|
|
9
9
|
import { detectSensitiveContent } from '../security/sensitive-content.js';
|
|
10
10
|
import {
|
|
11
11
|
CapturePolicyStore,
|
|
@@ -38,10 +38,16 @@ import {
|
|
|
38
38
|
beginTaskContext as beginTaskContextWorkflow,
|
|
39
39
|
checkpointTaskKnowledge as checkpointTaskKnowledgeWorkflow,
|
|
40
40
|
discoverCommonKnowledgeCandidates as discoverCommonKnowledgeCandidatesWorkflow,
|
|
41
|
+
discoverPersonalGlobalPreferenceCandidates as
|
|
42
|
+
discoverPersonalGlobalPreferenceCandidatesWorkflow,
|
|
43
|
+
applyPersonalGlobalPreferenceDecision as
|
|
44
|
+
applyPersonalGlobalPreferenceDecisionWorkflow,
|
|
41
45
|
providerCommonKnowledgePromotion,
|
|
42
46
|
recordDecisionTrace as recordDecisionTraceWorkflow,
|
|
43
47
|
recordKnowledgeFeedback as recordKnowledgeFeedbackWorkflow,
|
|
44
|
-
searchCurrentProjectKnowledge as searchCurrentProjectKnowledgeWorkflow
|
|
48
|
+
searchCurrentProjectKnowledge as searchCurrentProjectKnowledgeWorkflow,
|
|
49
|
+
previewPersonalGlobalPreferenceDecision as
|
|
50
|
+
previewPersonalGlobalPreferenceDecisionWorkflow
|
|
45
51
|
} from './agent-knowledge-workflows.js';
|
|
46
52
|
import {
|
|
47
53
|
assertPublicKnowledgeEligible,
|
|
@@ -56,6 +62,29 @@ import {
|
|
|
56
62
|
recordKnowledgeReviewProgress as recordKnowledgeReviewProgressWorkflow,
|
|
57
63
|
startKnowledgeReview as startKnowledgeReviewWorkflow
|
|
58
64
|
} from './knowledge-review.js';
|
|
65
|
+
import {
|
|
66
|
+
providerWorkflowCandidateSearch,
|
|
67
|
+
workflowCandidatePage
|
|
68
|
+
} from './workflow-candidate-mapping.js';
|
|
69
|
+
import {
|
|
70
|
+
providerWorkflowTransitionObservation
|
|
71
|
+
} from './workflow-observation.js';
|
|
72
|
+
import {
|
|
73
|
+
relatedProjectGuidance
|
|
74
|
+
} from './related-project-suggestions.js';
|
|
75
|
+
import {
|
|
76
|
+
agentCollaborationPreference,
|
|
77
|
+
agentDeferredPreferenceConflict,
|
|
78
|
+
deferredPreferenceConflicts
|
|
79
|
+
} from './collaboration-preference-projection.js';
|
|
80
|
+
import { buildUserTasteSkill } from './user-taste-skill.js';
|
|
81
|
+
import { getWritingTasteProfile as getWritingTasteProfileWorkflow } from './writing-taste-profile-workflow.js';
|
|
82
|
+
import {
|
|
83
|
+
groupSubscriptions,
|
|
84
|
+
loadSearchRelatedProjectSuggestions,
|
|
85
|
+
rankedSearchItems,
|
|
86
|
+
retrievalGuidanceForScope
|
|
87
|
+
} from './federated-search-support.js';
|
|
59
88
|
|
|
60
89
|
export function openFederatedGraphApplication({
|
|
61
90
|
runtimeConfigPath,
|
|
@@ -75,7 +104,8 @@ export function openFederatedGraphApplication({
|
|
|
75
104
|
agentAccessPolicyStore: agentAccessPolicyStore ?? new AgentAccessPolicyStore(
|
|
76
105
|
agentAccessPolicyPathForRuntime(runtimeConfigPath)
|
|
77
106
|
),
|
|
78
|
-
consoleUrl: sourceConsoleUrl(runtimeConfigPath)
|
|
107
|
+
consoleUrl: sourceConsoleUrl(runtimeConfigPath),
|
|
108
|
+
providerRequestTimeoutMs: providerRequestTimeoutFromEnv(env)
|
|
79
109
|
});
|
|
80
110
|
if (typeof runtimeConfigPath === 'string' && runtimeConfigPath) {
|
|
81
111
|
attachExternalKnowledgeRuntime(app, {
|
|
@@ -87,6 +117,18 @@ export function openFederatedGraphApplication({
|
|
|
87
117
|
return app;
|
|
88
118
|
}
|
|
89
119
|
|
|
120
|
+
function providerRequestTimeoutFromEnv(env) {
|
|
121
|
+
const value = env?.FULI_PROVIDER_REQUEST_TIMEOUT_MS;
|
|
122
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
123
|
+
const parsed = Number(value);
|
|
124
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 2_147_483_647) {
|
|
125
|
+
throw new TypeError(
|
|
126
|
+
'FULI_PROVIDER_REQUEST_TIMEOUT_MS must be a positive safe integer'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return parsed;
|
|
130
|
+
}
|
|
131
|
+
|
|
90
132
|
export class FederatedGraphApplication {
|
|
91
133
|
constructor(config, {
|
|
92
134
|
fetchImpl = globalThis.fetch,
|
|
@@ -107,6 +149,7 @@ export class FederatedGraphApplication {
|
|
|
107
149
|
this.personal = new GraphitiProviderClient({
|
|
108
150
|
baseUrl: config.personal.providerUrl,
|
|
109
151
|
accessToken: config.personal.accessToken,
|
|
152
|
+
workflowObservationToken: config.personal.workflowObservationToken ?? null,
|
|
110
153
|
fetchImpl,
|
|
111
154
|
requestTimeoutMs: providerRequestTimeoutMs
|
|
112
155
|
});
|
|
@@ -156,7 +199,10 @@ export class FederatedGraphApplication {
|
|
|
156
199
|
const episode = providerEpisode(input);
|
|
157
200
|
if (input.targetKind === 'personal') {
|
|
158
201
|
if (input.spaceId !== this.config.personal.spaceId) {
|
|
159
|
-
throw new
|
|
202
|
+
throw new ApplicationError(
|
|
203
|
+
ApplicationErrorCode.VALIDATION,
|
|
204
|
+
'Personal capture target must be the active personal space'
|
|
205
|
+
);
|
|
160
206
|
}
|
|
161
207
|
const result = await this.personal.commit({
|
|
162
208
|
space_id: input.spaceId,
|
|
@@ -166,7 +212,10 @@ export class FederatedGraphApplication {
|
|
|
166
212
|
return { route: 'personal', ...result };
|
|
167
213
|
}
|
|
168
214
|
if (episode.sensitivity !== 'normal') {
|
|
169
|
-
throw new
|
|
215
|
+
throw new ApplicationError(
|
|
216
|
+
ApplicationErrorCode.VALIDATION,
|
|
217
|
+
'Private or restricted knowledge cannot enter a team-shared project queue. Use targetKind "personal" with personalProjectId to keep it in the personal graph'
|
|
218
|
+
);
|
|
170
219
|
}
|
|
171
220
|
assertPublicKnowledgeEligible(episode);
|
|
172
221
|
const workspace = this.#workspace(input.providerUrl);
|
|
@@ -185,6 +234,23 @@ export class FederatedGraphApplication {
|
|
|
185
234
|
};
|
|
186
235
|
}
|
|
187
236
|
|
|
237
|
+
async recordWorkflowTransitionObservation(input) {
|
|
238
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
239
|
+
const capturePolicy = this.getCapturePolicy();
|
|
240
|
+
if (!capturePolicy.enabled) {
|
|
241
|
+
return {
|
|
242
|
+
route: 'disabled',
|
|
243
|
+
status: 'capture_disabled',
|
|
244
|
+
capturePolicy
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
assertNoCredentials(input);
|
|
248
|
+
const result = await this.personal.recordWorkflowObservation(
|
|
249
|
+
providerWorkflowTransitionObservation(input)
|
|
250
|
+
);
|
|
251
|
+
return { route: 'personal', ...result };
|
|
252
|
+
}
|
|
253
|
+
|
|
188
254
|
async getCollaborationPreferences({
|
|
189
255
|
personalProjectId = null,
|
|
190
256
|
projectPath = null,
|
|
@@ -231,8 +297,10 @@ export class FederatedGraphApplication {
|
|
|
231
297
|
apply: 'effective_preferences',
|
|
232
298
|
global_scope: 'Apply personal-global preferences in every user task.',
|
|
233
299
|
project_scope: resolvedProjectId
|
|
234
|
-
? `Also apply preferences scoped to
|
|
300
|
+
? `Also apply preferences scoped to ${resolvedProjectId} and confirmed ancestor preferences whose descendants or selected-projects mode explicitly reaches it.`
|
|
235
301
|
: 'No exact personal project matched; do not apply project-scoped preferences.',
|
|
302
|
+
inheritance: 'Project preference inheritance follows only PART_OF or USES_KNOWLEDGE_FROM for at most two hops. Preserve inherited_from_project_id, scope_path, and scope_distance. An exact item wins only after confirmation authority is compared; weight never expands scope or resolves a conflict.',
|
|
303
|
+
related_projects: 'RELATED_TO preferences never auto-apply. Use structured related-project suggestions to ask before a one-time read-only expansion.',
|
|
236
304
|
conflicts: 'Do not apply entries listed in conflicts until the user resolves them.',
|
|
237
305
|
deferred_conflicts: 'If the current task would use a deferred_conflict, call resolve_deferred_preference_conflict before applying either side. Ignore unrelated deferred conflicts. The resolution must preserve the AI audit marker.',
|
|
238
306
|
authority: 'Human or authoritative-source confirmed preferences outrank agent-confirmed preferences. Agent-confirmed preferences are usable but lower priority and remain explicitly marked.',
|
|
@@ -259,6 +327,9 @@ export class FederatedGraphApplication {
|
|
|
259
327
|
personal_project_id: result.personal_project_id,
|
|
260
328
|
global_preference_count: (result.global_preferences ?? []).length,
|
|
261
329
|
project_preference_count: (result.project_preferences ?? []).length,
|
|
330
|
+
inherited_project_preference_count: (result.project_preferences ?? [])
|
|
331
|
+
.filter(({ inherited_from_project_id: projectId }) => Boolean(projectId))
|
|
332
|
+
.length,
|
|
262
333
|
conflict_count: (result.conflicts ?? []).length,
|
|
263
334
|
ai_deferred_conflict_count: deferredConflicts.length,
|
|
264
335
|
overridden_global_count: (result.overridden_global_ids ?? []).length,
|
|
@@ -268,6 +339,43 @@ export class FederatedGraphApplication {
|
|
|
268
339
|
};
|
|
269
340
|
}
|
|
270
341
|
|
|
342
|
+
async getUserTasteSkill({
|
|
343
|
+
personalProjectId = null,
|
|
344
|
+
projectPath = null,
|
|
345
|
+
taskPrompt = null,
|
|
346
|
+
limit = 100
|
|
347
|
+
} = {}) {
|
|
348
|
+
const context = await this.getCollaborationPreferences({
|
|
349
|
+
personalProjectId,
|
|
350
|
+
projectPath,
|
|
351
|
+
taskPrompt,
|
|
352
|
+
limit,
|
|
353
|
+
agentInvocation: true,
|
|
354
|
+
agentToolName: 'get_user_taste_skill'
|
|
355
|
+
});
|
|
356
|
+
const skill = buildUserTasteSkill({
|
|
357
|
+
preferences: context.effective_preferences,
|
|
358
|
+
taskPrompt,
|
|
359
|
+
personalSpaceId: context.context.personal_space_id,
|
|
360
|
+
personalProjectId: context.context.personal_project_id
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
status: 'generated',
|
|
364
|
+
...skill,
|
|
365
|
+
...(context.task_knowledge_recall
|
|
366
|
+
? { task_knowledge_recall: context.task_knowledge_recall }
|
|
367
|
+
: {}),
|
|
368
|
+
application_guidance: context.application_guidance,
|
|
369
|
+
deferred_conflicts: context.deferred_conflicts,
|
|
370
|
+
context: context.context
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async getWritingTasteProfile(input = {}) {
|
|
375
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
376
|
+
return getWritingTasteProfileWorkflow(this, input);
|
|
377
|
+
}
|
|
378
|
+
|
|
271
379
|
async #resolvePreferenceProject({ personalProjectId, projectPath }) {
|
|
272
380
|
if (projectPath !== undefined && projectPath !== null) {
|
|
273
381
|
const projects = await this.personal.listPersonalProjects(
|
|
@@ -432,6 +540,10 @@ export class FederatedGraphApplication {
|
|
|
432
540
|
.map(({ id }) => ({ item_id: id, item_kind: 'entity' }))
|
|
433
541
|
], agentToolName);
|
|
434
542
|
}
|
|
543
|
+
const relatedProjects = await loadSearchRelatedProjectSuggestions(
|
|
544
|
+
this,
|
|
545
|
+
{ facts, entities }
|
|
546
|
+
);
|
|
435
547
|
return {
|
|
436
548
|
query,
|
|
437
549
|
sourceMarker,
|
|
@@ -446,7 +558,10 @@ export class FederatedGraphApplication {
|
|
|
446
558
|
searchedProjectIds,
|
|
447
559
|
failedProjectIds: unavailableProjectIds,
|
|
448
560
|
partial: unavailableProjectIds.length > 0,
|
|
449
|
-
subscriptions: selectedSubscriptions
|
|
561
|
+
subscriptions: selectedSubscriptions,
|
|
562
|
+
relatedProjectSuggestions: relatedProjects.suggestions,
|
|
563
|
+
relatedProjectSuggestionsStatus: relatedProjects.status,
|
|
564
|
+
relatedProjectGuidance: relatedProjectGuidance(relatedProjects.suggestions)
|
|
450
565
|
};
|
|
451
566
|
}
|
|
452
567
|
|
|
@@ -463,6 +578,23 @@ export class FederatedGraphApplication {
|
|
|
463
578
|
return discoverCommonKnowledgeCandidatesWorkflow(this, input);
|
|
464
579
|
}
|
|
465
580
|
|
|
581
|
+
async discoverPersonalGlobalPreferenceCandidates(input) {
|
|
582
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
583
|
+
return discoverPersonalGlobalPreferenceCandidatesWorkflow(this, input);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async previewPersonalGlobalPreferenceDecision(input) {
|
|
587
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
588
|
+
assertSafeKnowledgeMutation(input);
|
|
589
|
+
return previewPersonalGlobalPreferenceDecisionWorkflow(this, input);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async applyPersonalGlobalPreferenceDecision(input) {
|
|
593
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
594
|
+
assertSafeKnowledgeMutation(input);
|
|
595
|
+
return applyPersonalGlobalPreferenceDecisionWorkflow(this, input);
|
|
596
|
+
}
|
|
597
|
+
|
|
466
598
|
async previewCommonKnowledgePromotion(input) {
|
|
467
599
|
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
468
600
|
assertSafeKnowledgeMutation(input);
|
|
@@ -531,6 +663,22 @@ export class FederatedGraphApplication {
|
|
|
531
663
|
return finishKnowledgeReviewWorkflow(this, input);
|
|
532
664
|
}
|
|
533
665
|
|
|
666
|
+
async listWorkflowCandidates(input) {
|
|
667
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
668
|
+
const result = await this.personal.searchWorkflowCandidates(
|
|
669
|
+
providerWorkflowCandidateSearch(input)
|
|
670
|
+
);
|
|
671
|
+
return workflowCandidatePage(result);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async recommendNextWorkflowSteps(input) {
|
|
675
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
676
|
+
const result = await this.personal.recommendWorkflowCandidates(
|
|
677
|
+
providerWorkflowCandidateSearch(input)
|
|
678
|
+
);
|
|
679
|
+
return workflowCandidatePage(result);
|
|
680
|
+
}
|
|
681
|
+
|
|
534
682
|
async recordKnowledgeUsage({
|
|
535
683
|
personalSpaceId,
|
|
536
684
|
taskId,
|
|
@@ -1095,89 +1243,6 @@ export class FederatedGraphApplication {
|
|
|
1095
1243
|
}
|
|
1096
1244
|
}
|
|
1097
1245
|
|
|
1098
|
-
function agentCollaborationPreference(item) {
|
|
1099
|
-
const preference = {
|
|
1100
|
-
instruction: item.instruction ?? '',
|
|
1101
|
-
preference_key: item.preference_key ?? item.key ?? item.id,
|
|
1102
|
-
title: item.title ?? item.preference_key ?? item.key ?? item.id,
|
|
1103
|
-
profile_aspect: item.profile_aspect ?? null,
|
|
1104
|
-
preference_scope: item.preference_scope ?? 'global',
|
|
1105
|
-
confirmation_status: item.confirmation_status ?? 'confirmed'
|
|
1106
|
-
};
|
|
1107
|
-
if (item.preference_project_id) {
|
|
1108
|
-
preference.preference_project_id = item.preference_project_id;
|
|
1109
|
-
}
|
|
1110
|
-
return preference;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
function deferredPreferenceConflicts(result, queuedConflicts) {
|
|
1114
|
-
const items = [
|
|
1115
|
-
...(result.global_preferences ?? []),
|
|
1116
|
-
...(result.project_preferences ?? [])
|
|
1117
|
-
];
|
|
1118
|
-
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
1119
|
-
const activeConflictPairs = new Set(
|
|
1120
|
-
(result.conflicts ?? []).flatMap((conflict) => {
|
|
1121
|
-
const ids = conflict.item_ids ?? [];
|
|
1122
|
-
const pairs = [];
|
|
1123
|
-
for (let left = 0; left < ids.length; left += 1) {
|
|
1124
|
-
for (let right = left + 1; right < ids.length; right += 1) {
|
|
1125
|
-
pairs.push(preferenceConflictPairKey(ids[left], ids[right]));
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
return pairs;
|
|
1129
|
-
})
|
|
1130
|
-
);
|
|
1131
|
-
return (queuedConflicts ?? [])
|
|
1132
|
-
.filter((conflict) =>
|
|
1133
|
-
conflict.status === 'ai_pending' &&
|
|
1134
|
-
activeConflictPairs.has(preferenceConflictPairKey(
|
|
1135
|
-
conflict.left_item_id,
|
|
1136
|
-
conflict.right_item_id
|
|
1137
|
-
)) &&
|
|
1138
|
-
itemById.has(conflict.left_item_id) &&
|
|
1139
|
-
itemById.has(conflict.right_item_id)
|
|
1140
|
-
)
|
|
1141
|
-
.map((conflict) => ({
|
|
1142
|
-
...conflict,
|
|
1143
|
-
left: itemById.get(conflict.left_item_id),
|
|
1144
|
-
right: itemById.get(conflict.right_item_id)
|
|
1145
|
-
}));
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
function agentDeferredPreferenceConflict(conflict) {
|
|
1149
|
-
return {
|
|
1150
|
-
id: conflict.id,
|
|
1151
|
-
preference_key: conflict.preference_key,
|
|
1152
|
-
preference_scope: conflict.preference_scope,
|
|
1153
|
-
...(conflict.preference_project_id
|
|
1154
|
-
? { preference_project_id: conflict.preference_project_id }
|
|
1155
|
-
: {}),
|
|
1156
|
-
status: conflict.status,
|
|
1157
|
-
deferred_at: conflict.deferred_at,
|
|
1158
|
-
reason: conflict.reason,
|
|
1159
|
-
left: agentConflictPreference(conflict.left),
|
|
1160
|
-
right: agentConflictPreference(conflict.right),
|
|
1161
|
-
required_action: 'Resolve this conflict before using either side when it is relevant to the current task.',
|
|
1162
|
-
resolution_options: ['merge', 'keep_left', 'keep_right', 'split_scope']
|
|
1163
|
-
};
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
function agentConflictPreference(item) {
|
|
1167
|
-
return {
|
|
1168
|
-
item_id: item.id,
|
|
1169
|
-
item_kind: item.item_kind,
|
|
1170
|
-
title: item.title,
|
|
1171
|
-
instruction: item.instruction,
|
|
1172
|
-
confirmed_at: item.confirmed_at,
|
|
1173
|
-
attributes: item.attributes ?? {}
|
|
1174
|
-
};
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
function preferenceConflictPairKey(leftId, rightId) {
|
|
1178
|
-
return [leftId, rightId].sort().join('\u0000');
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
1246
|
function assertNoCredentials(input) {
|
|
1182
1247
|
const content = JSON.stringify({
|
|
1183
1248
|
name: input.name,
|
|
@@ -1196,49 +1261,6 @@ function assertNoCredentials(input) {
|
|
|
1196
1261
|
}
|
|
1197
1262
|
}
|
|
1198
1263
|
|
|
1199
|
-
function retrievalGuidanceForScope(personalProjectScope) {
|
|
1200
|
-
if (personalProjectScope === 'all_local_confirmed') {
|
|
1201
|
-
return {
|
|
1202
|
-
currentPersonalProjectScope: 'all_local_confirmed',
|
|
1203
|
-
markerToUseIfNoSupportingEvidence: 'noMatchSourceMarker',
|
|
1204
|
-
requiredNextActionIfNoSupportingEvidence:
|
|
1205
|
-
'search_current_workspace_files_or_ask_for_safe_root',
|
|
1206
|
-
workspaceFileSearch: {
|
|
1207
|
-
available: true,
|
|
1208
|
-
consentSource: 'bounded_expansion_confirmation',
|
|
1209
|
-
rootBoundary: 'current_working_directory',
|
|
1210
|
-
requiresSafeProjectOrWorkspaceRoot: true,
|
|
1211
|
-
forbiddenBroadRoots: ['user_home', 'filesystem_root'],
|
|
1212
|
-
readOnly: true,
|
|
1213
|
-
includesPublicProjects: false
|
|
1214
|
-
},
|
|
1215
|
-
instruction: 'Use read-only local file search in the current repository or explicit ' +
|
|
1216
|
-
'workspace root, preserving exact names first. If the working directory is the user ' +
|
|
1217
|
-
'home, filesystem root, or otherwise too broad, ask for a safe root. Never search ' +
|
|
1218
|
-
'outside that root or inspect credential stores; if no evidence supports the answer, ' +
|
|
1219
|
-
'ask for a source clue.'
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
return {
|
|
1223
|
-
currentPersonalProjectScope: 'bounded',
|
|
1224
|
-
markerToUseIfNoSupportingEvidence: 'noMatchSourceMarker',
|
|
1225
|
-
requiredNextActionIfNoSupportingEvidence:
|
|
1226
|
-
'ask_user_to_confirm_all_local_and_workspace_search',
|
|
1227
|
-
expansion: {
|
|
1228
|
-
available: true,
|
|
1229
|
-
requiresExplicitUserConfirmation: true,
|
|
1230
|
-
input: { personalProjectScope: 'all_local_confirmed' },
|
|
1231
|
-
readOnly: true,
|
|
1232
|
-
oneQueryOnly: true,
|
|
1233
|
-
includesPublicProjects: false,
|
|
1234
|
-
includesCurrentWorkspaceFiles: true
|
|
1235
|
-
},
|
|
1236
|
-
instruction: 'Ask whether to widen this one read-only lookup to all registered local ' +
|
|
1237
|
-
'personal projects and, if still unresolved, current repository or workspace files. ' +
|
|
1238
|
-
'Exclude public projects and paths outside the current workspace; then stop and wait.'
|
|
1239
|
-
};
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
1264
|
function assertSafeKnowledgeMutation(input) {
|
|
1243
1265
|
const content = JSON.stringify({
|
|
1244
1266
|
reason: input.reason,
|
|
@@ -1262,34 +1284,6 @@ function assertSafeKnowledgeMutation(input) {
|
|
|
1262
1284
|
}
|
|
1263
1285
|
}
|
|
1264
1286
|
|
|
1265
|
-
function groupSubscriptions(subscriptions) {
|
|
1266
|
-
const grouped = new Map();
|
|
1267
|
-
for (const subscription of subscriptions) {
|
|
1268
|
-
const url = canonicalProviderUrl(subscription.provider_url);
|
|
1269
|
-
if (!grouped.has(url)) grouped.set(url, []);
|
|
1270
|
-
grouped.get(url).push(subscription);
|
|
1271
|
-
}
|
|
1272
|
-
return grouped;
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
function rankedSearchItems(searchResults, key, limit) {
|
|
1276
|
-
return searchResults
|
|
1277
|
-
.flatMap((result) => result[key])
|
|
1278
|
-
.map((item, index) => ({ item, index }))
|
|
1279
|
-
.sort((left, right) =>
|
|
1280
|
-
searchItemScore(right.item) - searchItemScore(left.item) ||
|
|
1281
|
-
left.index - right.index
|
|
1282
|
-
)
|
|
1283
|
-
.slice(0, limit)
|
|
1284
|
-
.map(({ item }) => item);
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
function searchItemScore(item) {
|
|
1288
|
-
return typeof item?.score === 'number' && Number.isFinite(item.score)
|
|
1289
|
-
? item.score
|
|
1290
|
-
: 0;
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
1287
|
async function safely(operation, fallback) {
|
|
1294
1288
|
try {
|
|
1295
1289
|
return await operation();
|