fuli-context 0.2.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/LICENSE +201 -0
- package/README.md +178 -0
- package/compose.graphiti.yml +86 -0
- package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
- package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
- package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
- package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
- package/dist/web/assets/console-tbvTUi6p.js +1 -0
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-B3s5CXhv.js +1 -0
- package/dist/web/assets/index-Devf7glZ.css +1 -0
- package/dist/web/assets/index-DwUDhQKc.js +6 -0
- package/dist/web/assets/model-DVT8sjy9.js +1 -0
- package/dist/web/assets/paths-B790iniO.js +1 -0
- package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
- package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
- package/dist/web/index.html +22 -0
- package/graph-provider/Dockerfile +16 -0
- package/graph-provider/fuli_graph/__init__.py +5 -0
- package/graph-provider/fuli_graph/app.py +547 -0
- package/graph-provider/fuli_graph/auth.py +17 -0
- package/graph-provider/fuli_graph/collaboration_context.py +290 -0
- package/graph-provider/fuli_graph/config.py +34 -0
- package/graph-provider/fuli_graph/graph_models.py +123 -0
- package/graph-provider/fuli_graph/graph_projection.py +405 -0
- package/graph-provider/fuli_graph/graph_query.py +561 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
- package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
- package/graph-provider/fuli_graph/knowledge_management.py +816 -0
- package/graph-provider/fuli_graph/knowledge_records.py +65 -0
- package/graph-provider/fuli_graph/knowledge_search.py +723 -0
- package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
- package/graph-provider/fuli_graph/model_validation.py +58 -0
- package/graph-provider/fuli_graph/models.py +1179 -0
- package/graph-provider/fuli_graph/personal_project_access.py +29 -0
- package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
- package/graph-provider/fuli_graph/project_action_models.py +142 -0
- package/graph-provider/fuli_graph/project_knowledge.py +707 -0
- package/graph-provider/fuli_graph/project_management.py +251 -0
- package/graph-provider/fuli_graph/provider_values.py +40 -0
- package/graph-provider/fuli_graph/runtime.py +350 -0
- package/graph-provider/fuli_graph/search_projection.py +156 -0
- package/graph-provider/fuli_graph/store.py +24 -0
- package/graph-provider/fuli_graph/store_identity.py +237 -0
- package/graph-provider/fuli_graph/store_knowledge.py +447 -0
- package/graph-provider/fuli_graph/store_projects.py +242 -0
- package/graph-provider/fuli_graph/store_publication.py +370 -0
- package/graph-provider/fuli_graph/store_records.py +134 -0
- package/graph-provider/pyproject.toml +35 -0
- package/npm-shrinkwrap.json +6045 -0
- package/package.json +99 -0
- package/skills/capturing-session-knowledge/SKILL.md +226 -0
- package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
- package/skills/grilling-project/SKILL.md +129 -0
- package/skills/grilling-project/agents/openai.yaml +4 -0
- package/skills/grilling-project/references/project-profile.md +105 -0
- package/src/agent/candidate-read-service.js +53 -0
- package/src/agent/context-pack-service.js +83 -0
- package/src/agent/public-read-policy.js +39 -0
- package/src/agent/public-read-service.js +69 -0
- package/src/agent/read-projection.js +107 -0
- package/src/agent/search-service.js +61 -0
- package/src/agent/space-boundary.js +33 -0
- package/src/agent-tools/graph-definitions.js +479 -0
- package/src/agent-tools/graph-handlers.js +96 -0
- package/src/agent-tools/schema.js +37 -0
- package/src/agent-tools.js +16 -0
- package/src/app/application-error.js +13 -0
- package/src/app/bootstrap.js +8 -0
- package/src/app/create-application.js +143 -0
- package/src/app/state-service.js +38 -0
- package/src/candidates.js +78 -0
- package/src/classifier.js +63 -0
- package/src/cli/command-arguments.js +14 -0
- package/src/cli/command-registry.js +78 -0
- package/src/cli/commands/candidates.js +17 -0
- package/src/cli/commands/ingestion.js +32 -0
- package/src/cli/commands/reads.js +50 -0
- package/src/cli/commands/spaces.js +20 -0
- package/src/cli/invocation.js +47 -0
- package/src/cli/local-runtime-command.js +94 -0
- package/src/cli/local-runtime-options.js +64 -0
- package/src/cli/migrate-command.js +71 -0
- package/src/cli/migration-destination.js +117 -0
- package/src/cli/setup-agent-selection.js +195 -0
- package/src/cli/setup-command.js +85 -0
- package/src/cli/uninstall-command.js +67 -0
- package/src/cli.js +90 -0
- package/src/context-pack.js +206 -0
- package/src/defaults.js +1 -0
- package/src/extractor.js +77 -0
- package/src/fact-history.js +39 -0
- package/src/facts/fact-service.js +89 -0
- package/src/graphiti/agent-access-policy.js +88 -0
- package/src/graphiti/capture-policy.js +84 -0
- package/src/graphiti/federated-application.js +1292 -0
- package/src/graphiti/personal-context.js +29 -0
- package/src/graphiti/project-path-context.js +159 -0
- package/src/graphiti/provider-client.js +288 -0
- package/src/graphiti/runtime-config.js +85 -0
- package/src/graphiti/setup.js +43 -0
- package/src/graphiti/source-marker.js +142 -0
- package/src/http/api-router.js +133 -0
- package/src/http/bounded-json-array.js +19 -0
- package/src/http/error-mapping.js +36 -0
- package/src/http/graph-api-router.js +257 -0
- package/src/http/lens-route.js +50 -0
- package/src/http/request-policy.js +41 -0
- package/src/http/response.js +29 -0
- package/src/http/static-handler.js +55 -0
- package/src/ingestion.js +161 -0
- package/src/lens/lens-correction-service.js +81 -0
- package/src/lens/lens-fact-writer.js +85 -0
- package/src/lens/lens-input.js +62 -0
- package/src/lens/lens-query-projection.js +87 -0
- package/src/lens/lens-query-ranking.js +62 -0
- package/src/lens/lens-query.js +143 -0
- package/src/lens/lens-resource-projection.js +93 -0
- package/src/lens/lens-resource-selection.js +45 -0
- package/src/lens/lens-resource-service.js +103 -0
- package/src/lens/lens-search-query.js +122 -0
- package/src/lens/lens-service.js +183 -0
- package/src/local-runtime/lifecycle.js +254 -0
- package/src/mcp/create-mcp-server.js +85 -0
- package/src/mcp/instructions.js +10 -0
- package/src/mcp/project-action-preview-tokens.js +72 -0
- package/src/mcp/runtime.js +71 -0
- package/src/mcp/tool-annotations.js +63 -0
- package/src/mcp/tool-result.js +167 -0
- package/src/mcp/tool-schema.js +60 -0
- package/src/mcp-server.js +76 -0
- package/src/models.js +44 -0
- package/src/observer.js +83 -0
- package/src/package-metadata.js +6 -0
- package/src/project-rules.js +33 -0
- package/src/publication/canonical-json.js +39 -0
- package/src/publication/outbox-service.js +46 -0
- package/src/publication/publication-envelope-schema.js +75 -0
- package/src/publication/publication-envelope.js +37 -0
- package/src/publication/publication-policy.js +82 -0
- package/src/publication/publication-service.js +48 -0
- package/src/router.js +58 -0
- package/src/runtime-options.js +139 -0
- package/src/security/episode-visibility.js +14 -0
- package/src/security/fact-visibility.js +15 -0
- package/src/security/sensitive-content.js +18 -0
- package/src/security/source-metadata-policy.js +8 -0
- package/src/server/application-lifecycle.js +34 -0
- package/src/server/listen.js +24 -0
- package/src/server.js +135 -0
- package/src/setup/agent-installation-status.js +97 -0
- package/src/setup/agents.js +123 -0
- package/src/setup/codex-bootstrap.js +118 -0
- package/src/setup/codex-config.js +122 -0
- package/src/setup/config-backup.js +22 -0
- package/src/setup/container-runtime.js +388 -0
- package/src/setup/cursor-config.js +61 -0
- package/src/setup/graph-runtime.js +360 -0
- package/src/setup/node-runtime.js +24 -0
- package/src/setup/options.js +91 -0
- package/src/setup/paths.js +47 -0
- package/src/setup/session-skill.js +91 -0
- package/src/setup/setup.js +85 -0
- package/src/setup/uninstall.js +90 -0
- package/src/storage/episode-evidence-preview.js +100 -0
- package/src/storage/fact-search-order.js +18 -0
- package/src/storage/file/episode-evidence.js +37 -0
- package/src/storage/file/episode-index.js +59 -0
- package/src/storage/file/fact-search.js +60 -0
- package/src/storage/file-store.js +437 -0
- package/src/storage/import-json.js +70 -0
- package/src/storage/json-file.js +41 -0
- package/src/storage/legacy-record-validation.js +152 -0
- package/src/storage/legacy-snapshot-validation.js +32 -0
- package/src/storage/migrate.js +128 -0
- package/src/storage/migrations/001-initial.sql +83 -0
- package/src/storage/migrations/002-storage-integrity.sql +113 -0
- package/src/storage/record-validation.js +46 -0
- package/src/storage/snapshot-defaults.js +54 -0
- package/src/storage/sqlite/candidates-repository.js +84 -0
- package/src/storage/sqlite/episode-evidence-repository.js +154 -0
- package/src/storage/sqlite/episode-privacy-sql.js +33 -0
- package/src/storage/sqlite/episodes-repository.js +52 -0
- package/src/storage/sqlite/fact-search-repository.js +162 -0
- package/src/storage/sqlite/facts-repository.js +146 -0
- package/src/storage/sqlite/imports-repository.js +39 -0
- package/src/storage/sqlite/mapper.js +84 -0
- package/src/storage/sqlite/open-database.js +15 -0
- package/src/storage/sqlite/outbox-repository.js +103 -0
- package/src/storage/sqlite/snapshot-repository.js +69 -0
- package/src/storage/sqlite/spaces-repository.js +52 -0
- package/src/storage/sqlite/subscriptions-repository.js +54 -0
- package/src/storage/sqlite-store.js +159 -0
- package/src/storage/stable-json.js +24 -0
- package/src/storage/store-port.js +20 -0
- package/src/store.js +2 -0
- package/src/workspace-protocol/array-validation.js +19 -0
- package/src/workspace-protocol/base64url-contract.js +27 -0
- package/src/workspace-protocol/constants.js +37 -0
- package/src/workspace-protocol/error-contract.js +24 -0
- package/src/workspace-protocol/fact-contract.js +110 -0
- package/src/workspace-protocol/governance-contract.js +47 -0
- package/src/workspace-protocol/index.js +53 -0
- package/src/workspace-protocol/json-budget.js +19 -0
- package/src/workspace-protocol/manifest-contract.js +186 -0
- package/src/workspace-protocol/publication-contract.js +212 -0
- package/src/workspace-protocol/query-contract.js +149 -0
- package/src/workspace-protocol/scalar-contract.js +192 -0
- package/src/workspace-protocol/sync-contract.js +184 -0
- package/src/workspace-protocol/text-contract.js +39 -0
- package/src/workspace-protocol/validation.js +66 -0
|
@@ -0,0 +1,1292 @@
|
|
|
1
|
+
import { GraphitiProviderClient } from './provider-client.js';
|
|
2
|
+
import {
|
|
3
|
+
canonicalProviderUrl,
|
|
4
|
+
readGraphRuntimeConfig
|
|
5
|
+
} from './runtime-config.js';
|
|
6
|
+
import { ApplicationError } from '../app/application-error.js';
|
|
7
|
+
import { detectSensitiveContent } from '../security/sensitive-content.js';
|
|
8
|
+
import {
|
|
9
|
+
CapturePolicyStore,
|
|
10
|
+
capturePolicyPathForRuntime
|
|
11
|
+
} from './capture-policy.js';
|
|
12
|
+
import {
|
|
13
|
+
AgentAccessPolicyStore,
|
|
14
|
+
agentAccessPolicyPathForRuntime
|
|
15
|
+
} from './agent-access-policy.js';
|
|
16
|
+
import {
|
|
17
|
+
personalContextProjectIds,
|
|
18
|
+
personalProjectSearchBatches,
|
|
19
|
+
scopedSearchItems
|
|
20
|
+
} from './personal-context.js';
|
|
21
|
+
import {
|
|
22
|
+
createFuliSourceMarker,
|
|
23
|
+
sourceConsoleUrl
|
|
24
|
+
} from './source-marker.js';
|
|
25
|
+
import { resolvePersonalProjectPath } from './project-path-context.js';
|
|
26
|
+
|
|
27
|
+
export function openFederatedGraphApplication({
|
|
28
|
+
runtimeConfigPath,
|
|
29
|
+
config,
|
|
30
|
+
capturePolicyStore,
|
|
31
|
+
agentAccessPolicyStore,
|
|
32
|
+
fetchImpl = globalThis.fetch
|
|
33
|
+
}) {
|
|
34
|
+
const resolved = config ?? readGraphRuntimeConfig(runtimeConfigPath);
|
|
35
|
+
const policyStore = capturePolicyStore ?? new CapturePolicyStore(
|
|
36
|
+
capturePolicyPathForRuntime(runtimeConfigPath)
|
|
37
|
+
);
|
|
38
|
+
return new FederatedGraphApplication(resolved, {
|
|
39
|
+
fetchImpl,
|
|
40
|
+
capturePolicyStore: policyStore,
|
|
41
|
+
agentAccessPolicyStore: agentAccessPolicyStore ?? new AgentAccessPolicyStore(
|
|
42
|
+
agentAccessPolicyPathForRuntime(runtimeConfigPath)
|
|
43
|
+
),
|
|
44
|
+
consoleUrl: sourceConsoleUrl(runtimeConfigPath)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class FederatedGraphApplication {
|
|
49
|
+
constructor(config, {
|
|
50
|
+
fetchImpl = globalThis.fetch,
|
|
51
|
+
capturePolicyStore = new CapturePolicyStore(),
|
|
52
|
+
agentAccessPolicyStore = new AgentAccessPolicyStore(),
|
|
53
|
+
consoleUrl = sourceConsoleUrl(null),
|
|
54
|
+
projectPathResolver = resolvePersonalProjectPath
|
|
55
|
+
} = {}) {
|
|
56
|
+
this.graphiti = true;
|
|
57
|
+
this.config = config;
|
|
58
|
+
this.capturePolicyStore = capturePolicyStore;
|
|
59
|
+
this.agentAccessPolicyStore = agentAccessPolicyStore;
|
|
60
|
+
this.consoleUrl = consoleUrl;
|
|
61
|
+
this.projectPathResolver = projectPathResolver;
|
|
62
|
+
this.personal = new GraphitiProviderClient({
|
|
63
|
+
baseUrl: config.personal.providerUrl,
|
|
64
|
+
accessToken: config.personal.accessToken,
|
|
65
|
+
fetchImpl
|
|
66
|
+
});
|
|
67
|
+
this.workspaces = new Map(
|
|
68
|
+
config.workspaces.map((workspace) => {
|
|
69
|
+
const url = canonicalProviderUrl(workspace.providerUrl);
|
|
70
|
+
return [url, {
|
|
71
|
+
...workspace,
|
|
72
|
+
client: new GraphitiProviderClient({
|
|
73
|
+
baseUrl: url,
|
|
74
|
+
accessToken: workspace.accessToken,
|
|
75
|
+
fetchImpl
|
|
76
|
+
})
|
|
77
|
+
}];
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async captureSessionKnowledge(input) {
|
|
83
|
+
const capturePolicy = this.getCapturePolicy();
|
|
84
|
+
if (!capturePolicy.enabled) {
|
|
85
|
+
return {
|
|
86
|
+
route: 'disabled',
|
|
87
|
+
status: 'capture_disabled',
|
|
88
|
+
capturePolicy
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
assertNoCredentials(input);
|
|
92
|
+
const episode = providerEpisode(input);
|
|
93
|
+
if (input.targetKind === 'personal') {
|
|
94
|
+
if (input.spaceId !== this.config.personal.spaceId) {
|
|
95
|
+
throw new TypeError('Personal capture target must be the active personal space');
|
|
96
|
+
}
|
|
97
|
+
const result = await this.personal.commit({
|
|
98
|
+
space_id: input.spaceId,
|
|
99
|
+
personal_project_id: input.personalProjectId ?? null,
|
|
100
|
+
episode
|
|
101
|
+
});
|
|
102
|
+
return { route: 'personal', ...result };
|
|
103
|
+
}
|
|
104
|
+
if (episode.sensitivity !== 'normal') {
|
|
105
|
+
throw new TypeError('Private or restricted knowledge cannot enter a team-shared project queue');
|
|
106
|
+
}
|
|
107
|
+
assertPublicKnowledgeEligible(episode);
|
|
108
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
109
|
+
const draft = await this.personal.createPublicationDraft({
|
|
110
|
+
personal_space_id: this.config.personal.spaceId,
|
|
111
|
+
target_project_id: input.spaceId,
|
|
112
|
+
provider_url: workspace.providerUrl,
|
|
113
|
+
episode
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
route: 'personal_review',
|
|
117
|
+
status: draft.status,
|
|
118
|
+
draftId: draft.id,
|
|
119
|
+
projectId: input.spaceId,
|
|
120
|
+
providerUrl: workspace.providerUrl
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async getCollaborationPreferences({
|
|
125
|
+
personalProjectId = null,
|
|
126
|
+
projectPath = null,
|
|
127
|
+
limit = 100,
|
|
128
|
+
agentInvocation = false,
|
|
129
|
+
agentToolName = 'get_collaboration_preferences'
|
|
130
|
+
} = {}) {
|
|
131
|
+
const projectResolution = await this.#resolvePreferenceProject({
|
|
132
|
+
personalProjectId,
|
|
133
|
+
projectPath
|
|
134
|
+
});
|
|
135
|
+
const resolvedProjectId = projectResolution.personalProjectId;
|
|
136
|
+
const [result, queuedConflicts] = await Promise.all([
|
|
137
|
+
this.personal.collaborationPreferences(
|
|
138
|
+
this.config.personal.spaceId,
|
|
139
|
+
resolvedProjectId,
|
|
140
|
+
limit
|
|
141
|
+
),
|
|
142
|
+
this.personal.listPreferenceConflicts(
|
|
143
|
+
this.config.personal.spaceId,
|
|
144
|
+
'ai_pending',
|
|
145
|
+
limit
|
|
146
|
+
)
|
|
147
|
+
]);
|
|
148
|
+
const deferredConflicts = deferredPreferenceConflicts(
|
|
149
|
+
result,
|
|
150
|
+
queuedConflicts
|
|
151
|
+
);
|
|
152
|
+
if (agentInvocation) {
|
|
153
|
+
const items = [
|
|
154
|
+
...(result.global_preferences ?? []),
|
|
155
|
+
...(result.project_preferences ?? [])
|
|
156
|
+
];
|
|
157
|
+
await this.#recordAgentViews(items.map(({ id, item_kind: itemKind }) => ({
|
|
158
|
+
item_id: id,
|
|
159
|
+
item_kind: itemKind
|
|
160
|
+
})), agentToolName);
|
|
161
|
+
}
|
|
162
|
+
const applicationGuidance = {
|
|
163
|
+
apply: 'effective_preferences',
|
|
164
|
+
global_scope: 'Apply personal-global preferences in every user task.',
|
|
165
|
+
project_scope: resolvedProjectId
|
|
166
|
+
? `Also apply preferences scoped to the matched personal project ${resolvedProjectId}.`
|
|
167
|
+
: 'No exact personal project matched; do not apply project-scoped preferences.',
|
|
168
|
+
conflicts: 'Do not apply entries listed in conflicts until the user resolves them.',
|
|
169
|
+
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.',
|
|
170
|
+
authority: 'Human or authoritative-source confirmed preferences outrank agent-confirmed preferences. Agent-confirmed preferences are usable but lower priority and remain explicitly marked.',
|
|
171
|
+
pending: 'Pending preferences are available only through on-demand knowledge search; invalid and unrelated-project preferences are excluded. Automatic preference injection never counts as usage evidence.'
|
|
172
|
+
};
|
|
173
|
+
if (!agentInvocation) {
|
|
174
|
+
return {
|
|
175
|
+
...result,
|
|
176
|
+
deferred_conflicts: deferredConflicts,
|
|
177
|
+
application_guidance: applicationGuidance,
|
|
178
|
+
project_resolution: agentProjectResolution(projectResolution)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
effective_preferences: (result.effective_preferences ?? [])
|
|
183
|
+
.map(agentCollaborationPreference),
|
|
184
|
+
deferred_conflicts: deferredConflicts.map(agentDeferredPreferenceConflict),
|
|
185
|
+
application_guidance: applicationGuidance,
|
|
186
|
+
context: {
|
|
187
|
+
personal_space_id: result.personal_space_id,
|
|
188
|
+
personal_project_id: result.personal_project_id,
|
|
189
|
+
global_preference_count: (result.global_preferences ?? []).length,
|
|
190
|
+
project_preference_count: (result.project_preferences ?? []).length,
|
|
191
|
+
conflict_count: (result.conflicts ?? []).length,
|
|
192
|
+
ai_deferred_conflict_count: deferredConflicts.length,
|
|
193
|
+
overridden_global_count: (result.overridden_global_ids ?? []).length,
|
|
194
|
+
source_truncated: result.truncated === true,
|
|
195
|
+
project_resolution: agentProjectResolution(projectResolution)
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async #resolvePreferenceProject({ personalProjectId, projectPath }) {
|
|
201
|
+
if (projectPath !== undefined && projectPath !== null) {
|
|
202
|
+
const projects = await this.personal.listPersonalProjects(
|
|
203
|
+
this.config.personal.spaceId
|
|
204
|
+
);
|
|
205
|
+
const resolution = this.projectPathResolver(projectPath, projects);
|
|
206
|
+
if (
|
|
207
|
+
personalProjectId
|
|
208
|
+
&& resolution.personalProjectId
|
|
209
|
+
&& personalProjectId !== resolution.personalProjectId
|
|
210
|
+
) {
|
|
211
|
+
throw new TypeError(
|
|
212
|
+
'personalProjectId conflicts with the exact projectPath match'
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (personalProjectId) {
|
|
216
|
+
return {
|
|
217
|
+
status: 'matched',
|
|
218
|
+
basis: 'explicit_personal_project_id',
|
|
219
|
+
personalProjectId
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return resolution;
|
|
223
|
+
}
|
|
224
|
+
if (personalProjectId) {
|
|
225
|
+
return {
|
|
226
|
+
status: 'matched',
|
|
227
|
+
basis: 'explicit_personal_project_id',
|
|
228
|
+
personalProjectId
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
status: 'not_provided',
|
|
233
|
+
basis: null,
|
|
234
|
+
personalProjectId: null
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async searchKnowledge({
|
|
239
|
+
personalSpaceId,
|
|
240
|
+
query,
|
|
241
|
+
projectIds = [],
|
|
242
|
+
personalProjectId = null,
|
|
243
|
+
contextPersonalProjectIds = [],
|
|
244
|
+
personalProjectScope = 'bounded',
|
|
245
|
+
limit = 12,
|
|
246
|
+
includeHistorical = false,
|
|
247
|
+
includePending = true,
|
|
248
|
+
agentInvocation = false,
|
|
249
|
+
agentToolName = 'search_knowledge_graph'
|
|
250
|
+
}) {
|
|
251
|
+
if (personalSpaceId !== this.config.personal.spaceId) {
|
|
252
|
+
throw new TypeError('Search must use the active personal space');
|
|
253
|
+
}
|
|
254
|
+
if (!['bounded', 'all_local_confirmed'].includes(personalProjectScope)) {
|
|
255
|
+
throw new TypeError('Unknown personal project search scope');
|
|
256
|
+
}
|
|
257
|
+
const subscriptions = await this.personal.listSubscriptions(personalSpaceId);
|
|
258
|
+
const requestedProjects = new Set(projectIds);
|
|
259
|
+
const selectedSubscriptions = subscriptions.filter(({ project_id: projectId }) =>
|
|
260
|
+
requestedProjects.has(projectId)
|
|
261
|
+
);
|
|
262
|
+
if (selectedSubscriptions.length !== requestedProjects.size) {
|
|
263
|
+
throw new TypeError('Every selected project must be an active subscription');
|
|
264
|
+
}
|
|
265
|
+
const selectedPersonalProjectIds = personalProjectScope === 'all_local_confirmed'
|
|
266
|
+
? personalContextProjectIds(
|
|
267
|
+
null,
|
|
268
|
+
(await this.personal.listPersonalProjects(personalSpaceId))
|
|
269
|
+
.map(({ project_id: projectId }) => projectId)
|
|
270
|
+
)
|
|
271
|
+
: personalContextProjectIds(personalProjectId, contextPersonalProjectIds);
|
|
272
|
+
const personalSearches = personalProjectSearchBatches(selectedPersonalProjectIds)
|
|
273
|
+
.map((personalProjectIds, index) => this.personal.search({
|
|
274
|
+
space_ids: [personalSpaceId],
|
|
275
|
+
query,
|
|
276
|
+
limit,
|
|
277
|
+
include_historical: includeHistorical,
|
|
278
|
+
include_exploratory: includePending,
|
|
279
|
+
personal_project_ids: personalProjectIds,
|
|
280
|
+
active_personal_project_id: (
|
|
281
|
+
personalProjectScope === 'bounded' &&
|
|
282
|
+
personalProjectId &&
|
|
283
|
+
personalProjectIds.includes(personalProjectId)
|
|
284
|
+
) ? personalProjectId : null,
|
|
285
|
+
inherit_project_knowledge: personalProjectScope === 'bounded',
|
|
286
|
+
include_personal_global: index === 0
|
|
287
|
+
}));
|
|
288
|
+
const grouped = groupSubscriptions(selectedSubscriptions);
|
|
289
|
+
const workspaceSearches = [...grouped.entries()].map(([providerUrl, items]) => {
|
|
290
|
+
const projectIdsForProvider = items.map(({ project_id: projectId }) => projectId);
|
|
291
|
+
return {
|
|
292
|
+
projectIds: projectIdsForProvider,
|
|
293
|
+
operation: (async () => {
|
|
294
|
+
const workspace = this.#workspace(providerUrl);
|
|
295
|
+
const result = await workspace.client.search({
|
|
296
|
+
space_ids: projectIdsForProvider,
|
|
297
|
+
query,
|
|
298
|
+
limit,
|
|
299
|
+
include_historical: includeHistorical,
|
|
300
|
+
include_exploratory: includePending
|
|
301
|
+
});
|
|
302
|
+
return scopedSearchItems(result, 'project', workspace.providerUrl);
|
|
303
|
+
})()
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
const searchResults = await Promise.allSettled([
|
|
307
|
+
...personalSearches,
|
|
308
|
+
...workspaceSearches.map(({ operation }) => operation)
|
|
309
|
+
]);
|
|
310
|
+
const personalSettlements = searchResults.slice(0, personalSearches.length);
|
|
311
|
+
const personalFailure = personalSettlements.find(({ status }) => status === 'rejected');
|
|
312
|
+
if (personalFailure) throw personalFailure.reason;
|
|
313
|
+
const personalItems = personalSettlements
|
|
314
|
+
.map(({ value }) => scopedSearchItems(
|
|
315
|
+
value,
|
|
316
|
+
'personal',
|
|
317
|
+
null,
|
|
318
|
+
this.config.personal.spaceId
|
|
319
|
+
));
|
|
320
|
+
const successfulProjectIds = new Set();
|
|
321
|
+
const failedProjectIds = new Set();
|
|
322
|
+
const projects = [];
|
|
323
|
+
searchResults.slice(personalSearches.length).forEach((settlement, index) => {
|
|
324
|
+
const { projectIds: providerProjectIds } = workspaceSearches[index];
|
|
325
|
+
if (settlement.status === 'fulfilled') {
|
|
326
|
+
projects.push(settlement.value);
|
|
327
|
+
providerProjectIds.forEach((projectId) => successfulProjectIds.add(projectId));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
providerProjectIds.forEach((projectId) => failedProjectIds.add(projectId));
|
|
331
|
+
});
|
|
332
|
+
const requestedProjectIds = [...requestedProjects];
|
|
333
|
+
const searchedProjectIds = requestedProjectIds.filter((projectId) =>
|
|
334
|
+
successfulProjectIds.has(projectId)
|
|
335
|
+
);
|
|
336
|
+
const unavailableProjectIds = requestedProjectIds.filter((projectId) =>
|
|
337
|
+
failedProjectIds.has(projectId)
|
|
338
|
+
);
|
|
339
|
+
const rankedSources = [...personalItems, ...projects];
|
|
340
|
+
const facts = rankedSearchItems(rankedSources, 'facts', limit);
|
|
341
|
+
const entities = rankedSearchItems(rankedSources, 'entities', limit);
|
|
342
|
+
const sourceMarker = createFuliSourceMarker({
|
|
343
|
+
consoleUrl: this.consoleUrl,
|
|
344
|
+
facts,
|
|
345
|
+
entities
|
|
346
|
+
});
|
|
347
|
+
const noMatchSourceMarker = createFuliSourceMarker({
|
|
348
|
+
consoleUrl: this.consoleUrl,
|
|
349
|
+
facts: [],
|
|
350
|
+
entities: []
|
|
351
|
+
});
|
|
352
|
+
if (agentInvocation) {
|
|
353
|
+
await this.#recordAgentViews([
|
|
354
|
+
...facts
|
|
355
|
+
.filter(({ scope }) => scope === 'personal')
|
|
356
|
+
.map(({ id }) => ({ item_id: id, item_kind: 'relationship' })),
|
|
357
|
+
...entities
|
|
358
|
+
.filter(({ scope }) => scope === 'personal')
|
|
359
|
+
.map(({ id }) => ({ item_id: id, item_kind: 'entity' }))
|
|
360
|
+
], agentToolName);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
query,
|
|
364
|
+
sourceMarker,
|
|
365
|
+
noMatchSourceMarker,
|
|
366
|
+
retrievalGuidance: retrievalGuidanceForScope(personalProjectScope),
|
|
367
|
+
facts,
|
|
368
|
+
entities,
|
|
369
|
+
personalGlobalIncluded: true,
|
|
370
|
+
personalProjectScope,
|
|
371
|
+
searchedPersonalProjectIds: selectedPersonalProjectIds,
|
|
372
|
+
requestedProjectIds,
|
|
373
|
+
searchedProjectIds,
|
|
374
|
+
failedProjectIds: unavailableProjectIds,
|
|
375
|
+
partial: unavailableProjectIds.length > 0,
|
|
376
|
+
subscriptions: selectedSubscriptions
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async getKnowledgeGraph({
|
|
381
|
+
spaceId,
|
|
382
|
+
providerUrl = null,
|
|
383
|
+
personalProjectId = null,
|
|
384
|
+
limit = 500,
|
|
385
|
+
agentInvocation = false,
|
|
386
|
+
agentToolName = 'get_knowledge_graph'
|
|
387
|
+
}) {
|
|
388
|
+
if (!providerUrl) {
|
|
389
|
+
if (spaceId !== this.config.personal.spaceId) {
|
|
390
|
+
throw new TypeError('A configured providerUrl is required for a team-shared project graph');
|
|
391
|
+
}
|
|
392
|
+
const result = await this.personal.graph(spaceId, limit, personalProjectId);
|
|
393
|
+
if (agentInvocation) {
|
|
394
|
+
await this.#recordAgentViews([
|
|
395
|
+
...result.nodes.map(({ id }) => ({ item_id: id, item_kind: 'entity' })),
|
|
396
|
+
...result.edges.map(({ id }) => ({ item_id: id, item_kind: 'relationship' }))
|
|
397
|
+
], agentToolName);
|
|
398
|
+
}
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
if (personalProjectId) {
|
|
402
|
+
throw new TypeError('Personal project scope cannot be used with a team-shared provider');
|
|
403
|
+
}
|
|
404
|
+
return this.#workspace(providerUrl).client.graph(spaceId, limit);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async recordKnowledgeUsage({
|
|
408
|
+
personalSpaceId,
|
|
409
|
+
taskId,
|
|
410
|
+
sessionId = null,
|
|
411
|
+
toolName = null,
|
|
412
|
+
items
|
|
413
|
+
}) {
|
|
414
|
+
this.#assertActivePersonalSpace(personalSpaceId);
|
|
415
|
+
return this.personal.recordKnowledgeUsage({
|
|
416
|
+
personal_space_id: personalSpaceId,
|
|
417
|
+
task_id: taskId,
|
|
418
|
+
session_id: sessionId,
|
|
419
|
+
tool_name: toolName,
|
|
420
|
+
items: items.map((item) => ({
|
|
421
|
+
item_id: item.itemId,
|
|
422
|
+
item_kind: item.itemKind,
|
|
423
|
+
use_kind: item.useKind
|
|
424
|
+
}))
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async reviseKnowledgeItem(input) {
|
|
429
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
430
|
+
assertSafeKnowledgeMutation(input);
|
|
431
|
+
const body = {
|
|
432
|
+
personal_space_id: input.personalSpaceId,
|
|
433
|
+
personal_project_id: input.personalProjectId ?? null,
|
|
434
|
+
item_kind: input.itemKind,
|
|
435
|
+
action: input.action,
|
|
436
|
+
reason: input.reason,
|
|
437
|
+
name: input.name ?? null,
|
|
438
|
+
summary: input.summary ?? null,
|
|
439
|
+
fact: input.fact ?? null,
|
|
440
|
+
operation_actor: input.operationActor ?? 'agent'
|
|
441
|
+
};
|
|
442
|
+
if (input.currentQuadrant !== undefined) {
|
|
443
|
+
body.current_quadrant = input.currentQuadrant;
|
|
444
|
+
}
|
|
445
|
+
if (input.originQuadrant !== undefined) {
|
|
446
|
+
body.origin_quadrant = input.originQuadrant;
|
|
447
|
+
}
|
|
448
|
+
if (input.epistemicStatus !== undefined) {
|
|
449
|
+
body.epistemic_status = input.epistemicStatus;
|
|
450
|
+
}
|
|
451
|
+
if (input.confirmationStatus !== undefined) {
|
|
452
|
+
body.confirmation_status = input.confirmationStatus;
|
|
453
|
+
}
|
|
454
|
+
if (input.confirmationBasis !== undefined) {
|
|
455
|
+
body.confirmation_basis = providerConfirmationBasis(input.confirmationBasis);
|
|
456
|
+
}
|
|
457
|
+
if (input.reasoningSummary !== undefined) {
|
|
458
|
+
body.reasoning_summary = input.reasoningSummary;
|
|
459
|
+
}
|
|
460
|
+
if (input.profileAspect !== undefined) {
|
|
461
|
+
body.profile_aspect = input.profileAspect;
|
|
462
|
+
}
|
|
463
|
+
if (input.inheritanceMode !== undefined) {
|
|
464
|
+
body.inheritance_mode = input.inheritanceMode;
|
|
465
|
+
}
|
|
466
|
+
if (input.inheritedProjectIds !== undefined) {
|
|
467
|
+
body.inherited_project_ids = input.inheritedProjectIds;
|
|
468
|
+
}
|
|
469
|
+
if (input.replacementItemId !== undefined) {
|
|
470
|
+
body.replacement_item_id = input.replacementItemId;
|
|
471
|
+
}
|
|
472
|
+
if (input.replacementItemKind !== undefined) {
|
|
473
|
+
body.replacement_item_kind = input.replacementItemKind;
|
|
474
|
+
}
|
|
475
|
+
return this.personal.reviseKnowledgeItem(input.itemId, body);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async listPreferenceConflicts({
|
|
479
|
+
personalSpaceId,
|
|
480
|
+
status = null,
|
|
481
|
+
limit = 500
|
|
482
|
+
}) {
|
|
483
|
+
this.#assertActivePersonalSpace(personalSpaceId);
|
|
484
|
+
return this.personal.listPreferenceConflicts(
|
|
485
|
+
personalSpaceId,
|
|
486
|
+
status,
|
|
487
|
+
limit
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async deferPreferenceConflict(input) {
|
|
492
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
493
|
+
return this.personal.deferPreferenceConflict({
|
|
494
|
+
personal_space_id: input.personalSpaceId,
|
|
495
|
+
conflict_id: input.conflictId,
|
|
496
|
+
preference_key: input.preferenceKey,
|
|
497
|
+
preference_scope: input.preferenceScope,
|
|
498
|
+
preference_project_id: input.preferenceProjectId ?? null,
|
|
499
|
+
left_item_id: input.leftItemId,
|
|
500
|
+
left_item_kind: input.leftItemKind,
|
|
501
|
+
right_item_id: input.rightItemId,
|
|
502
|
+
right_item_kind: input.rightItemKind,
|
|
503
|
+
reason: input.reason,
|
|
504
|
+
operation_actor: input.operationActor ?? 'human'
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async resolveDeferredPreferenceConflict(input) {
|
|
509
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
510
|
+
assertSafeKnowledgeMutation(input);
|
|
511
|
+
return this.personal.resolvePreferenceConflict(input.conflictId, {
|
|
512
|
+
personal_space_id: input.personalSpaceId,
|
|
513
|
+
resolution: input.resolution,
|
|
514
|
+
reason: input.reason,
|
|
515
|
+
canonical_item_id: input.canonicalItemId ?? null,
|
|
516
|
+
merged_instruction: input.mergedInstruction ?? null,
|
|
517
|
+
split_item_id: input.splitItemId ?? null,
|
|
518
|
+
split_project_id: input.splitProjectId ?? null,
|
|
519
|
+
operation_actor: input.operationActor ?? 'agent'
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async completePreferenceConflict(input) {
|
|
524
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
525
|
+
return this.personal.completePreferenceConflict(input.conflictId, {
|
|
526
|
+
personal_space_id: input.personalSpaceId,
|
|
527
|
+
resolution: input.resolution,
|
|
528
|
+
reason: input.reason,
|
|
529
|
+
operation_actor: input.operationActor ?? 'human'
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async confirmKnowledgeBatch(input) {
|
|
534
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
535
|
+
assertSafeKnowledgeMutation(input);
|
|
536
|
+
return this.personal.confirmKnowledgeBatch({
|
|
537
|
+
personal_space_id: input.personalSpaceId,
|
|
538
|
+
group_kind: input.groupKind,
|
|
539
|
+
group_value: input.groupValue,
|
|
540
|
+
reason: input.reason,
|
|
541
|
+
confirmer: providerConfirmationActor(input.confirmer),
|
|
542
|
+
operation_actor: input.operationActor ?? 'agent',
|
|
543
|
+
items: input.items.map((item) => ({
|
|
544
|
+
item_id: item.itemId,
|
|
545
|
+
item_kind: item.itemKind,
|
|
546
|
+
existence_reason: item.existenceReason,
|
|
547
|
+
quadrant_reason: item.quadrantReason,
|
|
548
|
+
proposed_by: providerConfirmationActor(item.proposedBy)
|
|
549
|
+
}))
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async reassignKnowledgeItem(input) {
|
|
554
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
555
|
+
return this.personal.reassignKnowledgeItem(input.itemId, {
|
|
556
|
+
personal_space_id: input.personalSpaceId,
|
|
557
|
+
item_kind: input.itemKind,
|
|
558
|
+
target_project_id: input.targetProjectId,
|
|
559
|
+
reason: input.reason,
|
|
560
|
+
operation_actor: input.operationActor ?? 'agent'
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async setPersonalPreferenceScope(input) {
|
|
565
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
566
|
+
assertSafeKnowledgeMutation(input);
|
|
567
|
+
return this.personal.setPreferenceScope(input.itemId, {
|
|
568
|
+
personal_space_id: input.personalSpaceId,
|
|
569
|
+
item_kind: input.itemKind,
|
|
570
|
+
scope: input.scope,
|
|
571
|
+
project_id: input.scope === 'project' ? input.projectId : null,
|
|
572
|
+
reason: input.reason,
|
|
573
|
+
operation_actor: input.operationActor ?? 'agent'
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async previewKnowledgeProjectAction(input) {
|
|
578
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
579
|
+
return this.personal.previewKnowledgeProjectAction(input.itemId, {
|
|
580
|
+
personal_space_id: input.personalSpaceId,
|
|
581
|
+
item_kind: input.itemKind,
|
|
582
|
+
mode: input.mode ?? 'existing',
|
|
583
|
+
target_project_id: input.targetProjectId ?? null,
|
|
584
|
+
new_project_id: input.newProjectId ?? null,
|
|
585
|
+
new_project_name: input.newProjectName ?? null,
|
|
586
|
+
new_project_purpose: input.newProjectPurpose ?? null,
|
|
587
|
+
keep_source_relation: input.keepSourceRelation ?? true,
|
|
588
|
+
relation_type: input.relationType ?? 'RELATED_TO',
|
|
589
|
+
conflict_resolution: input.conflictResolution ?? 'defer',
|
|
590
|
+
reason: input.reason ?? null
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async applyKnowledgeProjectAction(input) {
|
|
595
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
596
|
+
assertSafeKnowledgeMutation(input);
|
|
597
|
+
return this.personal.applyKnowledgeProjectAction(input.itemId, {
|
|
598
|
+
personal_space_id: input.personalSpaceId,
|
|
599
|
+
item_kind: input.itemKind,
|
|
600
|
+
mode: input.mode,
|
|
601
|
+
target_project_id: input.targetProjectId ?? null,
|
|
602
|
+
new_project_id: input.newProjectId ?? null,
|
|
603
|
+
new_project_name: input.newProjectName ?? null,
|
|
604
|
+
new_project_purpose: input.newProjectPurpose ?? null,
|
|
605
|
+
keep_source_relation: input.keepSourceRelation ?? true,
|
|
606
|
+
relation_type: input.relationType ?? 'RELATED_TO',
|
|
607
|
+
conflict_resolution: input.conflictResolution ?? 'defer',
|
|
608
|
+
reason: input.reason,
|
|
609
|
+
operation_actor: input.operationActor ?? 'agent'
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async searchHumanChanges({
|
|
614
|
+
personalSpaceId,
|
|
615
|
+
query = '',
|
|
616
|
+
status = 'all',
|
|
617
|
+
limit = 50,
|
|
618
|
+
agentInvocation = false,
|
|
619
|
+
agentToolName = 'search_human_knowledge_changes'
|
|
620
|
+
}) {
|
|
621
|
+
this.#assertActivePersonalSpace(personalSpaceId);
|
|
622
|
+
const result = await this.personal.searchHumanChanges({
|
|
623
|
+
personal_space_id: personalSpaceId,
|
|
624
|
+
query,
|
|
625
|
+
status,
|
|
626
|
+
limit
|
|
627
|
+
});
|
|
628
|
+
if (agentInvocation) {
|
|
629
|
+
await this.#recordAgentViews(
|
|
630
|
+
result.items.map(({ item_id: itemId, item_kind: itemKind }) => ({
|
|
631
|
+
item_id: itemId,
|
|
632
|
+
item_kind: itemKind
|
|
633
|
+
})),
|
|
634
|
+
agentToolName
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
return result;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
async reviewHumanChange(input) {
|
|
641
|
+
this.#assertActivePersonalSpace(input.personalSpaceId);
|
|
642
|
+
assertSafeKnowledgeMutation(input);
|
|
643
|
+
return this.personal.reviewHumanChange(input.itemId, {
|
|
644
|
+
personal_space_id: input.personalSpaceId,
|
|
645
|
+
item_kind: input.itemKind,
|
|
646
|
+
human_change_version: input.humanChangeVersion,
|
|
647
|
+
conflict_check: input.conflictCheck,
|
|
648
|
+
classification_check: input.classificationCheck,
|
|
649
|
+
note: input.note
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async listKnowledgeSpaces() {
|
|
654
|
+
const [personalSpaces, personalProjects, subscriptions, ...workspaceSpaces] = await Promise.all([
|
|
655
|
+
this.personal.listSpaces(),
|
|
656
|
+
this.personal.listPersonalProjects(this.config.personal.spaceId),
|
|
657
|
+
this.personal.listSubscriptions(this.config.personal.spaceId),
|
|
658
|
+
...[...this.workspaces.values()].map(async (workspace) => ({
|
|
659
|
+
providerUrl: workspace.providerUrl,
|
|
660
|
+
principalId: workspace.principalId,
|
|
661
|
+
spaces: await safely(() => workspace.client.listSpaces(), [])
|
|
662
|
+
}))
|
|
663
|
+
]);
|
|
664
|
+
return {
|
|
665
|
+
activePersonalSpaceId: this.config.personal.spaceId,
|
|
666
|
+
personalSpaces,
|
|
667
|
+
personalProjects,
|
|
668
|
+
projects: workspaceSpaces.flatMap(({ providerUrl, principalId, spaces }) =>
|
|
669
|
+
spaces.map((space) => ({
|
|
670
|
+
...space,
|
|
671
|
+
providerUrl,
|
|
672
|
+
isOwner: space.owner_id === principalId
|
|
673
|
+
}))
|
|
674
|
+
),
|
|
675
|
+
subscriptions
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async subscribePublicProject(input) {
|
|
680
|
+
if (input.personalSpaceId !== this.config.personal.spaceId) {
|
|
681
|
+
throw new TypeError('Subscription must belong to the active personal space');
|
|
682
|
+
}
|
|
683
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
684
|
+
const accessible = await workspace.client.listSpaces();
|
|
685
|
+
if (!accessible.some(({ id }) => id === input.projectId)) {
|
|
686
|
+
throw new TypeError('Public project is not accessible with the configured identity');
|
|
687
|
+
}
|
|
688
|
+
return this.personal.subscribe({
|
|
689
|
+
personal_space_id: input.personalSpaceId,
|
|
690
|
+
project_id: input.projectId,
|
|
691
|
+
provider_url: workspace.providerUrl,
|
|
692
|
+
project_name: input.projectName
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async unsubscribePublicProject(input) {
|
|
697
|
+
if (input.personalSpaceId !== this.config.personal.spaceId) {
|
|
698
|
+
throw new TypeError('Subscription must belong to the active personal space');
|
|
699
|
+
}
|
|
700
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
701
|
+
return this.personal.unsubscribe(
|
|
702
|
+
input.personalSpaceId,
|
|
703
|
+
input.projectId,
|
|
704
|
+
workspace.providerUrl
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async publishPersonalProject(input) {
|
|
709
|
+
if (input.personalSpaceId !== this.config.personal.spaceId) {
|
|
710
|
+
throw new TypeError('Published project must belong to the active personal space');
|
|
711
|
+
}
|
|
712
|
+
const localProjectId = String(input.localProjectId ?? '').trim();
|
|
713
|
+
if (!localProjectId) throw new TypeError('Local project ID is required');
|
|
714
|
+
const releaseVersion = String(input.releaseVersion ?? '').trim();
|
|
715
|
+
const updateSummary = String(input.updateSummary ?? '').trim();
|
|
716
|
+
if (!/^[0-9A-Za-z][0-9A-Za-z._-]{0,63}$/.test(releaseVersion)) {
|
|
717
|
+
throw new TypeError('Release version must use letters, numbers, dots, underscores, or hyphens');
|
|
718
|
+
}
|
|
719
|
+
if (!updateSummary || updateSummary.length > 4096) {
|
|
720
|
+
throw new TypeError('Release update summary is required and must be at most 4096 characters');
|
|
721
|
+
}
|
|
722
|
+
const localProject = await this.personal.getPersonalProject(
|
|
723
|
+
input.personalSpaceId,
|
|
724
|
+
localProjectId
|
|
725
|
+
);
|
|
726
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
727
|
+
const project = await workspace.client.createSpace({
|
|
728
|
+
name: localProject.profile.name,
|
|
729
|
+
kind: 'project',
|
|
730
|
+
description: localProject.profile.purpose?.slice(0, 2000) ?? null,
|
|
731
|
+
publication_key: localProject.publication_key,
|
|
732
|
+
profile: providerProjectProfile(localProject.profile),
|
|
733
|
+
release: {
|
|
734
|
+
version: releaseVersion,
|
|
735
|
+
summary: updateSummary
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
const subscription = await this.personal.subscribe({
|
|
739
|
+
personal_space_id: input.personalSpaceId,
|
|
740
|
+
project_id: project.id,
|
|
741
|
+
provider_url: workspace.providerUrl,
|
|
742
|
+
project_name: project.name
|
|
743
|
+
});
|
|
744
|
+
return { project: { ...project, providerUrl: workspace.providerUrl }, subscription };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async listProjectReleases(input) {
|
|
748
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
749
|
+
return {
|
|
750
|
+
projectId: input.projectId,
|
|
751
|
+
providerUrl: workspace.providerUrl,
|
|
752
|
+
releases: await workspace.client.listProjectReleases(input.projectId)
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async deletePublicProject(input) {
|
|
757
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
758
|
+
const result = await workspace.client.deleteProject(input.projectId);
|
|
759
|
+
await this.personal.unsubscribe(
|
|
760
|
+
this.config.personal.spaceId,
|
|
761
|
+
input.projectId,
|
|
762
|
+
workspace.providerUrl
|
|
763
|
+
);
|
|
764
|
+
return { ...result, providerUrl: workspace.providerUrl };
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
async upsertPersonalProject(input) {
|
|
768
|
+
if (input.personalSpaceId !== this.config.personal.spaceId) {
|
|
769
|
+
throw new TypeError('Personal project must belong to the active personal space');
|
|
770
|
+
}
|
|
771
|
+
return this.personal.upsertPersonalProject({
|
|
772
|
+
personal_space_id: input.personalSpaceId,
|
|
773
|
+
project_id: input.projectId,
|
|
774
|
+
profile: providerProjectProfile(input.profile)
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
async listPersonalProjects(input = {}) {
|
|
779
|
+
const personalSpaceId = input.personalSpaceId ?? this.config.personal.spaceId;
|
|
780
|
+
if (personalSpaceId !== this.config.personal.spaceId) {
|
|
781
|
+
throw new TypeError('Personal projects must use the active personal space');
|
|
782
|
+
}
|
|
783
|
+
return this.personal.listPersonalProjects(personalSpaceId);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async createProjectRelation(input) {
|
|
787
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
788
|
+
return workspace.client.createProjectRelation(input.sourceProjectId, {
|
|
789
|
+
target_project_id: input.targetProjectId,
|
|
790
|
+
relation_type: input.relationType,
|
|
791
|
+
note: input.note ?? null
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
async listProjectRelations(input) {
|
|
796
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
797
|
+
return {
|
|
798
|
+
projectId: input.projectId,
|
|
799
|
+
providerUrl: workspace.providerUrl,
|
|
800
|
+
relations: await workspace.client.listProjectRelations(input.projectId)
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async reviewProjectRelation(input) {
|
|
805
|
+
const workspace = this.#workspace(input.providerUrl);
|
|
806
|
+
return workspace.client.decideProjectRelation(
|
|
807
|
+
input.targetProjectId,
|
|
808
|
+
input.relationId,
|
|
809
|
+
{ decision: input.decision, note: input.note ?? null }
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async listPersonalReviewQueue(input = {}) {
|
|
814
|
+
const personalSpaceId = input.personalSpaceId ?? this.config.personal.spaceId;
|
|
815
|
+
if (personalSpaceId !== this.config.personal.spaceId) {
|
|
816
|
+
throw new TypeError('Personal review must use the active personal space');
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
personalSpaceId,
|
|
820
|
+
drafts: await this.personal.listPublicationDrafts(
|
|
821
|
+
personalSpaceId,
|
|
822
|
+
input.status ?? 'pending'
|
|
823
|
+
)
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async reviewPersonalDraft(input) {
|
|
828
|
+
const personalSpaceId = this.config.personal.spaceId;
|
|
829
|
+
if (!['submit_public', 'keep_personal', 'ignore'].includes(input.decision)) {
|
|
830
|
+
throw new TypeError('Unknown personal draft decision');
|
|
831
|
+
}
|
|
832
|
+
if (input.decision !== 'submit_public') {
|
|
833
|
+
return this.personal.decidePublicationDraft(personalSpaceId, input.draftId, {
|
|
834
|
+
decision: input.decision,
|
|
835
|
+
shared_proposal_id: null
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
const draft = await this.personal.getPublicationDraft(personalSpaceId, input.draftId);
|
|
839
|
+
if (draft.status !== 'pending') throw new TypeError('Publication draft is not pending');
|
|
840
|
+
if (draft.episode.sensitivity !== 'normal') {
|
|
841
|
+
throw new TypeError('Private or restricted knowledge cannot be submitted publicly');
|
|
842
|
+
}
|
|
843
|
+
assertPublicKnowledgeEligible(draft.episode);
|
|
844
|
+
const workspace = this.#workspace(draft.provider_url);
|
|
845
|
+
const proposal = await workspace.client.createProposal(draft.target_project_id, {
|
|
846
|
+
episode: draft.episode
|
|
847
|
+
});
|
|
848
|
+
return this.personal.decidePublicationDraft(personalSpaceId, input.draftId, {
|
|
849
|
+
decision: 'submit_public',
|
|
850
|
+
shared_proposal_id: proposal.id
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
async listReviewQueue({ projectId, providerUrl, status = 'pending' }) {
|
|
855
|
+
const workspace = this.#workspace(providerUrl);
|
|
856
|
+
return {
|
|
857
|
+
projectId,
|
|
858
|
+
providerUrl: workspace.providerUrl,
|
|
859
|
+
proposals: await workspace.client.listProposals(projectId, status)
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
async reviewProposal({ projectId, providerUrl, proposalId, decision, note = null }) {
|
|
864
|
+
const workspace = this.#workspace(providerUrl);
|
|
865
|
+
return workspace.client.decideProposal(projectId, proposalId, { decision, note });
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
async getGraphitiStatus() {
|
|
869
|
+
const [personal, ...workspaces] = await Promise.all([
|
|
870
|
+
this.personal.health(),
|
|
871
|
+
...[...this.workspaces.values()].map(async (workspace) => {
|
|
872
|
+
try {
|
|
873
|
+
return {
|
|
874
|
+
providerUrl: workspace.providerUrl,
|
|
875
|
+
...(await workspace.client.health())
|
|
876
|
+
};
|
|
877
|
+
} catch {
|
|
878
|
+
return { providerUrl: workspace.providerUrl, status: 'unavailable' };
|
|
879
|
+
}
|
|
880
|
+
})
|
|
881
|
+
]);
|
|
882
|
+
return { personal, workspaces };
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
getCapturePolicy() {
|
|
886
|
+
return this.capturePolicyStore.read();
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
updateCapturePolicy(input) {
|
|
890
|
+
return this.capturePolicyStore.update(input);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
getAgentAccessPolicy() {
|
|
894
|
+
return this.agentAccessPolicyStore.read();
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
updateAgentAccessPolicy(input) {
|
|
898
|
+
return this.agentAccessPolicyStore.update(input);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
async state() {
|
|
902
|
+
const [spaces, providers] = await Promise.all([
|
|
903
|
+
this.listKnowledgeSpaces(),
|
|
904
|
+
this.getGraphitiStatus()
|
|
905
|
+
]);
|
|
906
|
+
const configured = this.workspaces.size > 0;
|
|
907
|
+
const connected = providers.workspaces.some(({ status }) => status === 'ready');
|
|
908
|
+
const publicStatus = configured ? (connected ? 'ready' : 'unavailable') : 'not_connected';
|
|
909
|
+
return {
|
|
910
|
+
mode: configured ? (connected ? 'connected' : 'degraded') : 'personal_only',
|
|
911
|
+
...spaces,
|
|
912
|
+
providers,
|
|
913
|
+
capturePolicy: this.getCapturePolicy(),
|
|
914
|
+
agentAccessPolicy: this.getAgentAccessPolicy(),
|
|
915
|
+
publicProvider: { configured, status: publicStatus },
|
|
916
|
+
capabilities: {
|
|
917
|
+
browsePublicProjects: connected,
|
|
918
|
+
publishProject: connected,
|
|
919
|
+
submitKnowledge: connected,
|
|
920
|
+
subscribeProject: connected,
|
|
921
|
+
reviewProposals: connected && spaces.projects.some(({ role }) => role === 'maintainer')
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
close() {}
|
|
927
|
+
|
|
928
|
+
async #recordAgentViews(items, toolName) {
|
|
929
|
+
const unique = new Map(items.map((item) => [
|
|
930
|
+
`${item.item_kind}:${item.item_id}`,
|
|
931
|
+
item
|
|
932
|
+
]));
|
|
933
|
+
const values = [...unique.values()];
|
|
934
|
+
for (let index = 0; index < values.length; index += 200) {
|
|
935
|
+
await this.personal.recordAgentViews({
|
|
936
|
+
personal_space_id: this.config.personal.spaceId,
|
|
937
|
+
tool_name: toolName,
|
|
938
|
+
items: values.slice(index, index + 200)
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
#assertActivePersonalSpace(personalSpaceId) {
|
|
944
|
+
if (personalSpaceId !== this.config.personal.spaceId) {
|
|
945
|
+
throw new TypeError('Knowledge changes must use the active personal space');
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
#workspace(providerUrl) {
|
|
950
|
+
const requested = providerUrl
|
|
951
|
+
? canonicalProviderUrl(providerUrl)
|
|
952
|
+
: (this.workspaces.size === 1 ? [...this.workspaces.keys()][0] : null);
|
|
953
|
+
const workspace = requested ? this.workspaces.get(requested) : null;
|
|
954
|
+
if (!workspace) throw new TypeError('Public project provider is not configured');
|
|
955
|
+
return { ...workspace, providerUrl: requested };
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function agentCollaborationPreference(item) {
|
|
960
|
+
const preference = {
|
|
961
|
+
instruction: item.instruction ?? '',
|
|
962
|
+
preference_key: item.preference_key ?? item.key ?? item.id,
|
|
963
|
+
title: item.title ?? item.preference_key ?? item.key ?? item.id,
|
|
964
|
+
profile_aspect: item.profile_aspect ?? null,
|
|
965
|
+
preference_scope: item.preference_scope ?? 'global',
|
|
966
|
+
confirmation_status: item.confirmation_status ?? 'confirmed'
|
|
967
|
+
};
|
|
968
|
+
if (item.preference_project_id) {
|
|
969
|
+
preference.preference_project_id = item.preference_project_id;
|
|
970
|
+
}
|
|
971
|
+
return preference;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function deferredPreferenceConflicts(result, queuedConflicts) {
|
|
975
|
+
const items = [
|
|
976
|
+
...(result.global_preferences ?? []),
|
|
977
|
+
...(result.project_preferences ?? [])
|
|
978
|
+
];
|
|
979
|
+
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
980
|
+
const activeConflictPairs = new Set(
|
|
981
|
+
(result.conflicts ?? []).flatMap((conflict) => {
|
|
982
|
+
const ids = conflict.item_ids ?? [];
|
|
983
|
+
const pairs = [];
|
|
984
|
+
for (let left = 0; left < ids.length; left += 1) {
|
|
985
|
+
for (let right = left + 1; right < ids.length; right += 1) {
|
|
986
|
+
pairs.push(preferenceConflictPairKey(ids[left], ids[right]));
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return pairs;
|
|
990
|
+
})
|
|
991
|
+
);
|
|
992
|
+
return (queuedConflicts ?? [])
|
|
993
|
+
.filter((conflict) =>
|
|
994
|
+
conflict.status === 'ai_pending' &&
|
|
995
|
+
activeConflictPairs.has(preferenceConflictPairKey(
|
|
996
|
+
conflict.left_item_id,
|
|
997
|
+
conflict.right_item_id
|
|
998
|
+
)) &&
|
|
999
|
+
itemById.has(conflict.left_item_id) &&
|
|
1000
|
+
itemById.has(conflict.right_item_id)
|
|
1001
|
+
)
|
|
1002
|
+
.map((conflict) => ({
|
|
1003
|
+
...conflict,
|
|
1004
|
+
left: itemById.get(conflict.left_item_id),
|
|
1005
|
+
right: itemById.get(conflict.right_item_id)
|
|
1006
|
+
}));
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function agentDeferredPreferenceConflict(conflict) {
|
|
1010
|
+
return {
|
|
1011
|
+
id: conflict.id,
|
|
1012
|
+
preference_key: conflict.preference_key,
|
|
1013
|
+
preference_scope: conflict.preference_scope,
|
|
1014
|
+
...(conflict.preference_project_id
|
|
1015
|
+
? { preference_project_id: conflict.preference_project_id }
|
|
1016
|
+
: {}),
|
|
1017
|
+
status: conflict.status,
|
|
1018
|
+
deferred_at: conflict.deferred_at,
|
|
1019
|
+
reason: conflict.reason,
|
|
1020
|
+
left: agentConflictPreference(conflict.left),
|
|
1021
|
+
right: agentConflictPreference(conflict.right),
|
|
1022
|
+
required_action: 'Resolve this conflict before using either side when it is relevant to the current task.',
|
|
1023
|
+
resolution_options: ['merge', 'keep_left', 'keep_right', 'split_scope']
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function agentConflictPreference(item) {
|
|
1028
|
+
return {
|
|
1029
|
+
item_id: item.id,
|
|
1030
|
+
item_kind: item.item_kind,
|
|
1031
|
+
title: item.title,
|
|
1032
|
+
instruction: item.instruction,
|
|
1033
|
+
confirmed_at: item.confirmed_at,
|
|
1034
|
+
attributes: item.attributes ?? {}
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function preferenceConflictPairKey(leftId, rightId) {
|
|
1039
|
+
return [leftId, rightId].sort().join('\u0000');
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function agentProjectResolution(resolution) {
|
|
1043
|
+
const result = {
|
|
1044
|
+
status: resolution.status,
|
|
1045
|
+
basis: resolution.basis,
|
|
1046
|
+
personal_project_id: resolution.personalProjectId
|
|
1047
|
+
};
|
|
1048
|
+
if (resolution.candidateCount !== undefined) {
|
|
1049
|
+
result.candidate_count = resolution.candidateCount;
|
|
1050
|
+
}
|
|
1051
|
+
return result;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function assertNoCredentials(input) {
|
|
1055
|
+
const content = JSON.stringify({
|
|
1056
|
+
name: input.name,
|
|
1057
|
+
sourceDescription: input.sourceDescription,
|
|
1058
|
+
sourceExcerpt: input.sourceExcerpt,
|
|
1059
|
+
summary: input.summary,
|
|
1060
|
+
entities: input.entities,
|
|
1061
|
+
relationships: input.relationships
|
|
1062
|
+
});
|
|
1063
|
+
if (detectSensitiveContent(content).restricted) {
|
|
1064
|
+
throw new ApplicationError(
|
|
1065
|
+
'validation',
|
|
1066
|
+
'Structured knowledge contains credentials and cannot be stored'
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function retrievalGuidanceForScope(personalProjectScope) {
|
|
1072
|
+
if (personalProjectScope === 'all_local_confirmed') {
|
|
1073
|
+
return {
|
|
1074
|
+
currentPersonalProjectScope: 'all_local_confirmed',
|
|
1075
|
+
markerToUseIfNoSupportingEvidence: 'noMatchSourceMarker',
|
|
1076
|
+
requiredNextActionIfNoSupportingEvidence:
|
|
1077
|
+
'search_current_workspace_files_or_ask_for_safe_root',
|
|
1078
|
+
workspaceFileSearch: {
|
|
1079
|
+
available: true,
|
|
1080
|
+
consentSource: 'bounded_expansion_confirmation',
|
|
1081
|
+
rootBoundary: 'current_working_directory',
|
|
1082
|
+
requiresSafeProjectOrWorkspaceRoot: true,
|
|
1083
|
+
forbiddenBroadRoots: ['user_home', 'filesystem_root'],
|
|
1084
|
+
readOnly: true,
|
|
1085
|
+
includesPublicProjects: false
|
|
1086
|
+
},
|
|
1087
|
+
instruction: 'Use read-only local file search in the current repository or explicit ' +
|
|
1088
|
+
'workspace root, preserving exact names first. If the working directory is the user ' +
|
|
1089
|
+
'home, filesystem root, or otherwise too broad, ask for a safe root. Never search ' +
|
|
1090
|
+
'outside that root or inspect credential stores; if no evidence supports the answer, ' +
|
|
1091
|
+
'ask for a source clue.'
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
return {
|
|
1095
|
+
currentPersonalProjectScope: 'bounded',
|
|
1096
|
+
markerToUseIfNoSupportingEvidence: 'noMatchSourceMarker',
|
|
1097
|
+
requiredNextActionIfNoSupportingEvidence:
|
|
1098
|
+
'ask_user_to_confirm_all_local_and_workspace_search',
|
|
1099
|
+
expansion: {
|
|
1100
|
+
available: true,
|
|
1101
|
+
requiresExplicitUserConfirmation: true,
|
|
1102
|
+
input: { personalProjectScope: 'all_local_confirmed' },
|
|
1103
|
+
readOnly: true,
|
|
1104
|
+
oneQueryOnly: true,
|
|
1105
|
+
includesPublicProjects: false,
|
|
1106
|
+
includesCurrentWorkspaceFiles: true
|
|
1107
|
+
},
|
|
1108
|
+
instruction: 'Ask whether to widen this one read-only lookup to all registered local ' +
|
|
1109
|
+
'personal projects and, if still unresolved, current repository or workspace files. ' +
|
|
1110
|
+
'Exclude public projects and paths outside the current workspace; then stop and wait.'
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function assertSafeKnowledgeMutation(input) {
|
|
1115
|
+
const content = JSON.stringify({
|
|
1116
|
+
reason: input.reason,
|
|
1117
|
+
name: input.name,
|
|
1118
|
+
summary: input.summary,
|
|
1119
|
+
fact: input.fact,
|
|
1120
|
+
note: input.note,
|
|
1121
|
+
newProjectName: input.newProjectName,
|
|
1122
|
+
newProjectPurpose: input.newProjectPurpose,
|
|
1123
|
+
batchItems: input.items?.map((item) => ({
|
|
1124
|
+
existenceReason: item.existenceReason,
|
|
1125
|
+
quadrantReason: item.quadrantReason
|
|
1126
|
+
}))
|
|
1127
|
+
});
|
|
1128
|
+
if (detectSensitiveContent(content).restricted) {
|
|
1129
|
+
throw new ApplicationError(
|
|
1130
|
+
'validation',
|
|
1131
|
+
'Knowledge revision contains credentials and cannot be stored'
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function providerEpisode(input) {
|
|
1137
|
+
return {
|
|
1138
|
+
idempotency_key: input.idempotencyKey,
|
|
1139
|
+
session_id: input.sessionId,
|
|
1140
|
+
name: input.name,
|
|
1141
|
+
source_kind: input.sourceKind,
|
|
1142
|
+
source_description: input.sourceDescription,
|
|
1143
|
+
source_application: input.sourceApplication ?? null,
|
|
1144
|
+
source_turn_id: input.sourceTurnId ?? null,
|
|
1145
|
+
source_excerpt: input.sourceExcerpt ?? null,
|
|
1146
|
+
reference_time: input.referenceTime,
|
|
1147
|
+
summary: input.summary ?? '',
|
|
1148
|
+
sensitivity: input.sensitivity ?? 'normal',
|
|
1149
|
+
entities: input.entities.map((entity) => ({
|
|
1150
|
+
key: entity.key,
|
|
1151
|
+
name: entity.name,
|
|
1152
|
+
type: entity.type,
|
|
1153
|
+
summary: entity.summary ?? '',
|
|
1154
|
+
origin_quadrant: entity.originQuadrant ?? 'known_known',
|
|
1155
|
+
current_quadrant: entity.currentQuadrant ?? entity.originQuadrant ?? 'known_known',
|
|
1156
|
+
epistemic_status: entity.epistemicStatus ?? 'confirmed',
|
|
1157
|
+
confirmation_status: entity.confirmationStatus,
|
|
1158
|
+
confirmation_basis: providerConfirmationBasis(entity.confirmationBasis),
|
|
1159
|
+
reasoning_summary: entity.reasoningSummary ?? null,
|
|
1160
|
+
profile_aspect: entity.profileAspect ?? null,
|
|
1161
|
+
inheritance_mode: entity.inheritanceMode ?? 'local_only',
|
|
1162
|
+
inherited_project_ids: entity.inheritedProjectIds ?? [],
|
|
1163
|
+
attributes: entity.attributes ?? {}
|
|
1164
|
+
})),
|
|
1165
|
+
relationships: input.relationships.map((relationship) => ({
|
|
1166
|
+
key: relationship.key,
|
|
1167
|
+
source: relationship.source,
|
|
1168
|
+
target: relationship.target,
|
|
1169
|
+
type: relationship.type,
|
|
1170
|
+
fact: relationship.fact,
|
|
1171
|
+
valid_at: relationship.validAt ?? null,
|
|
1172
|
+
invalid_at: relationship.invalidAt ?? null,
|
|
1173
|
+
supersedes: relationship.supersedes ?? [],
|
|
1174
|
+
confidence: relationship.confidence ?? 1,
|
|
1175
|
+
origin_quadrant: relationship.originQuadrant ?? 'known_known',
|
|
1176
|
+
current_quadrant: relationship.currentQuadrant ??
|
|
1177
|
+
relationship.originQuadrant ?? 'known_known',
|
|
1178
|
+
epistemic_status: relationship.epistemicStatus ?? 'confirmed',
|
|
1179
|
+
confirmation_status: relationship.confirmationStatus,
|
|
1180
|
+
confirmation_basis: providerConfirmationBasis(relationship.confirmationBasis),
|
|
1181
|
+
reasoning_summary: relationship.reasoningSummary ?? null,
|
|
1182
|
+
profile_aspect: relationship.profileAspect ?? null,
|
|
1183
|
+
inheritance_mode: relationship.inheritanceMode ?? 'local_only',
|
|
1184
|
+
inherited_project_ids: relationship.inheritedProjectIds ?? [],
|
|
1185
|
+
attributes: relationship.attributes ?? {}
|
|
1186
|
+
}))
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function assertPublicKnowledgeEligible(episode) {
|
|
1191
|
+
const blocked = [...episode.entities, ...episode.relationships].find((item) =>
|
|
1192
|
+
item.profile_aspect ||
|
|
1193
|
+
item.confirmation_status !== 'confirmed' ||
|
|
1194
|
+
!item.confirmation_basis?.confirmed_by ||
|
|
1195
|
+
!item.confirmation_basis?.confirmed_at ||
|
|
1196
|
+
!['user', 'authoritative_source'].includes(item.confirmation_basis.confirmed_by.kind)
|
|
1197
|
+
);
|
|
1198
|
+
if (blocked) {
|
|
1199
|
+
throw new TypeError(
|
|
1200
|
+
'Only knowledge with an auditable confirmation can enter public review'
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function providerConfirmationBasis(basis) {
|
|
1206
|
+
if (!basis) return undefined;
|
|
1207
|
+
return {
|
|
1208
|
+
existence_reason: basis.existenceReason,
|
|
1209
|
+
quadrant_reason: basis.quadrantReason,
|
|
1210
|
+
proposed_by: providerConfirmationActor(basis.proposedBy),
|
|
1211
|
+
confirmed_by: providerConfirmationActor(basis.confirmedBy),
|
|
1212
|
+
confirmed_at: basis.confirmedAt ?? null,
|
|
1213
|
+
agent_policy_version: basis.agentPolicyVersion ?? null
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function providerConfirmationActor(actor) {
|
|
1218
|
+
if (!actor) return null;
|
|
1219
|
+
return {
|
|
1220
|
+
kind: actor.kind,
|
|
1221
|
+
label: actor.label ?? null
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function providerProjectProfile(profile) {
|
|
1226
|
+
if (!profile || typeof profile !== 'object') throw new TypeError('Project profile is required');
|
|
1227
|
+
const assessment = profile.assessment ? sanitizeProjectAssessment(profile.assessment) : null;
|
|
1228
|
+
return {
|
|
1229
|
+
name: profile.name,
|
|
1230
|
+
purpose: profile.purpose ?? null,
|
|
1231
|
+
scope: profile.scope ?? null,
|
|
1232
|
+
technical_summary: profile.technical_summary ?? profile.technicalSummary ?? null,
|
|
1233
|
+
lifecycle: profile.lifecycle ?? 'planned',
|
|
1234
|
+
sources: profile.sources ?? [],
|
|
1235
|
+
boundaries: profile.boundaries ?? [],
|
|
1236
|
+
assessment
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function sanitizeProjectAssessment(input) {
|
|
1241
|
+
const assessment = {
|
|
1242
|
+
score: input.score,
|
|
1243
|
+
label: input.label,
|
|
1244
|
+
confirmed: input.confirmed ?? [],
|
|
1245
|
+
inferred: input.inferred ?? [],
|
|
1246
|
+
dimensions: (input.dimensions ?? []).map((dimension) => ({
|
|
1247
|
+
key: dimension.key,
|
|
1248
|
+
label: dimension.label,
|
|
1249
|
+
score: dimension.score,
|
|
1250
|
+
state: dimension.state === 'missing' ? 'inferred' : dimension.state,
|
|
1251
|
+
evidence: dimension.evidence ?? []
|
|
1252
|
+
})),
|
|
1253
|
+
analyzed_at: input.analyzed_at ?? input.analyzedAt
|
|
1254
|
+
};
|
|
1255
|
+
return assessment;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function groupSubscriptions(subscriptions) {
|
|
1259
|
+
const grouped = new Map();
|
|
1260
|
+
for (const subscription of subscriptions) {
|
|
1261
|
+
const url = canonicalProviderUrl(subscription.provider_url);
|
|
1262
|
+
if (!grouped.has(url)) grouped.set(url, []);
|
|
1263
|
+
grouped.get(url).push(subscription);
|
|
1264
|
+
}
|
|
1265
|
+
return grouped;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function rankedSearchItems(searchResults, key, limit) {
|
|
1269
|
+
return searchResults
|
|
1270
|
+
.flatMap((result) => result[key])
|
|
1271
|
+
.map((item, index) => ({ item, index }))
|
|
1272
|
+
.sort((left, right) =>
|
|
1273
|
+
searchItemScore(right.item) - searchItemScore(left.item) ||
|
|
1274
|
+
left.index - right.index
|
|
1275
|
+
)
|
|
1276
|
+
.slice(0, limit)
|
|
1277
|
+
.map(({ item }) => item);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function searchItemScore(item) {
|
|
1281
|
+
return typeof item?.score === 'number' && Number.isFinite(item.score)
|
|
1282
|
+
? item.score
|
|
1283
|
+
: 0;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
async function safely(operation, fallback) {
|
|
1287
|
+
try {
|
|
1288
|
+
return await operation();
|
|
1289
|
+
} catch {
|
|
1290
|
+
return fallback;
|
|
1291
|
+
}
|
|
1292
|
+
}
|