fuli-context 0.5.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -333
- package/README.md +370 -202
- package/README.zh-CN.md +448 -0
- package/compose.graphiti.yml +6 -6
- package/dist/web/assets/AboutPage-DXh7Fwxt.css +1 -0
- package/dist/web/assets/AboutPage-DfnZo7Lo.js +1 -0
- package/dist/web/assets/ConnectionsPage-BloaSKmt.js +1 -0
- package/dist/web/assets/ConnectionsPage-CBHAdZXT.css +1 -0
- package/dist/web/assets/KnowledgeInspector-Bjhdu2mf.css +1 -0
- package/dist/web/assets/KnowledgeInspector-QvNpv0_k.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-Cx1w1jb2.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-DKq1jVD5.js +1 -0
- package/dist/web/assets/KnowledgePage-BWL0jT01.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-Czq6aZ7b.js +4 -0
- package/dist/web/assets/OverviewPage-5sJoO1Gh.js +1 -0
- package/dist/web/assets/{PersonalProfilePage-S8ZcG2LU.css → PersonalProfilePage-CdmEzyvZ.css} +1 -1
- package/dist/web/assets/PersonalProfilePage-tVChM9ik.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-BPFrup4V.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BPJSeREN.js +1 -0
- package/dist/web/assets/ReviewPage-B7EN817i.js +1 -0
- package/dist/web/assets/SearchableSelect-ClLN0rIb.js +1 -0
- package/dist/web/assets/SettingsPage-C6ouU0sR.css +1 -0
- package/dist/web/assets/SettingsPage-Chd_B3eH.js +1 -0
- package/dist/web/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
- package/dist/web/assets/console-CbDsKrsO.js +1 -0
- package/dist/web/assets/fuli-logo-Bu9WBuQe.js +1 -0
- package/dist/web/assets/fuli-logo-ER1wQ8Kk.png +0 -0
- package/dist/web/assets/i18n-BQcns1PH.js +5 -0
- package/dist/web/assets/identity-B8K4VYg6.js +1 -0
- package/dist/web/assets/index-CB8Xmsc2.js +2 -0
- package/dist/web/assets/index-CZeuPh-U.css +1 -0
- package/dist/web/assets/model-eF52GgPF.js +1 -0
- package/dist/web/assets/paths-hwz-Ac1g.js +1 -0
- package/dist/web/assets/useMinimumLoadingDisplay-BpCyBeko.css +1 -0
- package/dist/web/assets/useMinimumLoadingDisplay-wCgGDmHg.js +1 -0
- package/dist/web/assets/vue-router-BsEp_XwI.js +1 -0
- package/dist/web/index.html +9 -6
- package/docs/external-knowledge-architecture.md +279 -0
- package/examples/external-knowledge/markdown-folder.mjs +235 -0
- package/graph-provider/fuli_graph/app.py +57 -1
- package/graph-provider/fuli_graph/graph_models.py +3 -0
- package/graph-provider/fuli_graph/graph_query.py +62 -16
- package/graph-provider/fuli_graph/knowledge_review.py +612 -0
- package/graph-provider/fuli_graph/knowledge_review_models.py +127 -0
- package/graph-provider/fuli_graph/knowledge_search.py +51 -4
- package/graph-provider/fuli_graph/models.py +19 -0
- package/graph-provider/fuli_graph/runtime.py +15 -0
- package/graph-provider/fuli_graph/store_knowledge.py +2 -1
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +5 -407
- package/package.json +7 -5
- package/skills/capturing-session-knowledge/SKILL.md +24 -4
- package/skills/flreview/SKILL.md +73 -0
- package/skills/flreview/agents/openai.yaml +6 -0
- package/src/agent-tools/graph-definitions.js +79 -4
- package/src/agent-tools/graph-handlers.js +12 -0
- package/src/cli/command-registry.js +3 -3
- package/src/cli/local-runtime-command.js +51 -28
- package/src/cli/local-runtime-options.js +14 -8
- package/src/cli/setup-agent-selection.js +12 -12
- package/src/cli/setup-command.js +41 -18
- package/src/cli/shell-argument.js +10 -0
- package/src/cli/uninstall-command.js +17 -15
- package/src/cli/update-command.js +38 -30
- package/src/external-knowledge/catalog.js +61 -0
- package/src/external-knowledge/conflict-policy.js +58 -0
- package/src/external-knowledge/connected-search.js +166 -0
- package/src/external-knowledge/connectors/custom.js +133 -0
- package/src/external-knowledge/connectors/feishu.js +461 -0
- package/src/external-knowledge/connectors/mcp.js +438 -0
- package/src/external-knowledge/connectors/notion.js +307 -0
- package/src/external-knowledge/connectors/retrieval-api.js +222 -0
- package/src/external-knowledge/document-mapping.js +151 -0
- package/src/external-knowledge/graph-projection.js +17 -0
- package/src/external-knowledge/index.js +8 -0
- package/src/external-knowledge/registry.js +91 -0
- package/src/external-knowledge/runtime.js +68 -0
- package/src/external-knowledge/safe-diagnostic.js +14 -0
- package/src/external-knowledge/service.js +811 -0
- package/src/graphiti/agent-knowledge-workflows.js +7 -2
- package/src/graphiti/federated-application.js +66 -41
- package/src/graphiti/knowledge-item-role.js +21 -0
- package/src/graphiti/knowledge-review.js +43 -0
- package/src/graphiti/project-profile-mapping.js +32 -0
- package/src/graphiti/provider-client.js +22 -1
- package/src/graphiti/source-marker.js +54 -3
- package/src/graphiti/task-knowledge-recall.js +250 -0
- package/src/http/api-router.js +23 -108
- package/src/http/external-knowledge-api-router.js +104 -0
- package/src/http/graph-api-router.js +12 -1
- package/src/http/request-policy.js +1 -1
- package/src/local-runtime/lifecycle.js +26 -15
- package/src/mcp/create-mcp-server.js +1 -0
- package/src/mcp/instructions.js +7 -7
- package/src/mcp/tool-annotations.js +6 -0
- package/src/mcp/tool-result.js +1 -0
- package/src/server/application-lifecycle.js +2 -11
- package/src/server/blocked-ports.js +11 -0
- package/src/server.js +36 -17
- package/src/setup/agent-installation-status.js +2 -1
- package/src/setup/agents.js +2 -1
- package/src/setup/claude-code-config.js +2 -1
- package/src/setup/codex-bootstrap.js +12 -7
- package/src/setup/container-runtime.js +25 -21
- package/src/setup/graph-runtime.js +148 -56
- package/src/setup/node-runtime.js +3 -3
- package/src/setup/options.js +2 -4
- package/src/setup/paths.js +18 -3
- package/src/setup/review-skill-adapters.js +49 -0
- package/src/setup/setup.js +26 -2
- package/src/setup/uninstall.js +4 -2
- package/src/system/resource-monitor.js +317 -0
- package/src/system/runtime-settings.js +131 -0
- package/src/system/system-service.js +119 -0
- package/dist/web/assets/ConnectionsPage-CCKWm-6p.js +0 -1
- package/dist/web/assets/KnowledgeInspector-BZkB2wJn.js +0 -1
- package/dist/web/assets/KnowledgeInspector-CrGYxEob.css +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-CZAF_OlN.css +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-DlvcLwlL.js +0 -1
- package/dist/web/assets/KnowledgePage-CNQXd15y.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-CXPE4GCD.js +0 -4
- package/dist/web/assets/OverviewPage-ul7g7HXh.js +0 -1
- package/dist/web/assets/PersonalProfilePage-CC40Pw2L.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-CPIEcShD.js +0 -1
- package/dist/web/assets/PublicProjectsPage-C700sR7z.js +0 -1
- package/dist/web/assets/ReviewPage-7MDDUDgA.js +0 -1
- package/dist/web/assets/console-B_Ena2DS.js +0 -5
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-CjBwYSMX.js +0 -1
- package/dist/web/assets/index-CpLDx3of.css +0 -1
- package/dist/web/assets/index-DAzeFJSc.js +0 -2
- package/dist/web/assets/model-C-5RVIJ2.js +0 -1
- package/dist/web/assets/paths-BkULqmGI.js +0 -1
- package/dist/web/assets/vue-router-DnD3hUm7.js +0 -1
- package/src/agent/candidate-read-service.js +0 -53
- package/src/agent/context-pack-service.js +0 -83
- package/src/agent/public-read-policy.js +0 -39
- package/src/agent/public-read-service.js +0 -69
- package/src/agent/read-projection.js +0 -107
- package/src/agent/search-service.js +0 -61
- package/src/agent/space-boundary.js +0 -33
- package/src/app/bootstrap.js +0 -8
- package/src/app/create-application.js +0 -143
- package/src/app/state-service.js +0 -38
- package/src/candidates.js +0 -78
- package/src/classifier.js +0 -63
- package/src/cli/command-arguments.js +0 -14
- package/src/context-pack.js +0 -206
- package/src/extractor.js +0 -77
- package/src/fact-history.js +0 -39
- package/src/facts/fact-service.js +0 -89
- package/src/http/bounded-json-array.js +0 -19
- package/src/http/lens-route.js +0 -50
- package/src/ingestion.js +0 -161
- package/src/lens/lens-correction-service.js +0 -81
- package/src/lens/lens-fact-writer.js +0 -85
- package/src/lens/lens-input.js +0 -62
- package/src/lens/lens-query-projection.js +0 -87
- package/src/lens/lens-query-ranking.js +0 -62
- package/src/lens/lens-query.js +0 -143
- package/src/lens/lens-resource-projection.js +0 -93
- package/src/lens/lens-resource-selection.js +0 -45
- package/src/lens/lens-resource-service.js +0 -103
- package/src/lens/lens-search-query.js +0 -122
- package/src/lens/lens-service.js +0 -183
- package/src/models.js +0 -44
- package/src/observer.js +0 -83
- package/src/project-rules.js +0 -33
- package/src/publication/outbox-service.js +0 -46
- package/src/publication/publication-envelope-schema.js +0 -75
- package/src/publication/publication-envelope.js +0 -37
- package/src/publication/publication-policy.js +0 -82
- package/src/publication/publication-service.js +0 -48
- package/src/router.js +0 -58
- package/src/runtime-options.js +0 -138
- package/src/security/episode-visibility.js +0 -14
- package/src/security/fact-visibility.js +0 -15
- package/src/security/source-metadata-policy.js +0 -8
- package/src/storage/episode-evidence-preview.js +0 -100
- package/src/storage/fact-search-order.js +0 -18
- package/src/storage/file/episode-evidence.js +0 -37
- package/src/storage/file/episode-index.js +0 -59
- package/src/storage/file/fact-search.js +0 -60
- package/src/storage/file-store.js +0 -437
- package/src/storage/migrate.js +0 -128
- package/src/storage/migrations/001-initial.sql +0 -83
- package/src/storage/migrations/002-storage-integrity.sql +0 -113
- package/src/storage/record-validation.js +0 -46
- package/src/storage/snapshot-defaults.js +0 -54
- package/src/storage/sqlite/candidates-repository.js +0 -84
- package/src/storage/sqlite/episode-evidence-repository.js +0 -154
- package/src/storage/sqlite/episode-privacy-sql.js +0 -33
- package/src/storage/sqlite/episodes-repository.js +0 -52
- package/src/storage/sqlite/fact-search-repository.js +0 -162
- package/src/storage/sqlite/facts-repository.js +0 -146
- package/src/storage/sqlite/imports-repository.js +0 -39
- package/src/storage/sqlite/mapper.js +0 -84
- package/src/storage/sqlite/open-database.js +0 -15
- package/src/storage/sqlite/outbox-repository.js +0 -103
- package/src/storage/sqlite/snapshot-repository.js +0 -69
- package/src/storage/sqlite/spaces-repository.js +0 -52
- package/src/storage/sqlite/subscriptions-repository.js +0 -54
- package/src/storage/sqlite-store.js +0 -159
- package/src/storage/stable-json.js +0 -24
- package/src/storage/store-port.js +0 -20
- package/src/store.js +0 -2
package/src/cli/setup-command.js
CHANGED
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
} from './setup-agent-selection.js';
|
|
8
8
|
import { parseSetupOptions } from '../setup/options.js';
|
|
9
9
|
import { applyLocalSetup, planLocalSetup } from '../setup/setup.js';
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_RUNTIME_SETTINGS,
|
|
12
|
+
runtimeSettingsWithOverrides
|
|
13
|
+
} from '../system/runtime-settings.js';
|
|
10
14
|
|
|
11
15
|
export async function runSetupCommand(args, dependencies = {}) {
|
|
12
16
|
const options = parseSetupOptions(args);
|
|
@@ -23,7 +27,7 @@ export async function runSetupCommand(args, dependencies = {}) {
|
|
|
23
27
|
write(formatSetupPreview(plan, options));
|
|
24
28
|
|
|
25
29
|
if (!options.yes && !await confirm()) {
|
|
26
|
-
write('
|
|
30
|
+
write('Cancelled. No changes were made.');
|
|
27
31
|
return { status: 'cancelled' };
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -39,33 +43,50 @@ export function formatSetupPreview(plan, options) {
|
|
|
39
43
|
const available = plan.agents.filter(({ available }) => available);
|
|
40
44
|
const selected = available.filter(({ selected }) => selected !== false);
|
|
41
45
|
const agentSummary = options.skipAgents || (available.length > 0 && selected.length === 0)
|
|
42
|
-
? '
|
|
43
|
-
: selected.map(({ label }) => label).join('
|
|
46
|
+
? 'skipped'
|
|
47
|
+
: selected.map(({ label }) => label).join(', ') || 'none detected';
|
|
48
|
+
const runtimeSettings = plan.runtimeSettings ?? runtimeSettingsWithOverrides(
|
|
49
|
+
DEFAULT_RUNTIME_SETTINGS,
|
|
50
|
+
{ consolePort: options.port }
|
|
51
|
+
);
|
|
44
52
|
return [
|
|
45
|
-
'
|
|
53
|
+
'Ready to set up Fuli',
|
|
46
54
|
options.personalOnly
|
|
47
|
-
? '
|
|
48
|
-
: '
|
|
49
|
-
'
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
? 'Storage: personal only, using local Graphiti / Neo4j'
|
|
56
|
+
: 'Storage: local Graphiti / Neo4j plus a development shared-project Provider',
|
|
57
|
+
'Container runtime: detected automatically; an installed desktop runtime starts if needed',
|
|
58
|
+
`Shared services: ${options.personalOnly ? 'not connected' : 'local development Provider'}`,
|
|
59
|
+
`Personal space: ${options.personalSpaceName}`,
|
|
60
|
+
`Management UI: ${options.noStart
|
|
61
|
+
? 'will not start'
|
|
62
|
+
: `http://127.0.0.1:${runtimeSettings.ports.console}`}`,
|
|
63
|
+
`Agents: ${agentSummary}`
|
|
54
64
|
].join('\n');
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
export function formatSetupResult(result, plan) {
|
|
58
|
-
const lines = [result.status === 'ready'
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
const lines = [result.status === 'ready'
|
|
69
|
+
? 'Fuli is ready.'
|
|
70
|
+
: 'Fuli started, but one or more Agents are not connected.'];
|
|
71
|
+
if (result.runtime.url) lines.push(`Open: ${result.runtime.url}`);
|
|
72
|
+
if (result.runtime.lan === true) {
|
|
73
|
+
lines.push(
|
|
74
|
+
'LAN URLs:',
|
|
75
|
+
...result.runtime.lanUrls.map((url) => ` ${url}`),
|
|
76
|
+
`Username: ${result.runtime.lanAccess.username}`,
|
|
77
|
+
`Temporary access code: ${result.runtime.lanAccess.accessCode}`,
|
|
78
|
+
'Use only on trusted Wi-Fi. Restarting LAN mode rotates the access code.'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
lines.push('Knowledge storage: Graphiti / Neo4j');
|
|
61
82
|
for (const agent of result.agents) {
|
|
62
83
|
if (agent.status !== 'connected') {
|
|
63
|
-
lines.push(`${agent.label}
|
|
84
|
+
lines.push(`${agent.label}: connection failed; retry setup later`);
|
|
64
85
|
continue;
|
|
65
86
|
}
|
|
66
87
|
lines.push(agent.newTaskRequired
|
|
67
|
-
? `${agent.label}
|
|
68
|
-
: `${agent.label}
|
|
88
|
+
? `${agent.label}: connected; create or reopen a task to load the new configuration`
|
|
89
|
+
: `${agent.label}: connected`);
|
|
69
90
|
}
|
|
70
91
|
return lines.join('\n');
|
|
71
92
|
}
|
|
@@ -73,7 +94,9 @@ export function formatSetupResult(result, plan) {
|
|
|
73
94
|
async function confirmInTerminal() {
|
|
74
95
|
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
75
96
|
try {
|
|
76
|
-
const answer = (await input.question('
|
|
97
|
+
const answer = (await input.question('Only the items above will change. Continue? [Y/n] '))
|
|
98
|
+
.trim()
|
|
99
|
+
.toLowerCase();
|
|
77
100
|
return answer === '' || answer === 'y' || answer === 'yes';
|
|
78
101
|
} finally {
|
|
79
102
|
input.close();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function quoteShellArgument(value, platform = process.platform) {
|
|
2
|
+
const argument = String(value);
|
|
3
|
+
if (platform === 'win32') {
|
|
4
|
+
if (argument.includes('"')) {
|
|
5
|
+
throw new TypeError('Windows paths cannot contain a double quote');
|
|
6
|
+
}
|
|
7
|
+
return `"${argument}"`;
|
|
8
|
+
}
|
|
9
|
+
return `'${argument.replaceAll("'", "'\\''")}'`;
|
|
10
|
+
}
|
|
@@ -14,7 +14,7 @@ export async function runUninstallCommand(args, dependencies = {}) {
|
|
|
14
14
|
write(formatUninstallPreview(plan));
|
|
15
15
|
|
|
16
16
|
if (!options.yes && !await confirm()) {
|
|
17
|
-
write('
|
|
17
|
+
write('Cancelled. No changes were made.');
|
|
18
18
|
return { status: 'cancelled' };
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -24,30 +24,32 @@ export async function runUninstallCommand(args, dependencies = {}) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function formatUninstallPreview(plan) {
|
|
27
|
-
const labels = plan.agents.map(({ label }) => label).join('
|
|
27
|
+
const labels = plan.agents.map(({ label }) => label).join(', ') || 'none';
|
|
28
28
|
return [
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
`Agent
|
|
32
|
-
'Skills
|
|
33
|
-
|
|
34
|
-
'Neo4j
|
|
29
|
+
'Ready to remove local Fuli integrations',
|
|
30
|
+
'Local services: stop',
|
|
31
|
+
`Agent integrations: ${labels}`,
|
|
32
|
+
'Skills: remove only copies identical to this package; preserve locally modified copies',
|
|
33
|
+
`Data: preserve ${plan.paths.dataDir}`,
|
|
34
|
+
'Neo4j volumes: preserve'
|
|
35
35
|
].join('\n');
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export function formatUninstallResult(result) {
|
|
39
39
|
const lines = [
|
|
40
|
-
result.status === 'ready'
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
result.status === 'ready'
|
|
41
|
+
? 'Local Fuli integrations were removed.'
|
|
42
|
+
: 'Some local Fuli integrations could not be removed.',
|
|
43
|
+
`Data preserved: ${result.data.path}`,
|
|
44
|
+
`Finally, remove the global command: npm uninstall --global ${FULI_PACKAGE_NAME}`
|
|
43
45
|
];
|
|
44
46
|
if (result.runtime.status !== 'stopped') {
|
|
45
|
-
lines.push(
|
|
47
|
+
lines.push(`Local services: ${result.runtime.message ?? 'could not safely confirm they stopped'}`);
|
|
46
48
|
}
|
|
47
49
|
for (const agent of result.agents) {
|
|
48
50
|
const modified = agent.skills.some(({ status }) => status === 'preserved_modified');
|
|
49
|
-
if (modified) lines.push(`${agent.label}
|
|
50
|
-
if (agent.errors?.length) lines.push(`${agent.label}
|
|
51
|
+
if (modified) lines.push(`${agent.label}: preserved a locally modified Skill`);
|
|
52
|
+
if (agent.errors?.length) lines.push(`${agent.label}: ${agent.errors.join('; ')}`);
|
|
51
53
|
}
|
|
52
54
|
return lines.join('\n');
|
|
53
55
|
}
|
|
@@ -55,7 +57,7 @@ export function formatUninstallResult(result) {
|
|
|
55
57
|
async function confirmInTerminal() {
|
|
56
58
|
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
57
59
|
try {
|
|
58
|
-
const answer = (await input.question('
|
|
60
|
+
const answer = (await input.question('Remove local integrations? [y/N] ')).trim().toLowerCase();
|
|
59
61
|
return answer === 'y' || answer === 'yes';
|
|
60
62
|
} finally {
|
|
61
63
|
input.close();
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
FULI_PACKAGE_NAME,
|
|
8
8
|
FULI_VERSION
|
|
9
9
|
} from '../package-metadata.js';
|
|
10
|
-
import { quoteShellArgument } from '
|
|
10
|
+
import { quoteShellArgument } from './shell-argument.js';
|
|
11
11
|
import { parseUpdateOptions } from '../setup/options.js';
|
|
12
12
|
import { runLocalRuntimeCommand } from './local-runtime-command.js';
|
|
13
13
|
|
|
@@ -31,7 +31,7 @@ export async function runUpdateCommand(args = [], dependencies = {}) {
|
|
|
31
31
|
|
|
32
32
|
write(formatUpdatePreview(options));
|
|
33
33
|
if (!options.yes && !await confirm()) {
|
|
34
|
-
write('
|
|
34
|
+
write('Cancelled. No changes were made.');
|
|
35
35
|
return { status: 'cancelled' };
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -51,13 +51,13 @@ export async function runUpdateCommand(args = [], dependencies = {}) {
|
|
|
51
51
|
const stopped = await stopRuntime('stop', stopArgs, { env, write });
|
|
52
52
|
if (stopped.status === 'partial') {
|
|
53
53
|
throw new Error([
|
|
54
|
-
'
|
|
55
|
-
'
|
|
54
|
+
'Could not safely confirm that the previous UI process stopped. The update did not start.',
|
|
55
|
+
'Run fuli status to inspect the local state, then retry fuli update.'
|
|
56
56
|
].join('\n'));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const selectedPackage = `${FULI_PACKAGE_NAME}@${latestVersion}`;
|
|
60
|
-
write(
|
|
60
|
+
write(`Installing ${selectedPackage} (npm latest)...`);
|
|
61
61
|
const installed = runInherited(npmCommand, [
|
|
62
62
|
'install',
|
|
63
63
|
'--global',
|
|
@@ -67,15 +67,16 @@ export async function runUpdateCommand(args = [], dependencies = {}) {
|
|
|
67
67
|
], { env });
|
|
68
68
|
if (!processSucceeded(installed)) {
|
|
69
69
|
throw new Error([
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
`Global package installation failed (${describeProcessFailure(installed)}). ` +
|
|
71
|
+
'Knowledge data was not deleted.',
|
|
72
|
+
`Run npm install --global ${LATEST_PACKAGE}, then run fuli setup --yes.`
|
|
72
73
|
].join('\n'));
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
const globalRootResult = runCaptured(npmCommand, ['root', '--global'], { env });
|
|
76
77
|
if (!processSucceeded(globalRootResult)) {
|
|
77
78
|
throw postInstallError(
|
|
78
|
-
|
|
79
|
+
`Could not locate the global npm directory (${describeProcessFailure(globalRootResult)})`,
|
|
79
80
|
args,
|
|
80
81
|
platform
|
|
81
82
|
);
|
|
@@ -83,33 +84,35 @@ export async function runUpdateCommand(args = [], dependencies = {}) {
|
|
|
83
84
|
const globalRoot = String(globalRootResult.stdout ?? '').trim();
|
|
84
85
|
const cliPath = resolveGlobalCliPath(globalRoot, platform);
|
|
85
86
|
if (!cliPath || !fileExists(cliPath)) {
|
|
86
|
-
throw postInstallError('
|
|
87
|
+
throw postInstallError('The new CLI file is missing; reinstall the global package', args, platform);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
const versionResult = runCaptured(nodePath, [cliPath, '--version'], { env });
|
|
90
91
|
if (!processSucceeded(versionResult)) {
|
|
91
92
|
throw postInstallError(
|
|
92
|
-
|
|
93
|
+
`The new CLI could not start (${describeProcessFailure(versionResult)})`,
|
|
93
94
|
args,
|
|
94
95
|
platform
|
|
95
96
|
);
|
|
96
97
|
}
|
|
97
98
|
const version = String(versionResult.stdout ?? '').trim();
|
|
98
|
-
if (!version) throw postInstallError('
|
|
99
|
+
if (!version) throw postInstallError('The new CLI returned no version', args, platform);
|
|
99
100
|
if (compareSemanticVersions(version, latestVersion) !== 0) {
|
|
100
101
|
throw postInstallError(
|
|
101
|
-
|
|
102
|
+
`The new CLI version ${version} does not match the npm latest version ${latestVersion} ` +
|
|
103
|
+
'confirmed before the update',
|
|
102
104
|
args,
|
|
103
105
|
platform
|
|
104
106
|
);
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
write('
|
|
109
|
+
write('Refreshing local services, Agent integrations, and Skills with the new CLI...');
|
|
108
110
|
const setupArgs = ['setup', '--yes', ...args.filter((arg) => arg !== '--yes')];
|
|
109
111
|
const setupResult = runInherited(nodePath, [cliPath, ...setupArgs], { env });
|
|
110
112
|
if (!processSucceeded(setupResult)) {
|
|
111
113
|
throw postInstallError(
|
|
112
|
-
|
|
114
|
+
`The new version was installed, but setup did not complete ` +
|
|
115
|
+
`(${describeProcessFailure(setupResult)})`,
|
|
113
116
|
args,
|
|
114
117
|
platform
|
|
115
118
|
);
|
|
@@ -126,27 +129,32 @@ export async function runUpdateCommand(args = [], dependencies = {}) {
|
|
|
126
129
|
|
|
127
130
|
export function formatUpdatePreview(options) {
|
|
128
131
|
return [
|
|
129
|
-
'
|
|
130
|
-
`npm
|
|
132
|
+
'Ready to update Fuli',
|
|
133
|
+
`npm package: ${LATEST_PACKAGE}`,
|
|
131
134
|
options.noStart
|
|
132
|
-
? '
|
|
133
|
-
: '
|
|
134
|
-
options.dataDir
|
|
135
|
-
|
|
135
|
+
? 'Local UI: stop safely and keep stopped after the update'
|
|
136
|
+
: 'Local services: stop safely and restart with the new version',
|
|
137
|
+
options.dataDir
|
|
138
|
+
? `Data: preserve ${options.dataDir}`
|
|
139
|
+
: 'Data: preserve the current default data directory',
|
|
140
|
+
`Agent integrations and Skills: ${options.skipAgents
|
|
141
|
+
? 'skip refresh as requested'
|
|
142
|
+
: 'refresh through setup in the new version'}`
|
|
136
143
|
].join('\n');
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
export function formatUpdateResult(result) {
|
|
140
147
|
if (result.status === 'ahead') {
|
|
141
148
|
return [
|
|
142
|
-
|
|
143
|
-
'
|
|
149
|
+
`The current CLI version ${result.version} is newer than npm latest ${result.latestVersion}. `,
|
|
150
|
+
'To avoid a downgrade, services were not stopped, no package was installed, and Agent ' +
|
|
151
|
+
'integrations were not changed.'
|
|
144
152
|
].join('');
|
|
145
153
|
}
|
|
146
154
|
if (result.status === 'current') {
|
|
147
|
-
return
|
|
155
|
+
return `Fuli is already at the latest version (${result.version}); local integrations refreshed.`;
|
|
148
156
|
}
|
|
149
|
-
return
|
|
157
|
+
return `Fuli updated from ${result.previousVersion} to ${result.version}.`;
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
export function compareSemanticVersions(left, right) {
|
|
@@ -185,8 +193,8 @@ export function resolveGlobalCliPath(globalRoot, platform = process.platform) {
|
|
|
185
193
|
|
|
186
194
|
function postInstallError(message, args, platform) {
|
|
187
195
|
return new Error([
|
|
188
|
-
`${message}
|
|
189
|
-
|
|
196
|
+
`${message}. Knowledge data was not deleted.`,
|
|
197
|
+
`Run again: ${formatSetupRecoveryCommand(args, platform)}`
|
|
190
198
|
].join('\n'));
|
|
191
199
|
}
|
|
192
200
|
|
|
@@ -198,8 +206,8 @@ function queryLatestVersion(npmCommand, runCaptured, env) {
|
|
|
198
206
|
);
|
|
199
207
|
if (!processSucceeded(result)) {
|
|
200
208
|
throw new Error([
|
|
201
|
-
|
|
202
|
-
'
|
|
209
|
+
`Could not check ${LATEST_PACKAGE} (${describeProcessFailure(result)}).`,
|
|
210
|
+
'Local services were not stopped, and no package was installed or changed.'
|
|
203
211
|
].join('\n'));
|
|
204
212
|
}
|
|
205
213
|
const raw = String(result.stdout ?? '').trim();
|
|
@@ -244,7 +252,7 @@ function formatCommandArgument(value, platform) {
|
|
|
244
252
|
try {
|
|
245
253
|
return quoteShellArgument(value, platform);
|
|
246
254
|
} catch {
|
|
247
|
-
return '
|
|
255
|
+
return '<reuse-original-argument>';
|
|
248
256
|
}
|
|
249
257
|
}
|
|
250
258
|
|
|
@@ -279,7 +287,7 @@ function spawnCaptured(command, args, { env }) {
|
|
|
279
287
|
async function confirmInTerminal() {
|
|
280
288
|
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
281
289
|
try {
|
|
282
|
-
const answer = (await input.question('
|
|
290
|
+
const answer = (await input.question('This will modify the global npm installation. Continue? [Y/n] '))
|
|
283
291
|
.trim()
|
|
284
292
|
.toLowerCase();
|
|
285
293
|
return answer === '' || answer === 'y' || answer === 'yes';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createCustomConnector } from './connectors/custom.js';
|
|
2
|
+
import { createFeishuConnector } from './connectors/feishu.js';
|
|
3
|
+
import { createMcpConnector } from './connectors/mcp.js';
|
|
4
|
+
import { createNotionConnector } from './connectors/notion.js';
|
|
5
|
+
import { createRetrievalApiConnector } from './connectors/retrieval-api.js';
|
|
6
|
+
|
|
7
|
+
export function createConnectorCatalog({
|
|
8
|
+
customConnectorDirectory,
|
|
9
|
+
fetchImpl = globalThis.fetch,
|
|
10
|
+
mcpOpenSession,
|
|
11
|
+
sleep
|
|
12
|
+
}) {
|
|
13
|
+
const connectors = [
|
|
14
|
+
withMetadata(createMcpConnector({ openSession: mcpOpenSession }), {
|
|
15
|
+
status: 'supported',
|
|
16
|
+
trust: 'remote_protocol',
|
|
17
|
+
description: 'MCP resources and explicitly configured read tools.'
|
|
18
|
+
}),
|
|
19
|
+
withMetadata(createNotionConnector({ fetchImpl }), {
|
|
20
|
+
status: 'supported',
|
|
21
|
+
trust: 'official_read_api',
|
|
22
|
+
description: 'Notion pages and data sources through the current markdown API.'
|
|
23
|
+
}),
|
|
24
|
+
withMetadata(createFeishuConnector({ fetchImpl, sleep }), {
|
|
25
|
+
status: 'supported',
|
|
26
|
+
trust: 'official_read_api',
|
|
27
|
+
description: 'Feishu/Lark wiki discovery and read-only docx content.',
|
|
28
|
+
limitations: ['docx_content_only', 'wiki_search_requires_user_access_token']
|
|
29
|
+
}),
|
|
30
|
+
withMetadata(createRetrievalApiConnector({ fetchImpl }), {
|
|
31
|
+
status: 'supported',
|
|
32
|
+
trust: 'compatible_read_api',
|
|
33
|
+
description: 'Dify-compatible retrieval API for external RAG knowledge bases.',
|
|
34
|
+
limitations: ['live_retrieval_only', 'requires_stable_document_metadata_for_best_provenance']
|
|
35
|
+
}),
|
|
36
|
+
withMetadata(createCustomConnector({ directory: customConnectorDirectory }), {
|
|
37
|
+
status: 'advanced',
|
|
38
|
+
trust: 'trusted_local_code',
|
|
39
|
+
description: 'A local ESM connector module explicitly installed by the user.'
|
|
40
|
+
})
|
|
41
|
+
];
|
|
42
|
+
const byType = new Map(connectors.map((connector) => [connector.type, connector]));
|
|
43
|
+
return {
|
|
44
|
+
get(type) {
|
|
45
|
+
return byType.get(type) ?? null;
|
|
46
|
+
},
|
|
47
|
+
list() {
|
|
48
|
+
return connectors.map(({ metadata }) => structuredClone(metadata));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function withMetadata(connector, metadata) {
|
|
54
|
+
const descriptor = Object.freeze({
|
|
55
|
+
type: connector.type,
|
|
56
|
+
name: connector.name,
|
|
57
|
+
capabilities: [...connector.capabilities],
|
|
58
|
+
...metadata
|
|
59
|
+
});
|
|
60
|
+
return Object.assign(connector, { metadata: descriptor });
|
|
61
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readJsonFile, writeJsonFileAtomic } from '../storage/json-file.js';
|
|
2
|
+
|
|
3
|
+
const MODES = new Set(['ask_human', 'agent_decide']);
|
|
4
|
+
const EMPTY_POLICIES = Object.freeze({ version: 1, projects: {} });
|
|
5
|
+
|
|
6
|
+
export class KnowledgeConflictPolicyStore {
|
|
7
|
+
constructor(filePath, { now = () => new Date() } = {}) {
|
|
8
|
+
if (typeof filePath !== 'string' || !filePath.trim()) {
|
|
9
|
+
throw new TypeError('Knowledge conflict policy path is required');
|
|
10
|
+
}
|
|
11
|
+
this.filePath = filePath;
|
|
12
|
+
this.now = now;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get(personalProjectId) {
|
|
16
|
+
const id = requiredString(personalProjectId, 'personalProjectId');
|
|
17
|
+
const projects = this.#read().projects;
|
|
18
|
+
const stored = projects[policyKey(id)] ??
|
|
19
|
+
(Object.hasOwn(projects, id) ? projects[id] : null);
|
|
20
|
+
return stored
|
|
21
|
+
? structuredClone(stored)
|
|
22
|
+
: { personalProjectId: id, mode: 'ask_human', updatedAt: null };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
set(personalProjectId, mode) {
|
|
26
|
+
const id = requiredString(personalProjectId, 'personalProjectId');
|
|
27
|
+
if (!MODES.has(mode)) {
|
|
28
|
+
throw new TypeError('Conflict policy mode must be ask_human or agent_decide');
|
|
29
|
+
}
|
|
30
|
+
const document = this.#read();
|
|
31
|
+
const policy = {
|
|
32
|
+
personalProjectId: id,
|
|
33
|
+
mode,
|
|
34
|
+
updatedAt: this.now().toISOString()
|
|
35
|
+
};
|
|
36
|
+
document.projects[policyKey(id)] = policy;
|
|
37
|
+
writeJsonFileAtomic(this.filePath, document);
|
|
38
|
+
return structuredClone(policy);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#read() {
|
|
42
|
+
const document = readJsonFile(this.filePath, EMPTY_POLICIES);
|
|
43
|
+
if (document?.version !== 1 || !document.projects ||
|
|
44
|
+
typeof document.projects !== 'object' || Array.isArray(document.projects)) {
|
|
45
|
+
throw new TypeError('Unsupported knowledge conflict policy format');
|
|
46
|
+
}
|
|
47
|
+
return structuredClone(document);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function policyKey(personalProjectId) {
|
|
52
|
+
return `project:${personalProjectId}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function requiredString(value, label) {
|
|
56
|
+
if (typeof value !== 'string' || !value.trim()) throw new TypeError(`${label} is required`);
|
|
57
|
+
return value.trim();
|
|
58
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { safeExternalSourceDiagnostic } from './safe-diagnostic.js';
|
|
2
|
+
|
|
3
|
+
export class ConnectedKnowledgeSearch {
|
|
4
|
+
constructor({ app, externalKnowledge, policies }) {
|
|
5
|
+
if (!app || !externalKnowledge || !policies) {
|
|
6
|
+
throw new TypeError('Connected knowledge search requires app, externalKnowledge, and policies');
|
|
7
|
+
}
|
|
8
|
+
this.app = app;
|
|
9
|
+
this.externalKnowledge = externalKnowledge;
|
|
10
|
+
this.policies = policies;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async query(input) {
|
|
14
|
+
const personalSpaceId = requiredString(input?.personalSpaceId, 'personalSpaceId');
|
|
15
|
+
const personalProjectId = requiredString(input?.personalProjectId, 'personalProjectId');
|
|
16
|
+
const query = requiredString(input?.query, 'query');
|
|
17
|
+
const limit = positiveInteger(input?.limit ?? 12, 'limit', 100);
|
|
18
|
+
const projectIds = stringArray(input?.projectIds, 'projectIds', 32);
|
|
19
|
+
const contextPersonalProjectIds = stringArray(
|
|
20
|
+
input?.contextPersonalProjectIds,
|
|
21
|
+
'contextPersonalProjectIds',
|
|
22
|
+
15
|
|
23
|
+
);
|
|
24
|
+
const [graph, bindings] = await Promise.all([
|
|
25
|
+
this.app.searchKnowledge({
|
|
26
|
+
personalSpaceId,
|
|
27
|
+
personalProjectId,
|
|
28
|
+
projectIds,
|
|
29
|
+
contextPersonalProjectIds,
|
|
30
|
+
personalProjectScope: input.personalProjectScope ?? 'bounded',
|
|
31
|
+
query,
|
|
32
|
+
limit,
|
|
33
|
+
includeHistorical: input.includeHistorical === true,
|
|
34
|
+
includePending: input.includePending !== false,
|
|
35
|
+
agentInvocation: true,
|
|
36
|
+
agentToolName: 'search_connected_knowledge'
|
|
37
|
+
}),
|
|
38
|
+
this.externalKnowledge.listBindings()
|
|
39
|
+
]);
|
|
40
|
+
const selected = bindings.flatMap((binding) =>
|
|
41
|
+
binding.status === 'ready'
|
|
42
|
+
? bindingTargets(binding)
|
|
43
|
+
.filter((target) =>
|
|
44
|
+
target.status !== 'error' &&
|
|
45
|
+
target.mode !== 'mirror' &&
|
|
46
|
+
target.personalSpaceId === personalSpaceId &&
|
|
47
|
+
target.personalProjectId === personalProjectId
|
|
48
|
+
)
|
|
49
|
+
.map((target) => ({ binding, target }))
|
|
50
|
+
: []
|
|
51
|
+
);
|
|
52
|
+
const retrieved = await Promise.allSettled(selected.map(({ binding, target }) =>
|
|
53
|
+
this.externalKnowledge.retrieveBinding(binding.id, {
|
|
54
|
+
query,
|
|
55
|
+
limit,
|
|
56
|
+
personalSpaceId: target.personalSpaceId,
|
|
57
|
+
personalProjectId: target.personalProjectId
|
|
58
|
+
})
|
|
59
|
+
));
|
|
60
|
+
const external = [];
|
|
61
|
+
const sourceErrors = [];
|
|
62
|
+
for (const [index, outcome] of retrieved.entries()) {
|
|
63
|
+
const { binding, target } = selected[index];
|
|
64
|
+
const identity = {
|
|
65
|
+
id: binding.id,
|
|
66
|
+
name: binding.name,
|
|
67
|
+
connectorType: binding.connectorType,
|
|
68
|
+
targetId: target.id,
|
|
69
|
+
mode: target.mode
|
|
70
|
+
};
|
|
71
|
+
if (outcome.status === 'fulfilled') {
|
|
72
|
+
external.push({
|
|
73
|
+
binding: identity,
|
|
74
|
+
items: outcome.value.items ?? [],
|
|
75
|
+
skippedCredentials: outcome.value.skippedCredentials ?? 0
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
sourceErrors.push({
|
|
79
|
+
binding: identity,
|
|
80
|
+
error: safeExternalSourceDiagnostic(outcome.reason)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const conflictPolicy = this.policies.get(personalProjectId);
|
|
85
|
+
return {
|
|
86
|
+
query,
|
|
87
|
+
graph,
|
|
88
|
+
external,
|
|
89
|
+
sourceErrors,
|
|
90
|
+
sourceScopes: {
|
|
91
|
+
personalProjectId,
|
|
92
|
+
publicProjectIds: projectIds,
|
|
93
|
+
publicSpaceStatus: projectIds.length ? 'beta' : 'not_requested'
|
|
94
|
+
},
|
|
95
|
+
conflictPolicy,
|
|
96
|
+
conflictAssessmentRequired: requiresConflictAssessment({
|
|
97
|
+
graph,
|
|
98
|
+
external,
|
|
99
|
+
publicProjectIds: projectIds
|
|
100
|
+
}),
|
|
101
|
+
conflictGuidance: guidanceFor(conflictPolicy.mode)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getConflictPolicy({ personalProjectId }) {
|
|
106
|
+
return this.policies.get(personalProjectId);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async updateConflictPolicy({ personalSpaceId, personalProjectId, mode }) {
|
|
110
|
+
const projects = await this.app.listPersonalProjects({ personalSpaceId });
|
|
111
|
+
if (!projects.some(({ project_id: id, id: fallback }) =>
|
|
112
|
+
(id ?? fallback) === personalProjectId
|
|
113
|
+
)) {
|
|
114
|
+
throw new TypeError('Conflict policy target must be an existing personal project');
|
|
115
|
+
}
|
|
116
|
+
return this.policies.set(personalProjectId, mode);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function bindingTargets(binding) {
|
|
121
|
+
if (Array.isArray(binding.targets)) return binding.targets;
|
|
122
|
+
return binding.target ? [{
|
|
123
|
+
id: binding.id,
|
|
124
|
+
...binding.target,
|
|
125
|
+
mode: binding.mode,
|
|
126
|
+
status: binding.status
|
|
127
|
+
}] : [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function guidanceFor(mode) {
|
|
131
|
+
return mode === 'agent_decide'
|
|
132
|
+
? 'When sources materially conflict, the Agent may decide for this response only. Explain the basis and source provenance; never rewrite, confirm, or invalidate underlying knowledge automatically.'
|
|
133
|
+
: 'When sources materially conflict, surface the conflict in the Agent conversation and ask the user before selecting durable truth. Do not rewrite any source.';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function hasGraphEvidence(graph) {
|
|
137
|
+
return (graph?.facts?.length ?? 0) > 0 || (graph?.entities?.length ?? 0) > 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function requiresConflictAssessment({ graph, external, publicProjectIds }) {
|
|
141
|
+
const graphHasEvidence = hasGraphEvidence(graph);
|
|
142
|
+
const liveSourceCount = external.filter(({ items }) => items.length > 0).length;
|
|
143
|
+
return (graphHasEvidence ? 1 : 0) + liveSourceCount > 1 ||
|
|
144
|
+
(graphHasEvidence && publicProjectIds.length > 0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function stringArray(value, label, maximum) {
|
|
148
|
+
if (value === undefined || value === null) return [];
|
|
149
|
+
if (!Array.isArray(value) || value.length > maximum ||
|
|
150
|
+
value.some((item) => typeof item !== 'string' || !item.trim())) {
|
|
151
|
+
throw new TypeError(`${label} must be an array of at most ${maximum} nonempty strings`);
|
|
152
|
+
}
|
|
153
|
+
return [...new Set(value.map((item) => item.trim()))];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function positiveInteger(value, label, maximum) {
|
|
157
|
+
if (!Number.isInteger(value) || value <= 0 || value > maximum) {
|
|
158
|
+
throw new TypeError(`${label} must be a positive integer no greater than ${maximum}`);
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function requiredString(value, label) {
|
|
164
|
+
if (typeof value !== 'string' || !value.trim()) throw new TypeError(`${label} is required`);
|
|
165
|
+
return value.trim();
|
|
166
|
+
}
|