fuli-context 0.4.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 +5 -0
- package/README.md +411 -173
- 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 -4
- 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 +60 -23
- package/src/cli/local-runtime-options.js +16 -6
- 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 +38 -3
- package/src/local-runtime/lifecycle.js +37 -16
- 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/lan-access.js +52 -0
- package/src/server.js +103 -25
- 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 +219 -58
- 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-CJCNI2Wd.css +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-PXH-TlGt.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-BQRkOwPx.js +0 -2
- package/dist/web/assets/index-CpLDx3of.css +0 -1
- 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
|
@@ -4,22 +4,30 @@ import { existsSync, unlinkSync } from 'node:fs';
|
|
|
4
4
|
import {
|
|
5
5
|
checkLocalConsoleHealth,
|
|
6
6
|
ensureGraphRuntime,
|
|
7
|
-
MANAGED_PERSONAL_PROVIDER_URL,
|
|
8
|
-
MANAGED_WORKSPACE_PROVIDER_URL,
|
|
9
7
|
stopGraphProviders
|
|
10
8
|
} from '../setup/graph-runtime.js';
|
|
11
9
|
import { readJsonFile } from '../storage/json-file.js';
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_RUNTIME_SETTINGS,
|
|
12
|
+
managedProviderUrls
|
|
13
|
+
} from '../system/runtime-settings.js';
|
|
12
14
|
|
|
13
15
|
export async function startLocalRuntime(input, dependencies = {}) {
|
|
14
16
|
const deps = lifecycleDependencies(dependencies);
|
|
17
|
+
const runtimeSettings = input.runtimeSettings ?? DEFAULT_RUNTIME_SETTINGS;
|
|
15
18
|
const config = deps.readConfig(input.paths.graphRuntimeConfigPath);
|
|
16
|
-
const managesDevelopmentWorkspace = hasManagedDevelopmentWorkspace(
|
|
19
|
+
const managesDevelopmentWorkspace = hasManagedDevelopmentWorkspace(
|
|
20
|
+
config,
|
|
21
|
+
runtimeSettings
|
|
22
|
+
);
|
|
17
23
|
const buildProviders = input.rebuild === true || !config ||
|
|
18
24
|
!deps.fileExists(input.paths.graphEnvPath);
|
|
19
25
|
const runtime = await deps.ensureRuntime({
|
|
20
26
|
paths: input.paths,
|
|
21
27
|
personalSpaceName: input.personalSpaceName,
|
|
22
28
|
port: input.port,
|
|
29
|
+
lan: input.lan === true,
|
|
30
|
+
runtimeSettings,
|
|
23
31
|
personalOnly: !managesDevelopmentWorkspace,
|
|
24
32
|
buildProviders,
|
|
25
33
|
noStart: false,
|
|
@@ -36,12 +44,13 @@ export async function startLocalRuntime(input, dependencies = {}) {
|
|
|
36
44
|
|
|
37
45
|
export async function stopLocalRuntime(input, dependencies = {}) {
|
|
38
46
|
const deps = lifecycleDependencies(dependencies);
|
|
47
|
+
const runtimeSettings = input.runtimeSettings ?? DEFAULT_RUNTIME_SETTINGS;
|
|
39
48
|
const config = deps.readConfig(input.paths.graphRuntimeConfigPath);
|
|
40
49
|
const state = deps.readState(input.paths.graphRuntimeStatePath);
|
|
41
50
|
const consoleResult = await stopRecordedConsole(state, input.paths, deps);
|
|
42
51
|
const managesDevelopmentWorkspace = state?.managedProviders?.includes(
|
|
43
52
|
'development-workspace'
|
|
44
|
-
) || hasManagedDevelopmentWorkspace(config);
|
|
53
|
+
) || hasManagedDevelopmentWorkspace(config, runtimeSettings);
|
|
45
54
|
|
|
46
55
|
let providers = 'not_initialized';
|
|
47
56
|
if (deps.fileExists(input.paths.graphEnvPath)) {
|
|
@@ -63,7 +72,7 @@ export async function stopLocalRuntime(input, dependencies = {}) {
|
|
|
63
72
|
export async function restartLocalRuntime(input, dependencies = {}) {
|
|
64
73
|
const stopped = await stopLocalRuntime(input, dependencies);
|
|
65
74
|
if (stopped.status === 'partial') {
|
|
66
|
-
throw new Error('
|
|
75
|
+
throw new Error('Could not safely verify the existing local UI process; restart was refused.');
|
|
67
76
|
}
|
|
68
77
|
const started = await startLocalRuntime(input, dependencies);
|
|
69
78
|
return { stopped, ...started, status: 'restarted' };
|
|
@@ -71,6 +80,7 @@ export async function restartLocalRuntime(input, dependencies = {}) {
|
|
|
71
80
|
|
|
72
81
|
export async function inspectLocalRuntime(input, dependencies = {}) {
|
|
73
82
|
const deps = lifecycleDependencies(dependencies);
|
|
83
|
+
const runtimeSettings = input.runtimeSettings ?? DEFAULT_RUNTIME_SETTINGS;
|
|
74
84
|
const config = deps.readConfig(input.paths.graphRuntimeConfigPath);
|
|
75
85
|
const state = deps.readState(input.paths.graphRuntimeStatePath);
|
|
76
86
|
const stateLooksOwned = isRuntimeState(state) && isLoopbackConsoleUrl(state.url);
|
|
@@ -80,13 +90,14 @@ export async function inspectLocalRuntime(input, dependencies = {}) {
|
|
|
80
90
|
state.pid,
|
|
81
91
|
state.version
|
|
82
92
|
);
|
|
83
|
-
const
|
|
93
|
+
const managedUrls = managedProviderUrls(runtimeSettings);
|
|
94
|
+
const personalUrl = config?.personal?.providerUrl ?? managedUrls.personal;
|
|
84
95
|
const personal = config
|
|
85
96
|
? await deps.providerHealth(personalUrl)
|
|
86
97
|
: { url: personalUrl, status: 'not_configured' };
|
|
87
98
|
const workspaces = await Promise.all((config?.workspaces ?? []).map(async (workspace) => ({
|
|
88
99
|
...(await deps.providerHealth(workspace.providerUrl)),
|
|
89
|
-
managedDevelopment: isManagedDevelopmentUrl(workspace
|
|
100
|
+
managedDevelopment: isManagedDevelopmentUrl(workspace, runtimeSettings)
|
|
90
101
|
})));
|
|
91
102
|
|
|
92
103
|
let status = 'stopped';
|
|
@@ -99,7 +110,10 @@ export async function inspectLocalRuntime(input, dependencies = {}) {
|
|
|
99
110
|
console: {
|
|
100
111
|
status: consoleReady ? 'ready' : processAlive ? 'unverified' : 'stopped',
|
|
101
112
|
url: stateLooksOwned ? state.url : `http://127.0.0.1:${input.port}`,
|
|
102
|
-
pid: processAlive ? state.pid : null
|
|
113
|
+
pid: processAlive ? state.pid : null,
|
|
114
|
+
...(stateLooksOwned && state.lan === true
|
|
115
|
+
? { lan: true, lanUrls: safeLanUrls(state.lanUrls) }
|
|
116
|
+
: {})
|
|
103
117
|
},
|
|
104
118
|
personal,
|
|
105
119
|
public: {
|
|
@@ -116,7 +130,7 @@ export async function openLocalConsole(input, dependencies = {}) {
|
|
|
116
130
|
if (!isRuntimeState(state) || !isLoopbackConsoleUrl(state.url) ||
|
|
117
131
|
!deps.isProcessAlive(state.pid) ||
|
|
118
132
|
!await deps.consoleHealth(state.url, state.pid, state.version)) {
|
|
119
|
-
throw new Error('
|
|
133
|
+
throw new Error('The local UI is not running. Run fl start first.');
|
|
120
134
|
}
|
|
121
135
|
await deps.openExternal(state.url);
|
|
122
136
|
return { status: 'opened', url: state.url };
|
|
@@ -197,17 +211,18 @@ export function externalOpenInvocation(url, platform = process.platform) {
|
|
|
197
211
|
return { command: 'xdg-open', args: [url] };
|
|
198
212
|
}
|
|
199
213
|
|
|
200
|
-
function hasManagedDevelopmentWorkspace(config) {
|
|
201
|
-
return (config?.workspaces ?? []).some((
|
|
202
|
-
isManagedDevelopmentUrl(
|
|
214
|
+
function hasManagedDevelopmentWorkspace(config, runtimeSettings) {
|
|
215
|
+
return (config?.workspaces ?? []).some((workspace) =>
|
|
216
|
+
isManagedDevelopmentUrl(workspace, runtimeSettings));
|
|
203
217
|
}
|
|
204
218
|
|
|
205
|
-
function isManagedDevelopmentUrl(
|
|
219
|
+
function isManagedDevelopmentUrl(workspace, runtimeSettings) {
|
|
220
|
+
if (workspace?.managedDevelopment === true) return true;
|
|
206
221
|
try {
|
|
207
|
-
const url = new URL(
|
|
222
|
+
const url = new URL(workspace?.providerUrl);
|
|
208
223
|
return url.protocol === 'http:' &&
|
|
209
224
|
['127.0.0.1', 'localhost', '::1', '[::1]'].includes(url.hostname) &&
|
|
210
|
-
url.port
|
|
225
|
+
[8788, runtimeSettings.ports.workspaceProvider].includes(Number(url.port));
|
|
211
226
|
} catch {
|
|
212
227
|
return false;
|
|
213
228
|
}
|
|
@@ -233,10 +248,16 @@ function publicProviderStatus(workspaces) {
|
|
|
233
248
|
}
|
|
234
249
|
|
|
235
250
|
function isRuntimeState(state) {
|
|
236
|
-
return (state?.version === 2 || state?.version === 3) &&
|
|
251
|
+
return (state?.version === 2 || state?.version === 3 || state?.version === 4) &&
|
|
237
252
|
Number.isInteger(state.pid) && typeof state.url === 'string';
|
|
238
253
|
}
|
|
239
254
|
|
|
255
|
+
function safeLanUrls(value) {
|
|
256
|
+
return Array.isArray(value)
|
|
257
|
+
? value.filter((url) => typeof url === 'string' && url.startsWith('http://'))
|
|
258
|
+
: [];
|
|
259
|
+
}
|
|
260
|
+
|
|
240
261
|
function isProcessAlive(pid) {
|
|
241
262
|
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
242
263
|
}
|
|
@@ -20,6 +20,7 @@ const TOOL_RESULT_LIMIT_BYTES = Object.freeze({
|
|
|
20
20
|
begin_task_context: 16 * 1024,
|
|
21
21
|
get_collaboration_preferences: 16 * 1024,
|
|
22
22
|
search_knowledge_graph: 32 * 1024,
|
|
23
|
+
search_connected_knowledge: 64 * 1024,
|
|
23
24
|
search_current_project_knowledge: 64 * 1024,
|
|
24
25
|
discover_common_knowledge_candidates: 32 * 1024
|
|
25
26
|
});
|
package/src/mcp/instructions.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const MCP_INSTRUCTIONS = [
|
|
2
|
-
"Each user task:
|
|
3
|
-
'
|
|
4
|
-
'Before claiming facts unknown, use search_knowledge_graph. Use exact IDs for extra projects
|
|
5
|
-
'Use record_decision_trace for confirmed choices
|
|
6
|
-
'Batch durable confirmed knowledge via capture_session_knowledge in exact scope
|
|
7
|
-
'
|
|
8
|
-
'Terminal-safe Markdown. Final MUST begin with sourceMarker.leadMarkdown; append sourceMarker.markdown unchanged
|
|
2
|
+
"Each user task: apply hook-provided task context from begin_task_context. Otherwise, before other tools/answer, call exactly get_collaboration_preferences with projectPath=cwd and taskPrompt=current user request as fallback; never substitute a project action/Fuli tool. Apply all effective_preferences: personal-global everywhere plus only Fuli's matched project. Resolve relevant deferred_conflict first. Never guess personalProjectId. Writes enforce preferences in the actual payload; final text is not compliance.",
|
|
3
|
+
'Before asking the user to repeat a stable project fact or method, inspect task_knowledge_recall. On miss use search_current_project_knowledge with 1-4 focused action, artifact, target-system, or identifier queries; never use the whole conversational request as the only query. Search the exact active child first, then inheritable parent PART_OF/USES_KNOWLEDGE_FROM knowledge within two hops; child stable keys override. Never inherit project preferences or traverse RELATED_TO.',
|
|
4
|
+
'Before claiming facts unknown, use search_knowledge_graph. Use exact IDs for extra projects. Widen a miss to all_local_confirmed only after consent, then rg only in the safe current repo/workspace. Never scan outside it. Live state needs deployment/monitoring/Git MCP evidence. External/public evidence: search_connected_knowledge; agent_decide is response-only.',
|
|
5
|
+
'Use record_decision_trace for confirmed choices and record_knowledge_feedback when knowledge is rejected, contradicted, outdated, or fails validation.',
|
|
6
|
+
'Batch durable confirmed knowledge via capture_session_knowledge in exact scope; never secrets, transcripts, or guesses. Finish once with checkpoint_task_knowledge: capture_candidates or retain_nothing.',
|
|
7
|
+
'After material personal-knowledge use call record_knowledge_usage; retrieval alone never counts or permits public use.',
|
|
8
|
+
'Terminal-safe Markdown. Final MUST begin with sourceMarker.leadMarkdown; append sourceMarker.markdown unchanged on match. On miss use noMatchSourceMarker leadMarkdown only (empty markdown). Never wrap markers in HTML.'
|
|
9
9
|
].join(' ');
|
|
@@ -5,6 +5,7 @@ const READ_TOOLS = new Set([
|
|
|
5
5
|
'preview_common_knowledge_promotion',
|
|
6
6
|
'list_knowledge_spaces',
|
|
7
7
|
'list_personal_projects',
|
|
8
|
+
'list_knowledge_review_candidates',
|
|
8
9
|
'preview_personal_project_action',
|
|
9
10
|
'list_project_releases',
|
|
10
11
|
'list_project_relations',
|
|
@@ -21,6 +22,7 @@ const WRITE_TOOLS = new Set([
|
|
|
21
22
|
'capture_session_knowledge',
|
|
22
23
|
'record_decision_trace',
|
|
23
24
|
'search_knowledge_graph',
|
|
25
|
+
'search_connected_knowledge',
|
|
24
26
|
'record_knowledge_usage',
|
|
25
27
|
'record_knowledge_feedback',
|
|
26
28
|
'apply_common_knowledge_promotion',
|
|
@@ -28,6 +30,9 @@ const WRITE_TOOLS = new Set([
|
|
|
28
30
|
'search_human_knowledge_changes',
|
|
29
31
|
'review_human_knowledge_change',
|
|
30
32
|
'upsert_personal_project',
|
|
33
|
+
'start_knowledge_review',
|
|
34
|
+
'record_knowledge_review_progress',
|
|
35
|
+
'finish_knowledge_review',
|
|
31
36
|
'revise_personal_knowledge',
|
|
32
37
|
'reassign_personal_knowledge',
|
|
33
38
|
'set_personal_preference_scope',
|
|
@@ -64,6 +69,7 @@ export function annotationsFor(name) {
|
|
|
64
69
|
name === 'record_knowledge_feedback' ||
|
|
65
70
|
name === 'checkpoint_task_knowledge',
|
|
66
71
|
openWorldHint: name === 'capture_session_knowledge' ||
|
|
72
|
+
name === 'search_connected_knowledge' ||
|
|
67
73
|
name === 'record_decision_trace' ||
|
|
68
74
|
name === 'record_knowledge_feedback' ||
|
|
69
75
|
name === 'checkpoint_task_knowledge' ||
|
package/src/mcp/tool-result.js
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
import { createApplication } from '../app/create-application.js';
|
|
2
1
|
import { openFederatedGraphApplication } from '../graphiti/federated-application.js';
|
|
3
|
-
import { openLocalApplication } from '../runtime-options.js';
|
|
4
2
|
|
|
5
3
|
export function createServerApplication({
|
|
6
4
|
app,
|
|
7
|
-
store,
|
|
8
|
-
dbPath,
|
|
9
5
|
runtimeConfigPath,
|
|
10
|
-
personalSpaceName,
|
|
11
6
|
closeApplicationOnShutdown
|
|
12
7
|
}) {
|
|
13
|
-
const application = app ?? (
|
|
14
|
-
|
|
15
|
-
: dbPath
|
|
16
|
-
? openLocalApplication({ dbPath, personalSpaceName })
|
|
17
|
-
: openFederatedGraphApplication({ runtimeConfigPath }));
|
|
18
|
-
const owned = closeApplicationOnShutdown ?? (!app && !store);
|
|
8
|
+
const application = app ?? openFederatedGraphApplication({ runtimeConfigPath });
|
|
9
|
+
const owned = closeApplicationOnShutdown ?? !app;
|
|
19
10
|
return {
|
|
20
11
|
application,
|
|
21
12
|
close: closeOnce(() => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const FETCH_BLOCKED_PORTS = new Set([
|
|
2
|
+
1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95,
|
|
3
|
+
101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389,
|
|
4
|
+
427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
|
|
5
|
+
989, 990, 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 6000, 6566,
|
|
6
|
+
6665, 6666, 6667, 6668, 6669, 6697, 10080
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export function isFetchBlockedPort(port) {
|
|
10
|
+
return FETCH_BLOCKED_PORTS.has(Number(port));
|
|
11
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isIPv4 } from 'node:net';
|
|
2
|
+
import { networkInterfaces } from 'node:os';
|
|
3
|
+
|
|
4
|
+
export const LAN_ACCESS_USERNAME = 'fuli';
|
|
5
|
+
|
|
6
|
+
export function discoverLanAddresses(interfaces = networkInterfaces()) {
|
|
7
|
+
const addresses = [];
|
|
8
|
+
for (const entries of Object.values(interfaces ?? {})) {
|
|
9
|
+
for (const entry of entries ?? []) {
|
|
10
|
+
if (!entry || entry.internal || !isIpv4Family(entry.family)) continue;
|
|
11
|
+
if (!isPrivateLanIpv4(entry.address)) continue;
|
|
12
|
+
addresses.push(entry.address);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return [...new Set(addresses)].sort(compareIpv4);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function lanConsoleUrls(addresses, port) {
|
|
19
|
+
return addresses.map((address) => `http://${consoleAuthority(address, port)}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function lanServerAuthorities(addresses, port) {
|
|
23
|
+
return lanConsoleUrls(addresses, port).map((url) => new URL(url).host);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isIpv4Family(family) {
|
|
27
|
+
return family === 'IPv4' || family === 4;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isPrivateLanIpv4(address) {
|
|
31
|
+
if (!isIPv4(address)) return false;
|
|
32
|
+
const [first, second] = address.split('.').map(Number);
|
|
33
|
+
return first === 10 ||
|
|
34
|
+
(first === 172 && second >= 16 && second <= 31) ||
|
|
35
|
+
(first === 192 && second === 168) ||
|
|
36
|
+
(first === 169 && second === 254);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function consoleAuthority(address, port) {
|
|
40
|
+
return Number(port) === 80 ? address : `${address}:${port}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function compareIpv4(left, right) {
|
|
44
|
+
const leftParts = left.split('.').map(Number);
|
|
45
|
+
const rightParts = right.split('.').map(Number);
|
|
46
|
+
for (let index = 0; index < 4; index += 1) {
|
|
47
|
+
if (leftParts[index] !== rightParts[index]) {
|
|
48
|
+
return leftParts[index] - rightParts[index];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
package/src/server.js
CHANGED
|
@@ -1,59 +1,94 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createServer as createHttpServer } from 'node:http';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
|
|
5
6
|
import { handleApiRequest } from './http/api-router.js';
|
|
6
7
|
import { mapHttpError } from './http/error-mapping.js';
|
|
7
|
-
import { localServerAuthority,
|
|
8
|
+
import { localServerAuthority, rejectRequestOutsidePolicy } from './http/request-policy.js';
|
|
8
9
|
import { sendJson } from './http/response.js';
|
|
9
10
|
import { serveStatic } from './http/static-handler.js';
|
|
10
11
|
import { DEFAULT_FULI_PORT } from './defaults.js';
|
|
11
12
|
import { resolveGraphRuntimeOptions } from './graphiti/runtime-config.js';
|
|
13
|
+
import { isFetchBlockedPort } from './server/blocked-ports.js';
|
|
14
|
+
import {
|
|
15
|
+
discoverLanAddresses,
|
|
16
|
+
lanConsoleUrls,
|
|
17
|
+
lanServerAuthorities
|
|
18
|
+
} from './server/lan-access.js';
|
|
12
19
|
import { createServerApplication } from './server/application-lifecycle.js';
|
|
13
20
|
import { listenServer } from './server/listen.js';
|
|
21
|
+
import { resolveSetupPaths } from './setup/paths.js';
|
|
22
|
+
import { createSystemService } from './system/system-service.js';
|
|
14
23
|
|
|
15
|
-
const
|
|
16
|
-
1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101, 102,
|
|
17
|
-
103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389, 427, 465,
|
|
18
|
-
512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993,
|
|
19
|
-
995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 6000, 6566, 6665, 6666, 6667, 6668,
|
|
20
|
-
6669, 6697, 10080
|
|
21
|
-
]);
|
|
24
|
+
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
22
25
|
|
|
23
26
|
export async function createServer(options = {}) {
|
|
24
27
|
const {
|
|
25
28
|
port = DEFAULT_FULI_PORT,
|
|
26
|
-
store,
|
|
27
29
|
app,
|
|
28
30
|
closeApplicationOnShutdown,
|
|
29
|
-
isBlockedPort = isFetchBlockedPort
|
|
31
|
+
isBlockedPort = isFetchBlockedPort,
|
|
32
|
+
lan = false,
|
|
33
|
+
lanAccessToken = null,
|
|
34
|
+
lanAddresses: configuredLanAddresses = null
|
|
30
35
|
} = options;
|
|
36
|
+
const lanEnabled = lan === true;
|
|
37
|
+
const lanToken = lanEnabled ? requiredLanToken(lanAccessToken) : null;
|
|
38
|
+
const lanAddresses = lanEnabled
|
|
39
|
+
? [...new Set(configuredLanAddresses ?? discoverLanAddresses())]
|
|
40
|
+
: [];
|
|
41
|
+
if (lanEnabled && lanAddresses.length === 0) {
|
|
42
|
+
throw new Error('No private IPv4 LAN address is available');
|
|
43
|
+
}
|
|
31
44
|
if (Number(port) !== 0 && isBlockedPort(port)) {
|
|
32
45
|
throw new Error(`Port ${port} is blocked for local fetch`);
|
|
33
46
|
}
|
|
34
|
-
const
|
|
35
|
-
const localOptions = legacyRuntime
|
|
36
|
-
? { dbPath: options.dbPath, personalSpaceName: options.personalSpaceName ?? '我' }
|
|
37
|
-
: resolveServerGraphOptions(options);
|
|
47
|
+
const localOptions = app ? {} : resolveServerGraphOptions(options);
|
|
38
48
|
const runtime = createServerApplication({
|
|
39
49
|
app,
|
|
40
|
-
store,
|
|
41
50
|
...localOptions,
|
|
42
51
|
closeApplicationOnShutdown
|
|
43
52
|
});
|
|
44
53
|
const application = runtime.application;
|
|
54
|
+
const externalKnowledge = options.externalKnowledge ??
|
|
55
|
+
application.externalKnowledge ?? null;
|
|
56
|
+
const connectedKnowledge = options.connectedKnowledge ??
|
|
57
|
+
application.connectedKnowledge ?? null;
|
|
58
|
+
const system = options.system ?? (!app
|
|
59
|
+
? createSystemService({
|
|
60
|
+
paths: resolveSetupPaths({
|
|
61
|
+
dataDir: dirname(localOptions.runtimeConfigPath),
|
|
62
|
+
packageRoot: PACKAGE_ROOT
|
|
63
|
+
}),
|
|
64
|
+
packageRoot: PACKAGE_ROOT,
|
|
65
|
+
activePort: port,
|
|
66
|
+
activeLan: lanEnabled
|
|
67
|
+
})
|
|
68
|
+
: null);
|
|
45
69
|
|
|
46
70
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
47
71
|
let authority = null;
|
|
72
|
+
let allowedLanAuthorities = [];
|
|
48
73
|
const server = createHttpServer((request, response) => {
|
|
49
|
-
handleRequest({
|
|
74
|
+
handleRequest({
|
|
75
|
+
request,
|
|
76
|
+
response,
|
|
77
|
+
app: application,
|
|
78
|
+
system,
|
|
79
|
+
externalKnowledge,
|
|
80
|
+
connectedKnowledge,
|
|
81
|
+
authority,
|
|
82
|
+
lanAuthorities: allowedLanAuthorities,
|
|
83
|
+
lanAccessToken: lanToken
|
|
84
|
+
}).catch((error) => {
|
|
50
85
|
const mapped = mapHttpError(error);
|
|
51
86
|
sendJson(response, mapped.status, mapped.body);
|
|
52
87
|
});
|
|
53
88
|
});
|
|
54
89
|
|
|
55
90
|
try {
|
|
56
|
-
await listenServer(server, port);
|
|
91
|
+
await listenServer(server, port, lanEnabled ? '0.0.0.0' : '127.0.0.1');
|
|
57
92
|
} catch (error) {
|
|
58
93
|
runtime.close();
|
|
59
94
|
throw error;
|
|
@@ -66,6 +101,9 @@ export async function createServer(options = {}) {
|
|
|
66
101
|
throw new Error(`Port ${address.port} is blocked for local fetch`);
|
|
67
102
|
}
|
|
68
103
|
authority = localServerAuthority(address);
|
|
104
|
+
allowedLanAuthorities = lanEnabled
|
|
105
|
+
? lanServerAuthorities(lanAddresses, address.port)
|
|
106
|
+
: [];
|
|
69
107
|
if (!authority) {
|
|
70
108
|
await new Promise((resolveClose) => server.close(resolveClose));
|
|
71
109
|
runtime.close();
|
|
@@ -73,7 +111,12 @@ export async function createServer(options = {}) {
|
|
|
73
111
|
}
|
|
74
112
|
|
|
75
113
|
server.once('close', runtime.close);
|
|
76
|
-
return {
|
|
114
|
+
return {
|
|
115
|
+
server,
|
|
116
|
+
url: `http://${authority}`,
|
|
117
|
+
lanUrls: lanEnabled ? lanConsoleUrls(lanAddresses, address.port) : [],
|
|
118
|
+
app: application
|
|
119
|
+
};
|
|
77
120
|
}
|
|
78
121
|
|
|
79
122
|
runtime.close();
|
|
@@ -90,13 +133,34 @@ function resolveServerGraphOptions(options) {
|
|
|
90
133
|
});
|
|
91
134
|
}
|
|
92
135
|
|
|
93
|
-
export
|
|
94
|
-
return FETCH_BLOCKED_PORTS.has(Number(port));
|
|
95
|
-
}
|
|
136
|
+
export { isFetchBlockedPort } from './server/blocked-ports.js';
|
|
96
137
|
|
|
97
|
-
async function handleRequest({
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
async function handleRequest({
|
|
139
|
+
request,
|
|
140
|
+
response,
|
|
141
|
+
app,
|
|
142
|
+
system,
|
|
143
|
+
externalKnowledge,
|
|
144
|
+
connectedKnowledge,
|
|
145
|
+
authority,
|
|
146
|
+
lanAuthorities,
|
|
147
|
+
lanAccessToken
|
|
148
|
+
}) {
|
|
149
|
+
if (rejectRequestOutsidePolicy({
|
|
150
|
+
request,
|
|
151
|
+
response,
|
|
152
|
+
authority,
|
|
153
|
+
lanAuthorities,
|
|
154
|
+
lanAccessToken
|
|
155
|
+
})) return;
|
|
156
|
+
if (await handleApiRequest({
|
|
157
|
+
request,
|
|
158
|
+
response,
|
|
159
|
+
app,
|
|
160
|
+
system,
|
|
161
|
+
externalKnowledge,
|
|
162
|
+
connectedKnowledge
|
|
163
|
+
})) return;
|
|
100
164
|
serveStatic(new URL(request.url, 'http://127.0.0.1').pathname, response);
|
|
101
165
|
}
|
|
102
166
|
|
|
@@ -110,7 +174,13 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
|
110
174
|
async function runProgram(args) {
|
|
111
175
|
const runtimeOptions = resolveGraphRuntimeOptions(args, process.env);
|
|
112
176
|
const port = numberOption(args, '--port', DEFAULT_FULI_PORT);
|
|
113
|
-
const
|
|
177
|
+
const lan = args.includes('--lan');
|
|
178
|
+
const { server, url, lanUrls } = await createServer({
|
|
179
|
+
...runtimeOptions,
|
|
180
|
+
port,
|
|
181
|
+
lan,
|
|
182
|
+
lanAccessToken: lan ? process.env.FULI_LAN_ACCESS_TOKEN : null
|
|
183
|
+
});
|
|
114
184
|
const handlers = new Map();
|
|
115
185
|
const close = () => server.close();
|
|
116
186
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
@@ -122,6 +192,14 @@ async function runProgram(args) {
|
|
|
122
192
|
for (const [signal, handler] of handlers) process.off(signal, handler);
|
|
123
193
|
});
|
|
124
194
|
console.log(`复利工作台 running at ${url}`);
|
|
195
|
+
for (const lanUrl of lanUrls) console.log(`复利工作台 LAN at ${lanUrl}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function requiredLanToken(value) {
|
|
199
|
+
if (typeof value !== 'string' || value.length < 16) {
|
|
200
|
+
throw new TypeError('LAN mode requires a generated access token');
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
125
203
|
}
|
|
126
204
|
|
|
127
205
|
function numberOption(args, flag, fallback) {
|
|
@@ -17,7 +17,8 @@ export function inspectAgentInstallations(agents, context, {
|
|
|
17
17
|
const registration = inspectRegistration(agent, context, { readText, readJson });
|
|
18
18
|
const skillPairs = [
|
|
19
19
|
[context.sessionSkillPath, agent.skillPath],
|
|
20
|
-
[context.projectSkillPath, agent.projectSkillPath]
|
|
20
|
+
[context.projectSkillPath, agent.projectSkillPath],
|
|
21
|
+
[context.reviewSkillPath, agent.reviewSkillPath]
|
|
21
22
|
];
|
|
22
23
|
const skillsPresent = skillPairs.some(([, targetPath]) => fileExists(targetPath));
|
|
23
24
|
const skillsCurrent = skillPairs.every(
|
package/src/setup/agents.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from './claude-code-config.js';
|
|
10
10
|
import { connectCursor, disconnectCursor } from './cursor-config.js';
|
|
11
11
|
import { connectCodex, disconnectCodex } from './codex-config.js';
|
|
12
|
+
import { adaptAgentForReviewSkill } from './review-skill-adapters.js';
|
|
12
13
|
|
|
13
14
|
export function discoverAgents({
|
|
14
15
|
platform = process.platform,
|
|
@@ -52,7 +53,7 @@ export function discoverAgents({
|
|
|
52
53
|
projectSkillPath: pathApi.join(homeDir, '.cursor', 'skills', projectSkillName),
|
|
53
54
|
available: Boolean(commandExists('cursor'))
|
|
54
55
|
}
|
|
55
|
-
];
|
|
56
|
+
].map((agent) => adaptAgentForReviewSkill(agent, { homeDir, pathApi }));
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export function buildAgentCommands(agent, context) {
|
|
@@ -115,7 +115,8 @@ function withManagedHooks(settings) {
|
|
|
115
115
|
...(hooks.UserPromptSubmit ?? []),
|
|
116
116
|
managedHookGroup('begin_task_context', {
|
|
117
117
|
sessionId: '${session_id}',
|
|
118
|
-
projectPath: '${cwd}'
|
|
118
|
+
projectPath: '${cwd}',
|
|
119
|
+
taskPrompt: '${prompt}'
|
|
119
120
|
}, 'Loading Fuli task context')
|
|
120
121
|
];
|
|
121
122
|
hooks.Stop = [
|
|
@@ -9,18 +9,23 @@ import {
|
|
|
9
9
|
} from 'node:fs';
|
|
10
10
|
import { basename, dirname, join } from 'node:path';
|
|
11
11
|
|
|
12
|
+
import { codexReviewCommandBridge } from './review-skill-adapters.js';
|
|
13
|
+
|
|
12
14
|
const BLOCK_START = '<!-- BEGIN FULI MANAGED INSTRUCTIONS -->';
|
|
13
15
|
const BLOCK_END = '<!-- END FULI MANAGED INSTRUCTIONS -->';
|
|
14
16
|
|
|
15
17
|
const GLOBAL_INSTRUCTIONS = `${BLOCK_START}
|
|
16
18
|
At the start of every user task, before any other tool or answer, call exactly
|
|
17
|
-
\`get_collaboration_preferences\` with \`projectPath\`=current working directory
|
|
18
|
-
project action
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
\`get_collaboration_preferences\` with \`projectPath\`=current working directory and
|
|
20
|
+
\`taskPrompt\`=current user request; never substitute a project action/Fuli tool. Fuli uses both
|
|
21
|
+
transiently and never stores or returns them. Apply all returned \`effective_preferences\`.
|
|
22
|
+
Before asking the user to repeat a stable project fact or method, inspect
|
|
23
|
+
\`task_knowledge_recall\`. On miss call \`search_current_project_knowledge\` with 1-4 focused
|
|
24
|
+
action/artifact/target/identifier queries; never use the full request as the only query.
|
|
25
|
+
For write tools enforce them in the actual payload; mentioning them later is not compliance.
|
|
26
|
+
Relevant \`deferred_conflict\`: call \`resolve_deferred_preference_conflict\` first; ignore others.
|
|
27
|
+
Current request wins.
|
|
28
|
+
${codexReviewCommandBridge()}
|
|
24
29
|
${BLOCK_END}`;
|
|
25
30
|
|
|
26
31
|
export function installCodexBootstrap(agent, _context, {
|