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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import {
|
|
4
4
|
chmodSync,
|
|
@@ -21,42 +21,55 @@ import {
|
|
|
21
21
|
lanConsoleUrls
|
|
22
22
|
} from '../server/lan-access.js';
|
|
23
23
|
import { readJsonFile, writeJsonFileAtomic } from '../storage/json-file.js';
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_RUNTIME_SETTINGS,
|
|
26
|
+
managedProviderUrls,
|
|
27
|
+
normalizeRuntimeSettings,
|
|
28
|
+
runtimeSettingsWithOverrides,
|
|
29
|
+
writeRuntimeSettings
|
|
30
|
+
} from '../system/runtime-settings.js';
|
|
24
31
|
|
|
25
32
|
export {
|
|
26
33
|
dockerInfoIndicatesDaemon,
|
|
27
34
|
selectDockerEnvironment
|
|
28
35
|
} from './container-runtime.js';
|
|
29
36
|
|
|
30
|
-
const PERSONAL_PROVIDER_URL = 'http://127.0.0.1:8787';
|
|
31
|
-
const WORKSPACE_PROVIDER_URL = 'http://127.0.0.1:8788';
|
|
32
|
-
|
|
33
|
-
export const MANAGED_PERSONAL_PROVIDER_URL = PERSONAL_PROVIDER_URL;
|
|
34
|
-
export const MANAGED_WORKSPACE_PROVIDER_URL = WORKSPACE_PROVIDER_URL;
|
|
35
|
-
|
|
36
37
|
export async function ensureGraphRuntime(input, dependencies = {}) {
|
|
37
38
|
const deps = graphDependencies(dependencies);
|
|
38
|
-
const
|
|
39
|
+
const runtimeSettings = normalizeRuntimeSettings(input.runtimeSettings ??
|
|
40
|
+
runtimeSettingsWithOverrides(DEFAULT_RUNTIME_SETTINGS, {
|
|
41
|
+
consolePort: input.port,
|
|
42
|
+
lanAccess: input.lan === true
|
|
43
|
+
}));
|
|
44
|
+
const urls = managedProviderUrls(runtimeSettings);
|
|
45
|
+
const lan = runtimeSettings.lanAccess;
|
|
39
46
|
const lanAddresses = lan ? deps.discoverLanAddresses() : [];
|
|
40
47
|
if (lan && lanAddresses.length === 0) {
|
|
41
|
-
throw new Error('
|
|
48
|
+
throw new Error('No private IPv4 LAN address is available, so LAN mode cannot start.');
|
|
42
49
|
}
|
|
43
50
|
const containerRuntime = await deps.ensureContainerRuntime({
|
|
44
51
|
env: input.env ?? process.env,
|
|
45
52
|
onProgress: input.onProgress
|
|
46
53
|
});
|
|
47
54
|
deps.ensureDirectory(input.paths.dataDir);
|
|
48
|
-
const secrets =
|
|
55
|
+
const secrets = ensureProviderEnvironment(
|
|
56
|
+
input.paths.graphEnvPath,
|
|
57
|
+
runtimeSettings,
|
|
58
|
+
deps
|
|
59
|
+
);
|
|
60
|
+
deps.writeRuntimeSettings(input.paths.runtimeSettingsPath, runtimeSettings);
|
|
49
61
|
deps.startProviders(input.paths, input.paths.graphEnvPath, {
|
|
50
62
|
personalOnly: input.personalOnly === true,
|
|
51
63
|
containerRuntime,
|
|
52
64
|
...(input.buildProviders === false ? { build: false } : {})
|
|
53
65
|
});
|
|
54
|
-
await waitForProvider(
|
|
55
|
-
if (!input.personalOnly) await waitForProvider(
|
|
66
|
+
await waitForProvider(urls.personal, deps.fetch);
|
|
67
|
+
if (!input.personalOnly) await waitForProvider(urls.workspace, deps.fetch);
|
|
56
68
|
|
|
57
69
|
let config = deps.readConfig(input.paths.graphRuntimeConfigPath);
|
|
58
70
|
if (!config) {
|
|
59
71
|
config = await bootstrapGraph({
|
|
72
|
+
urls,
|
|
60
73
|
personalSpaceName: input.personalSpaceName,
|
|
61
74
|
secrets,
|
|
62
75
|
personalOnly: input.personalOnly === true,
|
|
@@ -64,8 +77,22 @@ export async function ensureGraphRuntime(input, dependencies = {}) {
|
|
|
64
77
|
});
|
|
65
78
|
deps.writeConfig(input.paths.graphRuntimeConfigPath, config);
|
|
66
79
|
deps.secureFile(input.paths.graphRuntimeConfigPath);
|
|
80
|
+
} else {
|
|
81
|
+
const synchronized = synchronizeManagedProviderUrls(config, urls, {
|
|
82
|
+
personalOnly: input.personalOnly === true
|
|
83
|
+
});
|
|
84
|
+
if (synchronized.changed) {
|
|
85
|
+
await reconcileManagedWorkspaceSubscriptions({
|
|
86
|
+
previousConfig: config,
|
|
87
|
+
nextConfig: synchronized.config,
|
|
88
|
+
personalUrl: urls.personal,
|
|
89
|
+
fetchImpl: deps.fetch
|
|
90
|
+
});
|
|
91
|
+
config = synchronized.config;
|
|
92
|
+
deps.writeConfig(input.paths.graphRuntimeConfigPath, config);
|
|
93
|
+
deps.secureFile(input.paths.graphRuntimeConfigPath);
|
|
94
|
+
}
|
|
67
95
|
}
|
|
68
|
-
stopOwnedLegacyRuntime(input.paths, deps);
|
|
69
96
|
|
|
70
97
|
if (input.noStart) return { status: 'initialized', url: null, pid: null };
|
|
71
98
|
const existing = deps.readState(input.paths.graphRuntimeStatePath);
|
|
@@ -73,7 +100,7 @@ export async function ensureGraphRuntime(input, dependencies = {}) {
|
|
|
73
100
|
const healthy = await deps.webHealth(existing.url, existing.pid, existing.version);
|
|
74
101
|
if (
|
|
75
102
|
healthy &&
|
|
76
|
-
existing.port ===
|
|
103
|
+
existing.port === runtimeSettings.ports.console &&
|
|
77
104
|
exposureMatches(existing, { lan, lanAddresses })
|
|
78
105
|
) {
|
|
79
106
|
return runtimeResult('running', existing);
|
|
@@ -88,25 +115,26 @@ export async function ensureGraphRuntime(input, dependencies = {}) {
|
|
|
88
115
|
const child = deps.spawnWebRuntime({
|
|
89
116
|
paths: input.paths,
|
|
90
117
|
nodePath: process.execPath,
|
|
91
|
-
port:
|
|
118
|
+
port: runtimeSettings.ports.console,
|
|
92
119
|
lan,
|
|
93
120
|
lanAccessToken,
|
|
94
121
|
env: input.env ?? process.env
|
|
95
122
|
});
|
|
96
|
-
const url = `http://127.0.0.1:${
|
|
123
|
+
const url = `http://127.0.0.1:${runtimeSettings.ports.console}`;
|
|
97
124
|
if (!Number.isInteger(child.pid) || !await waitForWeb(url, child.pid, deps)) {
|
|
98
125
|
if (Number.isInteger(child.pid)) deps.stopProcess(child.pid);
|
|
99
126
|
throw new Error('Fuli Graphiti console could not start');
|
|
100
127
|
}
|
|
101
128
|
const state = {
|
|
102
|
-
version:
|
|
129
|
+
version: 4,
|
|
103
130
|
pid: child.pid,
|
|
104
131
|
url,
|
|
105
|
-
port:
|
|
132
|
+
port: runtimeSettings.ports.console,
|
|
133
|
+
runtimeSettings,
|
|
106
134
|
...(lan
|
|
107
135
|
? {
|
|
108
136
|
lan: true,
|
|
109
|
-
lanUrls: lanConsoleUrls(lanAddresses,
|
|
137
|
+
lanUrls: lanConsoleUrls(lanAddresses, runtimeSettings.ports.console)
|
|
110
138
|
}
|
|
111
139
|
: {}),
|
|
112
140
|
managedProviders: input.personalOnly
|
|
@@ -119,28 +147,35 @@ export async function ensureGraphRuntime(input, dependencies = {}) {
|
|
|
119
147
|
return runtimeResult('started', state, lanAccessToken);
|
|
120
148
|
}
|
|
121
149
|
|
|
122
|
-
function
|
|
123
|
-
|
|
150
|
+
function ensureProviderEnvironment(path, settings, deps) {
|
|
151
|
+
const previous = deps.fileExists(path) ? parseEnv(deps.readText(path)) : {};
|
|
124
152
|
const values = {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
153
|
+
...previous,
|
|
154
|
+
FULI_PERSONAL_NEO4J_PASSWORD: previous.FULI_PERSONAL_NEO4J_PASSWORD ?? secret(),
|
|
155
|
+
FULI_WORKSPACE_NEO4J_PASSWORD: previous.FULI_WORKSPACE_NEO4J_PASSWORD ?? secret(),
|
|
156
|
+
FULI_PERSONAL_BOOTSTRAP_TOKEN: previous.FULI_PERSONAL_BOOTSTRAP_TOKEN ?? secret(),
|
|
157
|
+
FULI_WORKSPACE_BOOTSTRAP_TOKEN: previous.FULI_WORKSPACE_BOOTSTRAP_TOKEN ?? secret(),
|
|
158
|
+
FULI_PERSONAL_NEO4J_HTTP_PORT: String(settings.ports.personalNeo4jHttp),
|
|
159
|
+
FULI_PERSONAL_NEO4J_BOLT_PORT: String(settings.ports.personalNeo4jBolt),
|
|
160
|
+
FULI_WORKSPACE_NEO4J_HTTP_PORT: String(settings.ports.workspaceNeo4jHttp),
|
|
161
|
+
FULI_WORKSPACE_NEO4J_BOLT_PORT: String(settings.ports.workspaceNeo4jBolt),
|
|
162
|
+
FULI_PERSONAL_PROVIDER_PORT: String(settings.ports.personalProvider),
|
|
163
|
+
FULI_WORKSPACE_PROVIDER_PORT: String(settings.ports.workspaceProvider)
|
|
129
164
|
};
|
|
130
165
|
const body = Object.entries(values).map(([key, value]) => `${key}=${value}`).join('\n') + '\n';
|
|
131
|
-
deps.writeText(path, body);
|
|
166
|
+
if (!deps.fileExists(path) || deps.readText(path) !== body) deps.writeText(path, body);
|
|
132
167
|
deps.secureFile(path);
|
|
133
168
|
return values;
|
|
134
169
|
}
|
|
135
170
|
|
|
136
|
-
async function bootstrapGraph({ personalSpaceName, secrets, personalOnly, fetchImpl }) {
|
|
171
|
+
async function bootstrapGraph({ urls, personalSpaceName, secrets, personalOnly, fetchImpl }) {
|
|
137
172
|
const personalIdentity = await bootstrapProvider(
|
|
138
|
-
|
|
173
|
+
urls.personal,
|
|
139
174
|
secrets.FULI_PERSONAL_BOOTSTRAP_TOKEN,
|
|
140
175
|
personalSpaceName,
|
|
141
176
|
fetchImpl
|
|
142
177
|
);
|
|
143
|
-
const personalSpace = await findOrCreateSpace(
|
|
178
|
+
const personalSpace = await findOrCreateSpace(urls.personal, {
|
|
144
179
|
token: personalIdentity.access_token,
|
|
145
180
|
name: personalSpaceName,
|
|
146
181
|
kind: 'personal',
|
|
@@ -149,7 +184,7 @@ async function bootstrapGraph({ personalSpaceName, secrets, personalOnly, fetchI
|
|
|
149
184
|
const config = {
|
|
150
185
|
version: 1,
|
|
151
186
|
personal: {
|
|
152
|
-
providerUrl:
|
|
187
|
+
providerUrl: urls.personal,
|
|
153
188
|
accessToken: personalIdentity.access_token,
|
|
154
189
|
principalId: personalIdentity.principal_id,
|
|
155
190
|
spaceId: personalSpace.id
|
|
@@ -159,35 +194,108 @@ async function bootstrapGraph({ personalSpaceName, secrets, personalOnly, fetchI
|
|
|
159
194
|
if (personalOnly) return config;
|
|
160
195
|
|
|
161
196
|
const workspaceIdentity = await bootstrapProvider(
|
|
162
|
-
|
|
197
|
+
urls.workspace,
|
|
163
198
|
secrets.FULI_WORKSPACE_BOOTSTRAP_TOKEN,
|
|
164
199
|
personalSpaceName,
|
|
165
200
|
fetchImpl
|
|
166
201
|
);
|
|
167
|
-
const project = await findOrCreateSpace(
|
|
202
|
+
const project = await findOrCreateSpace(urls.workspace, {
|
|
168
203
|
token: workspaceIdentity.access_token,
|
|
169
204
|
name: '工作',
|
|
170
205
|
kind: 'project',
|
|
171
206
|
fetchImpl
|
|
172
207
|
});
|
|
173
|
-
await providerRequest(
|
|
208
|
+
await providerRequest(urls.personal, '/v1/subscriptions', {
|
|
174
209
|
token: personalIdentity.access_token,
|
|
175
210
|
body: {
|
|
176
211
|
personal_space_id: personalSpace.id,
|
|
177
212
|
project_id: project.id,
|
|
178
|
-
provider_url:
|
|
213
|
+
provider_url: urls.workspace,
|
|
179
214
|
project_name: project.name
|
|
180
215
|
},
|
|
181
216
|
fetchImpl
|
|
182
217
|
});
|
|
183
218
|
config.workspaces.push({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
219
|
+
providerUrl: urls.workspace,
|
|
220
|
+
accessToken: workspaceIdentity.access_token,
|
|
221
|
+
principalId: workspaceIdentity.principal_id,
|
|
222
|
+
managedDevelopment: true
|
|
187
223
|
});
|
|
188
224
|
return config;
|
|
189
225
|
}
|
|
190
226
|
|
|
227
|
+
export function synchronizeManagedProviderUrls(config, urls, { personalOnly = true } = {}) {
|
|
228
|
+
const next = structuredClone(config);
|
|
229
|
+
let changed = next.personal?.providerUrl !== urls.personal;
|
|
230
|
+
next.personal.providerUrl = urls.personal;
|
|
231
|
+
if (!personalOnly) {
|
|
232
|
+
for (const workspace of next.workspaces ?? []) {
|
|
233
|
+
if (!isManagedWorkspace(workspace)) continue;
|
|
234
|
+
if (workspace.providerUrl !== urls.workspace || workspace.managedDevelopment !== true) {
|
|
235
|
+
changed = true;
|
|
236
|
+
}
|
|
237
|
+
workspace.providerUrl = urls.workspace;
|
|
238
|
+
workspace.managedDevelopment = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { config: next, changed };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function reconcileManagedWorkspaceSubscriptions({
|
|
245
|
+
previousConfig,
|
|
246
|
+
nextConfig,
|
|
247
|
+
personalUrl,
|
|
248
|
+
fetchImpl
|
|
249
|
+
}) {
|
|
250
|
+
const previous = (previousConfig.workspaces ?? []).find(isManagedWorkspace);
|
|
251
|
+
const next = (nextConfig.workspaces ?? []).find(isManagedWorkspace);
|
|
252
|
+
if (!previous || !next || previous.providerUrl === next.providerUrl) return;
|
|
253
|
+
const query = new URLSearchParams({ personal_space_id: nextConfig.personal.spaceId });
|
|
254
|
+
const response = await fetchImpl(`${personalUrl}/v1/subscriptions?${query}`, {
|
|
255
|
+
headers: { authorization: `Bearer ${nextConfig.personal.accessToken}` }
|
|
256
|
+
});
|
|
257
|
+
if (!response.ok) throw new Error('Could not reconcile managed workspace subscriptions');
|
|
258
|
+
const subscriptions = await response.json();
|
|
259
|
+
for (const subscription of subscriptions.filter(
|
|
260
|
+
(item) => item.provider_url === previous.providerUrl
|
|
261
|
+
)) {
|
|
262
|
+
const removal = new URLSearchParams({
|
|
263
|
+
personal_space_id: nextConfig.personal.spaceId,
|
|
264
|
+
provider_url: previous.providerUrl
|
|
265
|
+
});
|
|
266
|
+
const removed = await fetchImpl(
|
|
267
|
+
`${personalUrl}/v1/subscriptions/${encodeURIComponent(subscription.project_id)}?${removal}`,
|
|
268
|
+
{
|
|
269
|
+
method: 'DELETE',
|
|
270
|
+
headers: { authorization: `Bearer ${nextConfig.personal.accessToken}` }
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
if (!removed.ok) throw new Error('Could not remove the previous managed workspace port');
|
|
274
|
+
await providerRequest(personalUrl, '/v1/subscriptions', {
|
|
275
|
+
token: nextConfig.personal.accessToken,
|
|
276
|
+
body: {
|
|
277
|
+
personal_space_id: nextConfig.personal.spaceId,
|
|
278
|
+
project_id: subscription.project_id,
|
|
279
|
+
provider_url: next.providerUrl,
|
|
280
|
+
project_name: subscription.project_name
|
|
281
|
+
},
|
|
282
|
+
fetchImpl
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isManagedWorkspace(workspace) {
|
|
288
|
+
if (workspace?.managedDevelopment === true) return true;
|
|
289
|
+
try {
|
|
290
|
+
const url = new URL(workspace?.providerUrl);
|
|
291
|
+
return url.protocol === 'http:' &&
|
|
292
|
+
['127.0.0.1', 'localhost', '::1', '[::1]'].includes(url.hostname) &&
|
|
293
|
+
url.port === '8788';
|
|
294
|
+
} catch {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
191
299
|
async function bootstrapProvider(url, bootstrapToken, principalName, fetchImpl) {
|
|
192
300
|
const response = await fetchImpl(`${url}/v1/bootstrap`, {
|
|
193
301
|
method: 'POST',
|
|
@@ -246,7 +354,7 @@ async function waitForProvider(url, fetchImpl) {
|
|
|
246
354
|
async function waitForWeb(url, pid, deps) {
|
|
247
355
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
248
356
|
if (!deps.isProcessAlive(pid)) return false;
|
|
249
|
-
if (await deps.webHealth(url, pid,
|
|
357
|
+
if (await deps.webHealth(url, pid, 4)) return true;
|
|
250
358
|
await delay(100);
|
|
251
359
|
}
|
|
252
360
|
return false;
|
|
@@ -300,13 +408,6 @@ function spawnWebRuntime(input) {
|
|
|
300
408
|
}
|
|
301
409
|
}
|
|
302
410
|
|
|
303
|
-
function stopOwnedLegacyRuntime(paths, deps) {
|
|
304
|
-
deps.stopLegacyService();
|
|
305
|
-
const state = deps.readState(paths.statePath);
|
|
306
|
-
if (state?.version !== 1 || state.dbPath !== paths.dbPath || !Number.isInteger(state.pid)) return;
|
|
307
|
-
if (deps.isProcessAlive(state.pid)) deps.stopProcess(state.pid);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
411
|
function graphDependencies(overrides) {
|
|
311
412
|
return {
|
|
312
413
|
fetch: globalThis.fetch,
|
|
@@ -317,6 +418,7 @@ function graphDependencies(overrides) {
|
|
|
317
418
|
ensureDirectory: (path) => mkdirSync(path, { recursive: true }),
|
|
318
419
|
readConfig: (path) => readJsonFile(path, null),
|
|
319
420
|
writeConfig: writeJsonFileAtomic,
|
|
421
|
+
writeRuntimeSettings,
|
|
320
422
|
readState: (path) => readJsonFile(path, null),
|
|
321
423
|
writeState: writeJsonFileAtomic,
|
|
322
424
|
ensureContainerRuntime,
|
|
@@ -324,7 +426,6 @@ function graphDependencies(overrides) {
|
|
|
324
426
|
spawnWebRuntime,
|
|
325
427
|
isProcessAlive,
|
|
326
428
|
stopProcess,
|
|
327
|
-
stopLegacyService,
|
|
328
429
|
webHealth: checkLocalConsoleHealth,
|
|
329
430
|
discoverLanAddresses,
|
|
330
431
|
createLanAccessToken: secret,
|
|
@@ -356,7 +457,7 @@ export async function checkLocalConsoleHealth(url, expectedPid, stateVersion = 3
|
|
|
356
457
|
}
|
|
357
458
|
|
|
358
459
|
function isGraphRuntimeState(state) {
|
|
359
|
-
return (state?.version === 2 || state?.version === 3) &&
|
|
460
|
+
return (state?.version === 2 || state?.version === 3 || state?.version === 4) &&
|
|
360
461
|
Number.isInteger(state.pid) && typeof state.url === 'string';
|
|
361
462
|
}
|
|
362
463
|
|
|
@@ -384,15 +485,6 @@ function runtimeResult(status, state, lanAccessToken = null) {
|
|
|
384
485
|
};
|
|
385
486
|
}
|
|
386
487
|
|
|
387
|
-
function stopLegacyService() {
|
|
388
|
-
if (process.platform !== 'darwin') return;
|
|
389
|
-
spawnSync('launchctl', ['remove', 'dev.jevonscode.fuli.local'], {
|
|
390
|
-
encoding: 'utf8',
|
|
391
|
-
windowsHide: true,
|
|
392
|
-
stdio: 'ignore'
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
|
|
396
488
|
function parseEnv(body) {
|
|
397
489
|
return Object.fromEntries(body.split(/\r?\n/).filter(Boolean).map((line) => {
|
|
398
490
|
const index = line.indexOf('=');
|
|
@@ -4,14 +4,14 @@ export function assertSupportedNodeVersion(version = process.versions.node) {
|
|
|
4
4
|
const parsed = parseNodeVersion(version);
|
|
5
5
|
if (compareVersions(parsed, MINIMUM_NODE_VERSION) >= 0) return;
|
|
6
6
|
throw new Error(
|
|
7
|
-
`fl setup
|
|
8
|
-
|
|
7
|
+
`fl setup requires Node.js 24.12 or later; the current version is ${version}. ` +
|
|
8
|
+
'Upgrade Node.js, then run fl setup again.'
|
|
9
9
|
);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function parseNodeVersion(value) {
|
|
13
13
|
const match = String(value).match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
14
|
-
if (!match) throw new Error(
|
|
14
|
+
if (!match) throw new Error(`Could not parse Node.js version: ${value}`);
|
|
15
15
|
return match.slice(1).map(Number);
|
|
16
16
|
}
|
|
17
17
|
|
package/src/setup/options.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { DEFAULT_FULI_PORT } from '../defaults.js';
|
|
2
|
-
|
|
3
1
|
const VALUE_OPTIONS = Object.freeze({
|
|
4
2
|
'--data-dir': 'dataDir',
|
|
5
3
|
'--personal-space': 'personalSpaceName',
|
|
@@ -26,8 +24,8 @@ export function parseUpdateOptions(args = []) {
|
|
|
26
24
|
function parseSetupLikeOptions(args, command) {
|
|
27
25
|
const result = {
|
|
28
26
|
dataDir: null,
|
|
29
|
-
personalSpaceName: '
|
|
30
|
-
port:
|
|
27
|
+
personalSpaceName: 'Personal',
|
|
28
|
+
port: null,
|
|
31
29
|
yes: false,
|
|
32
30
|
codexOnly: false,
|
|
33
31
|
skipAgents: false,
|
package/src/setup/paths.js
CHANGED
|
@@ -16,19 +16,34 @@ export function resolveSetupPaths({
|
|
|
16
16
|
const root = resolve(packageRoot);
|
|
17
17
|
return {
|
|
18
18
|
dataDir: resolvedDataDir,
|
|
19
|
-
dbPath: join(resolvedDataDir, 'context.db'),
|
|
20
19
|
graphEnvPath: join(resolvedDataDir, 'graph-provider.env'),
|
|
21
20
|
graphRuntimeConfigPath: join(resolvedDataDir, 'graph-runtime.json'),
|
|
22
21
|
graphRuntimeStatePath: join(resolvedDataDir, 'graph-runtime-state.json'),
|
|
22
|
+
runtimeSettingsPath: join(resolvedDataDir, 'runtime-settings.json'),
|
|
23
|
+
externalKnowledgeRegistryPath: join(
|
|
24
|
+
resolvedDataDir,
|
|
25
|
+
'external-knowledge',
|
|
26
|
+
'bindings.json'
|
|
27
|
+
),
|
|
28
|
+
externalKnowledgeConflictPolicyPath: join(
|
|
29
|
+
resolvedDataDir,
|
|
30
|
+
'external-knowledge',
|
|
31
|
+
'conflict-policies.json'
|
|
32
|
+
),
|
|
33
|
+
externalKnowledgeConnectorDir: join(
|
|
34
|
+
resolvedDataDir,
|
|
35
|
+
'external-knowledge',
|
|
36
|
+
'connectors'
|
|
37
|
+
),
|
|
23
38
|
backupDir: join(resolvedDataDir, 'backups', 'agents'),
|
|
24
39
|
logPath: join(resolvedDataDir, 'logs', 'runtime.log'),
|
|
25
|
-
statePath: join(resolvedDataDir, 'runtime.json'),
|
|
26
40
|
serverPath: join(root, 'src', 'server.js'),
|
|
27
41
|
mcpServerPath: join(root, 'src', 'mcp-server.js'),
|
|
28
42
|
graphSetupPath: join(root, 'src', 'graphiti', 'setup.js'),
|
|
29
43
|
graphComposePath: join(root, 'compose.graphiti.yml'),
|
|
30
44
|
sessionSkillPath: join(root, 'skills', 'capturing-session-knowledge'),
|
|
31
|
-
projectSkillPath: join(root, 'skills', 'grilling-project')
|
|
45
|
+
projectSkillPath: join(root, 'skills', 'grilling-project'),
|
|
46
|
+
reviewSkillPath: join(root, 'skills', 'flreview')
|
|
32
47
|
};
|
|
33
48
|
}
|
|
34
49
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const REVIEW_SKILL_NAME = 'flreview';
|
|
2
|
+
|
|
3
|
+
// Keep every host-specific difference for the review Skill here. The shared
|
|
4
|
+
// SKILL.md intentionally contains no Codex/Claude/Cursor branching.
|
|
5
|
+
const ADAPTERS = Object.freeze({
|
|
6
|
+
codex: Object.freeze({
|
|
7
|
+
root: ['.agents', 'skills'],
|
|
8
|
+
nativeInvocation: '$flreview',
|
|
9
|
+
needsCommandBridge: true
|
|
10
|
+
}),
|
|
11
|
+
'claude-code': Object.freeze({
|
|
12
|
+
root: ['.claude', 'skills'],
|
|
13
|
+
nativeInvocation: '/flreview',
|
|
14
|
+
needsCommandBridge: false
|
|
15
|
+
}),
|
|
16
|
+
cursor: Object.freeze({
|
|
17
|
+
root: ['.cursor', 'skills'],
|
|
18
|
+
nativeInvocation: '/flreview',
|
|
19
|
+
needsCommandBridge: false
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export function adaptAgentForReviewSkill(agent, { homeDir, pathApi }) {
|
|
24
|
+
const adapter = adapterFor(agent.id);
|
|
25
|
+
return {
|
|
26
|
+
...agent,
|
|
27
|
+
reviewSkillPath: pathApi.join(homeDir, ...adapter.root, REVIEW_SKILL_NAME),
|
|
28
|
+
reviewSkillTrigger: reviewSkillTrigger(agent.id)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function reviewSkillTrigger(agentId) {
|
|
33
|
+
const adapter = adapterFor(agentId);
|
|
34
|
+
return {
|
|
35
|
+
userCommand: '/flreview',
|
|
36
|
+
nativeInvocation: adapter.nativeInvocation,
|
|
37
|
+
needsCommandBridge: adapter.needsCommandBridge
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function codexReviewCommandBridge() {
|
|
42
|
+
return 'Exact `/flreview`: load installed `flreview` Skill.';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function adapterFor(agentId) {
|
|
46
|
+
const adapter = ADAPTERS[agentId];
|
|
47
|
+
if (!adapter) throw new TypeError(`Unsupported review Skill agent: ${agentId}`);
|
|
48
|
+
return adapter;
|
|
49
|
+
}
|
package/src/setup/setup.js
CHANGED
|
@@ -4,27 +4,41 @@ import { connectAgent, discoverAgents } from './agents.js';
|
|
|
4
4
|
import { resolveSetupPaths } from './paths.js';
|
|
5
5
|
import { ensureGraphRuntime } from './graph-runtime.js';
|
|
6
6
|
import { installSessionSkill } from './session-skill.js';
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_RUNTIME_SETTINGS,
|
|
9
|
+
readRuntimeSettings,
|
|
10
|
+
runtimeSettingsWithOverrides
|
|
11
|
+
} from '../system/runtime-settings.js';
|
|
7
12
|
|
|
8
13
|
export function planLocalSetup(options, {
|
|
9
14
|
env = process.env,
|
|
10
15
|
resolvePaths = resolveSetupPaths,
|
|
11
16
|
discover = discoverAgents,
|
|
12
17
|
inspectInstallations = inspectAgentInstallations,
|
|
18
|
+
readSettings = readRuntimeSettings,
|
|
13
19
|
nodePath = process.execPath
|
|
14
20
|
} = {}) {
|
|
15
21
|
const paths = resolvePaths({ dataDir: options.dataDir, env });
|
|
22
|
+
const savedRuntimeSettings = paths.runtimeSettingsPath
|
|
23
|
+
? readSettings(paths.runtimeSettingsPath)
|
|
24
|
+
: DEFAULT_RUNTIME_SETTINGS;
|
|
25
|
+
const runtimeSettings = runtimeSettingsWithOverrides(savedRuntimeSettings, {
|
|
26
|
+
consolePort: options.port
|
|
27
|
+
});
|
|
16
28
|
const discovered = options.skipAgents ? [] : discover({ env });
|
|
17
29
|
const agents = options.codexOnly
|
|
18
30
|
? discovered.filter(({ id }) => id === 'codex')
|
|
19
31
|
: discovered;
|
|
20
32
|
return {
|
|
21
33
|
paths,
|
|
34
|
+
runtimeSettings,
|
|
22
35
|
agents: inspectInstallations(agents, {
|
|
23
36
|
nodePath,
|
|
24
37
|
mcpServerPath: paths.mcpServerPath,
|
|
25
38
|
runtimeConfigPath: paths.graphRuntimeConfigPath,
|
|
26
39
|
sessionSkillPath: paths.sessionSkillPath,
|
|
27
|
-
projectSkillPath: paths.projectSkillPath
|
|
40
|
+
projectSkillPath: paths.projectSkillPath,
|
|
41
|
+
reviewSkillPath: paths.reviewSkillPath
|
|
28
42
|
})
|
|
29
43
|
};
|
|
30
44
|
}
|
|
@@ -34,10 +48,16 @@ export async function applyLocalSetup(plan, options, dependencies = {}) {
|
|
|
34
48
|
const backupConfig = dependencies.backupConfig ?? backupAgentConfig;
|
|
35
49
|
const connect = dependencies.connect ?? connectAgent;
|
|
36
50
|
const installSkill = dependencies.installSkill ?? installSessionSkill;
|
|
51
|
+
const runtimeSettings = plan.runtimeSettings ?? runtimeSettingsWithOverrides(
|
|
52
|
+
DEFAULT_RUNTIME_SETTINGS,
|
|
53
|
+
{ consolePort: options.port }
|
|
54
|
+
);
|
|
37
55
|
const runtime = await ensureRuntime({
|
|
38
56
|
paths: plan.paths,
|
|
39
57
|
personalSpaceName: options.personalSpaceName,
|
|
40
|
-
port:
|
|
58
|
+
port: runtimeSettings.ports.console,
|
|
59
|
+
lan: runtimeSettings.lanAccess,
|
|
60
|
+
runtimeSettings,
|
|
41
61
|
personalOnly: options.personalOnly,
|
|
42
62
|
noStart: options.noStart,
|
|
43
63
|
env: dependencies.env ?? process.env,
|
|
@@ -64,6 +84,10 @@ export async function applyLocalSetup(plan, options, dependencies = {}) {
|
|
|
64
84
|
installSkill({ ...agent, skillPath: agent.projectSkillPath }, {
|
|
65
85
|
sourcePath: plan.paths.projectSkillPath,
|
|
66
86
|
backupDir: plan.paths.backupDir
|
|
87
|
+
}),
|
|
88
|
+
installSkill({ ...agent, skillPath: agent.reviewSkillPath }, {
|
|
89
|
+
sourcePath: plan.paths.reviewSkillPath,
|
|
90
|
+
backupDir: plan.paths.backupDir
|
|
67
91
|
})
|
|
68
92
|
];
|
|
69
93
|
agents.push({ ...connected, backupPath, skills });
|
package/src/setup/uninstall.js
CHANGED
|
@@ -20,7 +20,8 @@ export function planLocalUninstall(options, {
|
|
|
20
20
|
(agent.globalInstructionsOverridePath &&
|
|
21
21
|
fileExists(agent.globalInstructionsOverridePath)) ||
|
|
22
22
|
(agent.skillPath && fileExists(agent.skillPath)) ||
|
|
23
|
-
(agent.projectSkillPath && fileExists(agent.projectSkillPath))
|
|
23
|
+
(agent.projectSkillPath && fileExists(agent.projectSkillPath)) ||
|
|
24
|
+
(agent.reviewSkillPath && fileExists(agent.reviewSkillPath))
|
|
24
25
|
));
|
|
25
26
|
return { paths, agents };
|
|
26
27
|
}
|
|
@@ -60,7 +61,8 @@ export async function applyLocalUninstall(plan, dependencies = {}) {
|
|
|
60
61
|
|
|
61
62
|
for (const [skillPath, sourcePath] of [
|
|
62
63
|
[agent.skillPath, plan.paths.sessionSkillPath],
|
|
63
|
-
[agent.projectSkillPath, plan.paths.projectSkillPath]
|
|
64
|
+
[agent.projectSkillPath, plan.paths.projectSkillPath],
|
|
65
|
+
[agent.reviewSkillPath, plan.paths.reviewSkillPath]
|
|
64
66
|
]) {
|
|
65
67
|
try {
|
|
66
68
|
skills.push(removeSkill({ ...agent, skillPath }, { sourcePath }));
|