fuli-context 0.7.4-beta.3 → 0.7.5

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.
Files changed (128) hide show
  1. package/README.md +77 -6
  2. package/README.zh-CN.md +69 -6
  3. package/compose.graphiti.yml +6 -6
  4. package/dist/web/assets/AboutPage-ByCUor-j.js +1 -0
  5. package/dist/web/assets/ConnectionsPage-DkdkZ40Q.js +1 -0
  6. package/dist/web/assets/KnowledgeEditDialog-DSe3Gwep.js +1 -0
  7. package/dist/web/assets/KnowledgeInspector-CYAjAb9J.js +1 -0
  8. package/dist/web/assets/KnowledgeOrganizerPage-CV4F54R_.js +1 -0
  9. package/dist/web/assets/KnowledgePage-vYYXipOi.js +1 -0
  10. package/dist/web/assets/KnowledgeWorkspace-z4u5mXOG.js +4 -0
  11. package/dist/web/assets/OverviewPage-BWOfeS6k.js +1 -0
  12. package/dist/web/assets/PersonalProfilePage-c3w4-h7a.js +1 -0
  13. package/dist/web/assets/PersonalProjectsPage-B7AlLEwf.js +1 -0
  14. package/dist/web/assets/ProjectAgentsPage-CgftpIPB.css +1 -0
  15. package/dist/web/assets/ProjectAgentsPage-DTHcL_ox.js +11 -0
  16. package/dist/web/assets/PublicProjectsPage-DdH0oGB3.js +1 -0
  17. package/dist/web/assets/ReviewPage-BYIpcIHG.js +1 -0
  18. package/dist/web/assets/SearchableSelect-Dz13ClQb.js +1 -0
  19. package/dist/web/assets/SettingsPage-2VkbXgZi.js +1 -0
  20. package/dist/web/assets/SettingsPage-CwZp2ka4.css +1 -0
  21. package/dist/web/assets/WritingTastePage-BOPQZuva.js +1 -0
  22. package/dist/web/assets/{console-D_2zC4lm.js → console-BlAhujsH.js} +1 -1
  23. package/dist/web/assets/{conversation-launcher-settings-DQmMH2j7.js → conversation-launcher-settings-DTYxiLJt.js} +1 -1
  24. package/dist/web/assets/i18n-Ci3heuiL.js +9 -0
  25. package/dist/web/assets/{identity-DmGcY3Y6.js → identity-4_-v6cB7.js} +1 -1
  26. package/dist/web/assets/index-BnH5mcZx.css +1 -0
  27. package/dist/web/assets/index-C67Q0JY2.js +2 -0
  28. package/dist/web/assets/{model-CVoOdRsL.js → model-yG0YqUyZ.js} +1 -1
  29. package/dist/web/assets/{useApi-CROJJdhE-CBNg4CdH.js → useApi-CROJJdhE-twhdZArA.js} +1 -1
  30. package/dist/web/assets/vue-router-C-wot_Di.js +1 -0
  31. package/dist/web/index.html +6 -6
  32. package/graph-provider/fuli_graph/app.py +8 -0
  33. package/graph-provider/fuli_graph/collaboration_context.py +69 -6
  34. package/graph-provider/fuli_graph/knowledge_search.py +84 -5
  35. package/graph-provider/fuli_graph/model_validation.py +130 -0
  36. package/graph-provider/fuli_graph/models.py +24 -5
  37. package/graph-provider/fuli_graph/project_agent_access.py +54 -0
  38. package/graph-provider/fuli_graph/project_agent_executor_models.py +635 -0
  39. package/graph-provider/fuli_graph/project_agent_models.py +278 -0
  40. package/graph-provider/fuli_graph/project_agent_routes.py +597 -0
  41. package/graph-provider/fuli_graph/project_agent_task_models.py +542 -0
  42. package/graph-provider/fuli_graph/runtime.py +259 -0
  43. package/graph-provider/fuli_graph/search_projection.py +3 -0
  44. package/graph-provider/fuli_graph/store.py +6 -0
  45. package/graph-provider/fuli_graph/store_knowledge.py +62 -9
  46. package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +756 -0
  47. package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +601 -0
  48. package/graph-provider/fuli_graph/store_project_agent_executors.py +1087 -0
  49. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +458 -0
  50. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +699 -0
  51. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +409 -0
  52. package/graph-provider/fuli_graph/store_project_agent_tasks.py +1144 -0
  53. package/graph-provider/fuli_graph/store_project_agents.py +965 -0
  54. package/graph-provider/pyproject.toml +2 -1
  55. package/npm-shrinkwrap.json +2 -2
  56. package/package.json +1 -1
  57. package/skills/capturing-session-knowledge/SKILL.md +39 -0
  58. package/src/adaptive-runtime/executor-pool.js +127 -0
  59. package/src/adaptive-runtime/graph-services.js +166 -0
  60. package/src/adaptive-runtime/lease-client.js +335 -0
  61. package/src/adaptive-runtime/runtime-broker.js +318 -0
  62. package/src/adaptive-runtime/settings.js +111 -0
  63. package/src/adaptive-runtime/state.js +74 -0
  64. package/src/agent-tools/graph-definitions.js +396 -0
  65. package/src/agent-tools/graph-handlers.js +61 -0
  66. package/src/agent-tools/project-agent-definitions.js +307 -0
  67. package/src/backup/graph-backup.js +289 -0
  68. package/src/backup/identity-reconciliation.js +208 -0
  69. package/src/backup/runtime-adapters.js +269 -0
  70. package/src/cli/command-registry.js +6 -2
  71. package/src/cli/graph-data-command.js +112 -0
  72. package/src/cli/local-runtime-command.js +16 -2
  73. package/src/cli/setup-command.js +19 -3
  74. package/src/cli/update-command.js +4 -0
  75. package/src/cli.js +6 -0
  76. package/src/graphiti/collaboration-preference-projection.js +29 -1
  77. package/src/graphiti/collaboration-preference-workflow.js +121 -0
  78. package/src/graphiti/federated-application.js +53 -90
  79. package/src/graphiti/project-agent-control-plane.js +263 -0
  80. package/src/graphiti/project-agent-mapping.js +1003 -0
  81. package/src/graphiti/project-agent-workflows.js +557 -0
  82. package/src/graphiti/provider-client.js +270 -1
  83. package/src/http/api-router.js +63 -9
  84. package/src/http/graph-api-router.js +278 -0
  85. package/src/local-runtime/lifecycle.js +46 -10
  86. package/src/mcp/context-resources.js +15 -6
  87. package/src/mcp/create-mcp-server.js +51 -16
  88. package/src/mcp/instructions.js +7 -5
  89. package/src/mcp/runtime.js +17 -5
  90. package/src/mcp/session-id.js +38 -1
  91. package/src/mcp/tool-annotations.js +69 -0
  92. package/src/mcp-server.js +23 -6
  93. package/src/native-runtime/runtime.js +717 -0
  94. package/src/server.js +6 -2
  95. package/src/setup/agent-installation-status.js +7 -1
  96. package/src/setup/agents.js +3 -1
  97. package/src/setup/claude-code-config.js +3 -1
  98. package/src/setup/codex-bootstrap.js +11 -10
  99. package/src/setup/codex-config.js +7 -1
  100. package/src/setup/cursor-config.js +3 -1
  101. package/src/setup/graph-runtime.js +158 -13
  102. package/src/setup/neo4j-memory-profile.js +43 -0
  103. package/src/setup/options.js +38 -2
  104. package/src/setup/paths.js +14 -0
  105. package/src/setup/setup.js +37 -1
  106. package/src/system/resource-monitor.js +181 -16
  107. package/src/system/runtime-settings.js +10 -1
  108. package/src/system/system-service.js +27 -2
  109. package/dist/web/assets/AboutPage-Dpf5fRWv.js +0 -1
  110. package/dist/web/assets/ConnectionsPage-D21aUDCP.js +0 -1
  111. package/dist/web/assets/KnowledgeEditDialog-u7lpfN3j.js +0 -1
  112. package/dist/web/assets/KnowledgeInspector-k4xnHtXd.js +0 -1
  113. package/dist/web/assets/KnowledgeOrganizerPage-Bdj7Rn84.js +0 -1
  114. package/dist/web/assets/KnowledgePage-UHVAbC1W.js +0 -1
  115. package/dist/web/assets/KnowledgeWorkspace-Lq3d-MeC.js +0 -4
  116. package/dist/web/assets/OverviewPage-CycAgA1V.js +0 -1
  117. package/dist/web/assets/PersonalProfilePage-U9oIV5FT.js +0 -1
  118. package/dist/web/assets/PersonalProjectsPage-C5RpXKcA.js +0 -1
  119. package/dist/web/assets/PublicProjectsPage-DiWxm35E.js +0 -1
  120. package/dist/web/assets/ReviewPage-jxRfGSJK.js +0 -1
  121. package/dist/web/assets/SearchableSelect-B3-5IPup.js +0 -1
  122. package/dist/web/assets/SettingsPage-BqfGDOho.js +0 -1
  123. package/dist/web/assets/SettingsPage-CAfz4qiV.css +0 -1
  124. package/dist/web/assets/WritingTastePage-BJxhMqJR.js +0 -1
  125. package/dist/web/assets/i18n-CwqXDI1C.js +0 -5
  126. package/dist/web/assets/index-IUGLt4lG.js +0 -2
  127. package/dist/web/assets/index-X2j29j8i.css +0 -1
  128. package/dist/web/assets/vue-router-CWSsUUio.js +0 -1
package/src/server.js CHANGED
@@ -63,7 +63,8 @@ export async function createServer(options = {}) {
63
63
  }),
64
64
  packageRoot: PACKAGE_ROOT,
65
65
  activePort: port,
66
- activeLan: lanEnabled
66
+ activeLan: lanEnabled,
67
+ executorAdapters: options.executorAdapters
67
68
  })
68
69
  : null);
69
70
  for (let attempt = 0; attempt < 20; attempt += 1) {
@@ -109,7 +110,10 @@ export async function createServer(options = {}) {
109
110
  throw new Error('Server did not bind to a valid local authority');
110
111
  }
111
112
 
112
- server.once('close', runtime.close);
113
+ server.once('close', () => {
114
+ system?.close?.();
115
+ runtime.close();
116
+ });
113
117
  return {
114
118
  server,
115
119
  url: `http://${authority}`,
@@ -63,7 +63,13 @@ export function inspectAgentInstallations(agents, context, {
63
63
  function inspectRegistration(agent, context, readers) {
64
64
  const expected = {
65
65
  command: context.nodePath,
66
- args: [context.mcpServerPath, '--runtime-config', context.runtimeConfigPath]
66
+ args: [
67
+ context.mcpServerPath,
68
+ '--runtime-config',
69
+ context.runtimeConfigPath,
70
+ '--source-application',
71
+ agent.id === 'claude-code' ? 'claude_code' : agent.id
72
+ ]
67
73
  };
68
74
  try {
69
75
  if (agent.id === 'codex') {
@@ -60,7 +60,9 @@ export function buildAgentCommands(agent, context) {
60
60
  const serverArgs = [
61
61
  context.mcpServerPath,
62
62
  '--runtime-config',
63
- context.runtimeConfigPath
63
+ context.runtimeConfigPath,
64
+ '--source-application',
65
+ agent.id === 'claude-code' ? 'claude_code' : agent.id
64
66
  ];
65
67
  const mcpArgs = [context.nodePath, ...serverArgs];
66
68
  if (agent.id === 'codex') {
@@ -32,7 +32,9 @@ export function connectClaudeCode(agent, context, {
32
32
  args: [
33
33
  context.mcpServerPath,
34
34
  '--runtime-config',
35
- context.runtimeConfigPath
35
+ context.runtimeConfigPath,
36
+ '--source-application',
37
+ 'claude_code'
36
38
  ],
37
39
  alwaysLoad: true
38
40
  }
@@ -15,16 +15,17 @@ const BLOCK_START = '<!-- BEGIN FULI MANAGED INSTRUCTIONS -->';
15
15
  const BLOCK_END = '<!-- END FULI MANAGED INSTRUCTIONS -->';
16
16
 
17
17
  const GLOBAL_INSTRUCTIONS = `${BLOCK_START}
18
- At the start of every user task, before any other tool or answer, call exactly
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.
18
+ At start of every user task, before any other tool or answer, call exactly
19
+ \`get_collaboration_preferences\` with \`projectPath\`=current working directory,
20
+ \`taskPrompt\`=current user request. Never substitute a project action/Fuli tool.
21
+ Fuli never stores or returns them; inputs are transient. Apply all returned \`effective_preferences\`.
22
+ Before asking user to repeat a stable project fact or method, inspect \`task_knowledge_recall\`.
23
+ On miss call \`search_current_project_knowledge\` with focused action/artifact/target/identifier
24
+ queries; never use the full request as the only query.
25
+ Write tools enforce preferences in actual payload; mentioning later is not compliance.
26
+ Resolve relevant \`deferred_conflict\` first; ignore others.
27
+ Team: \`coordinate_project_agent_task\`; host \`acquire_runtime_lease\`, start workers, report
28
+ execution, \`release_runtime_lease\` in finally.
28
29
  ${codexReviewCommandBridge()}
29
30
  ${BLOCK_END}`;
30
31
 
@@ -22,7 +22,13 @@ export function connectCodex(agent, context, {
22
22
  const current = readConfig(agent.configPath);
23
23
  const next = replaceFuliTable(current, {
24
24
  command: context.nodePath,
25
- args: [context.mcpServerPath, '--runtime-config', context.runtimeConfigPath]
25
+ args: [
26
+ context.mcpServerPath,
27
+ '--runtime-config',
28
+ context.runtimeConfigPath,
29
+ '--source-application',
30
+ 'codex'
31
+ ]
26
32
  });
27
33
  const bootstrap = installBootstrap(agent, context);
28
34
  const registrationChanged = next !== current;
@@ -21,7 +21,9 @@ export function connectCursor(agent, context, {
21
21
  args: [
22
22
  context.mcpServerPath,
23
23
  '--runtime-config',
24
- context.runtimeConfigPath
24
+ context.runtimeConfigPath,
25
+ '--source-application',
26
+ 'cursor'
25
27
  ]
26
28
  }
27
29
  }
@@ -15,6 +15,10 @@ import {
15
15
  ensureContainerRuntime,
16
16
  runDockerCompose
17
17
  } from './container-runtime.js';
18
+ import {
19
+ createNativeGraphServices,
20
+ ensureNativeRuntime
21
+ } from '../native-runtime/runtime.js';
18
22
  import {
19
23
  discoverLanAddresses,
20
24
  LAN_ACCESS_USERNAME,
@@ -28,6 +32,20 @@ import {
28
32
  runtimeSettingsWithOverrides,
29
33
  writeRuntimeSettings
30
34
  } from '../system/runtime-settings.js';
35
+ import {
36
+ neo4jMemoryEnvironment,
37
+ resolveNeo4jMemoryProfile
38
+ } from './neo4j-memory-profile.js';
39
+ import {
40
+ DEFAULT_ADAPTIVE_RUNTIME_SETTINGS,
41
+ normalizeAdaptiveRuntimeSettings,
42
+ readAdaptiveRuntimeSettings,
43
+ writeAdaptiveRuntimeSettings
44
+ } from '../adaptive-runtime/settings.js';
45
+ import {
46
+ initialAdaptiveRuntimeState,
47
+ writeAdaptiveRuntimeState
48
+ } from '../adaptive-runtime/state.js';
31
49
 
32
50
  export {
33
51
  dockerInfoIndicatesDaemon,
@@ -42,31 +60,79 @@ export async function ensureGraphRuntime(input, dependencies = {}) {
42
60
  lanAccess: input.lan === true
43
61
  }));
44
62
  const urls = managedProviderUrls(runtimeSettings);
63
+ const adaptiveRuntimeSettings = normalizeAdaptiveRuntimeSettings(
64
+ input.adaptiveRuntimeSettings ?? (input.paths.adaptiveRuntimeSettingsPath
65
+ ? deps.readAdaptiveSettings(input.paths.adaptiveRuntimeSettingsPath)
66
+ : DEFAULT_ADAPTIVE_RUNTIME_SETTINGS)
67
+ );
45
68
  const lan = runtimeSettings.lanAccess;
69
+ const runtimeMode = input.runtimeMode ?? runtimeSettings.graphRuntimeMode;
70
+ const activeRuntimeState = deps.readState(input.paths.graphRuntimeStatePath);
71
+ const previousRuntimeMode = input.previousRuntimeMode ??
72
+ activeRuntimeState?.runtimeSettings?.graphRuntimeMode ?? runtimeMode;
46
73
  const lanAddresses = lan ? deps.discoverLanAddresses() : [];
47
74
  if (lan && lanAddresses.length === 0) {
48
75
  throw new Error('No private IPv4 LAN address is available, so LAN mode cannot start.');
49
76
  }
50
- const containerRuntime = await deps.ensureContainerRuntime({
51
- env: input.env ?? process.env,
52
- onProgress: input.onProgress
53
- });
77
+ const selectedGraphRuntime = runtimeMode === 'native'
78
+ ? await deps.ensureNativeRuntime({
79
+ paths: input.paths,
80
+ env: input.env ?? process.env,
81
+ runtimeSettings,
82
+ memoryProfile: input.memoryProfile,
83
+ personalOnly: input.personalOnly === true,
84
+ onProgress: input.onProgress
85
+ })
86
+ : await deps.ensureContainerRuntime({
87
+ env: input.env ?? process.env,
88
+ onProgress: input.onProgress
89
+ });
54
90
  deps.ensureDirectory(input.paths.dataDir);
91
+ const configSelection = selectRuntimeConfig({
92
+ paths: input.paths,
93
+ previousRuntimeMode,
94
+ runtimeMode,
95
+ deps
96
+ });
97
+ if (input.paths.adaptiveRuntimeSettingsPath) {
98
+ deps.writeAdaptiveSettings(
99
+ input.paths.adaptiveRuntimeSettingsPath,
100
+ adaptiveRuntimeSettings
101
+ );
102
+ }
55
103
  const secrets = ensureProviderEnvironment(
56
104
  input.paths.graphEnvPath,
57
105
  runtimeSettings,
106
+ input.memoryProfile,
58
107
  deps
59
108
  );
60
109
  deps.writeRuntimeSettings(input.paths.runtimeSettingsPath, runtimeSettings);
61
- deps.startProviders(input.paths, input.paths.graphEnvPath, {
62
- personalOnly: input.personalOnly === true,
63
- containerRuntime,
64
- ...(input.buildProviders === false ? { build: false } : {})
65
- });
110
+ if (runtimeMode === 'native') {
111
+ await deps.startNativeProviders(input.paths, input.paths.graphEnvPath, {
112
+ personalOnly: input.personalOnly === true,
113
+ nativeRuntime: selectedGraphRuntime
114
+ });
115
+ } else {
116
+ deps.startProviders(input.paths, input.paths.graphEnvPath, {
117
+ personalOnly: input.personalOnly === true,
118
+ containerRuntime: selectedGraphRuntime,
119
+ ...(input.buildProviders === false ? { build: false } : {})
120
+ });
121
+ }
66
122
  await waitForProvider(urls.personal, deps.fetch);
67
123
  if (!input.personalOnly) await waitForProvider(urls.workspace, deps.fetch);
124
+ if (input.paths.adaptiveRuntimeStatePath) {
125
+ deps.writeAdaptiveState(
126
+ input.paths.adaptiveRuntimeStatePath,
127
+ initialAdaptiveRuntimeState({ enabled: adaptiveRuntimeSettings.enabled })
128
+ );
129
+ }
68
130
 
69
- let config = deps.readConfig(input.paths.graphRuntimeConfigPath);
131
+ let config = configSelection.config;
132
+ if (configSelection.switched && config) {
133
+ deps.writeConfig(input.paths.graphRuntimeConfigPath, config);
134
+ deps.secureFile(input.paths.graphRuntimeConfigPath);
135
+ }
70
136
  if (!config) {
71
137
  config = await bootstrapGraph({
72
138
  urls,
@@ -95,9 +161,16 @@ export async function ensureGraphRuntime(input, dependencies = {}) {
95
161
  deps.secureFile(input.paths.graphRuntimeConfigPath);
96
162
  }
97
163
  }
164
+ const activeProfilePath = configSelection.switched
165
+ ? graphConfigProfilePath(input.paths, runtimeMode)
166
+ : null;
167
+ if (activeProfilePath) {
168
+ deps.writeConfig(activeProfilePath, config);
169
+ deps.secureFile(activeProfilePath);
170
+ }
98
171
 
99
172
  if (input.noStart) return { status: 'initialized', url: null, pid: null };
100
- const existing = deps.readState(input.paths.graphRuntimeStatePath);
173
+ const existing = activeRuntimeState;
101
174
  if (isGraphRuntimeState(existing) && deps.isProcessAlive(existing.pid)) {
102
175
  const healthy = await deps.webHealth(existing.url, existing.pid, existing.version);
103
176
  if (
@@ -105,6 +178,9 @@ export async function ensureGraphRuntime(input, dependencies = {}) {
105
178
  existing.port === runtimeSettings.ports.console &&
106
179
  exposureMatches(existing, { lan, lanAddresses })
107
180
  ) {
181
+ if (adaptiveRuntimeSettings.enabled) {
182
+ await notifyAdaptiveRuntime(existing.url, deps.fetch);
183
+ }
108
184
  return runtimeResult('running', existing);
109
185
  }
110
186
  if (!healthy) throw new Error('Recorded Fuli Graphiti runtime is not healthy');
@@ -149,10 +225,38 @@ export async function ensureGraphRuntime(input, dependencies = {}) {
149
225
  return runtimeResult('started', state, lanAccessToken);
150
226
  }
151
227
 
152
- function ensureProviderEnvironment(path, settings, deps) {
228
+ function selectRuntimeConfig({ paths, previousRuntimeMode, runtimeMode, deps }) {
229
+ const current = deps.readConfig(paths.graphRuntimeConfigPath);
230
+ const switched = previousRuntimeMode !== runtimeMode;
231
+ if (!switched) return { switched: false, config: current };
232
+
233
+ const previousProfilePath = graphConfigProfilePath(paths, previousRuntimeMode);
234
+ if (current && previousProfilePath) {
235
+ deps.writeConfig(previousProfilePath, current);
236
+ deps.secureFile(previousProfilePath);
237
+ }
238
+ const targetProfilePath = graphConfigProfilePath(paths, runtimeMode);
239
+ return {
240
+ switched: true,
241
+ config: targetProfilePath ? deps.readConfig(targetProfilePath) : null
242
+ };
243
+ }
244
+
245
+ function graphConfigProfilePath(paths, mode) {
246
+ if (mode === 'native') return paths.nativeGraphConfigProfilePath ?? null;
247
+ if (mode === 'container') return paths.containerGraphConfigProfilePath ?? null;
248
+ return null;
249
+ }
250
+
251
+ function ensureProviderEnvironment(path, settings, requestedMemoryProfile, deps) {
153
252
  const previous = deps.fileExists(path) ? parseEnv(deps.readText(path)) : {};
253
+ const memoryProfile = resolveNeo4jMemoryProfile(
254
+ requestedMemoryProfile,
255
+ previous.FULI_NEO4J_MEMORY_PROFILE
256
+ );
154
257
  const values = {
155
258
  ...previous,
259
+ ...neo4jMemoryEnvironment(memoryProfile),
156
260
  FULI_PERSONAL_NEO4J_PASSWORD: previous.FULI_PERSONAL_NEO4J_PASSWORD ?? secret(),
157
261
  FULI_WORKSPACE_NEO4J_PASSWORD: previous.FULI_WORKSPACE_NEO4J_PASSWORD ?? secret(),
158
262
  FULI_PERSONAL_BOOTSTRAP_TOKEN: previous.FULI_PERSONAL_BOOTSTRAP_TOKEN ?? secret(),
@@ -390,7 +494,14 @@ export function startGraphProviders(paths, envPath, options = {}) {
390
494
  runDockerCompose(args, options.containerRuntime);
391
495
  }
392
496
 
393
- export function stopGraphProviders(paths, envPath, options = {}) {
497
+ export async function stopGraphProviders(paths, envPath, options = {}) {
498
+ if (options.runtimeMode === 'native') {
499
+ await createNativeGraphServices({
500
+ paths,
501
+ personalOnly: options.personalOnly === true
502
+ }).stopDatabases();
503
+ return;
504
+ }
394
505
  const args = [
395
506
  'compose',
396
507
  '--env-file', envPath,
@@ -437,10 +548,19 @@ function graphDependencies(overrides) {
437
548
  readConfig: (path) => readJsonFile(path, null),
438
549
  writeConfig: writeJsonFileAtomic,
439
550
  writeRuntimeSettings,
551
+ readAdaptiveSettings: readAdaptiveRuntimeSettings,
552
+ writeAdaptiveSettings: writeAdaptiveRuntimeSettings,
553
+ writeAdaptiveState: writeAdaptiveRuntimeState,
440
554
  readState: (path) => readJsonFile(path, null),
441
555
  writeState: writeJsonFileAtomic,
442
556
  ensureContainerRuntime,
557
+ ensureNativeRuntime,
443
558
  startProviders: startGraphProviders,
559
+ startNativeProviders: (paths, _envPath, options) => createNativeGraphServices({
560
+ paths,
561
+ runtimeDescriptor: options.nativeRuntime,
562
+ personalOnly: options.personalOnly
563
+ }).start(),
444
564
  spawnWebRuntime,
445
565
  isProcessAlive,
446
566
  stopProcess,
@@ -452,6 +572,31 @@ function graphDependencies(overrides) {
452
572
  };
453
573
  }
454
574
 
575
+ async function notifyAdaptiveRuntime(url, fetchImpl) {
576
+ let leaseId = null;
577
+ try {
578
+ const acquired = await fetchImpl(`${url}/api/system/runtime/leases`, {
579
+ method: 'POST',
580
+ headers: { 'content-type': 'application/json' },
581
+ body: JSON.stringify({ kind: 'graph', owner: 'setup' }),
582
+ signal: AbortSignal.timeout(180_000)
583
+ });
584
+ if (!acquired.ok) return;
585
+ leaseId = (await acquired.json())?.leaseId ?? null;
586
+ } catch {
587
+ return;
588
+ }
589
+ if (!leaseId) return;
590
+ try {
591
+ await fetchImpl(`${url}/api/system/runtime/leases/${encodeURIComponent(leaseId)}`, {
592
+ method: 'DELETE',
593
+ signal: AbortSignal.timeout(10_000)
594
+ });
595
+ } catch {
596
+ // Lease expiry remains the safety net if setup cannot release explicitly.
597
+ }
598
+ }
599
+
455
600
  export async function checkLocalConsoleHealth(url, expectedPid, stateVersion = 3) {
456
601
  try {
457
602
  const response = await fetch(`${url}/api/health`, {
@@ -0,0 +1,43 @@
1
+ export const DEFAULT_NEO4J_MEMORY_PROFILE = 'balanced';
2
+
3
+ export const NEO4J_MEMORY_PROFILES = Object.freeze({
4
+ balanced: Object.freeze({
5
+ heapInitial: '256m',
6
+ heapMax: '512m',
7
+ pageCache: '256m'
8
+ }),
9
+ low: Object.freeze({
10
+ heapInitial: '128m',
11
+ heapMax: '256m',
12
+ pageCache: '64m'
13
+ })
14
+ });
15
+
16
+ export function normalizeNeo4jMemoryProfile(value) {
17
+ const profile = typeof value === 'string' ? value.trim() : '';
18
+ if (!Object.hasOwn(NEO4J_MEMORY_PROFILES, profile)) {
19
+ throw new TypeError('Neo4j memory profile must be "low" or "balanced"');
20
+ }
21
+ return profile;
22
+ }
23
+
24
+ export function resolveNeo4jMemoryProfile(requested, saved) {
25
+ if (requested !== null && requested !== undefined) {
26
+ return normalizeNeo4jMemoryProfile(requested);
27
+ }
28
+ if (typeof saved === 'string' && saved.trim()) {
29
+ return normalizeNeo4jMemoryProfile(saved);
30
+ }
31
+ return DEFAULT_NEO4J_MEMORY_PROFILE;
32
+ }
33
+
34
+ export function neo4jMemoryEnvironment(profile = DEFAULT_NEO4J_MEMORY_PROFILE) {
35
+ const resolved = normalizeNeo4jMemoryProfile(profile);
36
+ const values = NEO4J_MEMORY_PROFILES[resolved];
37
+ return {
38
+ FULI_NEO4J_MEMORY_PROFILE: resolved,
39
+ FULI_NEO4J_HEAP_INITIAL_SIZE: values.heapInitial,
40
+ FULI_NEO4J_HEAP_MAX_SIZE: values.heapMax,
41
+ FULI_NEO4J_PAGECACHE_SIZE: values.pageCache
42
+ };
43
+ }
@@ -1,7 +1,11 @@
1
+ import { normalizeNeo4jMemoryProfile } from './neo4j-memory-profile.js';
2
+
1
3
  const VALUE_OPTIONS = Object.freeze({
2
4
  '--data-dir': 'dataDir',
3
5
  '--personal-space': 'personalSpaceName',
4
- '--port': 'port'
6
+ '--port': 'port',
7
+ '--memory-profile': 'memoryProfile',
8
+ '--runtime-mode': 'runtimeMode'
5
9
  });
6
10
 
7
11
  const BOOLEAN_OPTIONS = Object.freeze({
@@ -11,6 +15,11 @@ const BOOLEAN_OPTIONS = Object.freeze({
11
15
  '--no-start': 'noStart'
12
16
  });
13
17
 
18
+ const ADAPTIVE_MEMORY_OPTIONS = Object.freeze({
19
+ '--adaptive-memory': true,
20
+ '--no-adaptive-memory': false
21
+ });
22
+
14
23
  const PROVIDER_MODE_OPTIONS = new Set(['--personal-only', '--with-dev-public']);
15
24
 
16
25
  export function parseSetupOptions(args = []) {
@@ -26,6 +35,9 @@ function parseSetupLikeOptions(args, command) {
26
35
  dataDir: null,
27
36
  personalSpaceName: 'Personal',
28
37
  port: null,
38
+ memoryProfile: null,
39
+ runtimeMode: null,
40
+ adaptiveMemory: null,
29
41
  yes: false,
30
42
  codexOnly: false,
31
43
  skipAgents: false,
@@ -37,6 +49,17 @@ function parseSetupLikeOptions(args, command) {
37
49
  for (let index = 0; index < args.length; index += 1) {
38
50
  const flag = args[index];
39
51
  if (seen.has(flag)) throw new TypeError(`Duplicate ${flag}`);
52
+ if (Object.hasOwn(ADAPTIVE_MEMORY_OPTIONS, flag)) {
53
+ const conflicting = flag === '--adaptive-memory'
54
+ ? '--no-adaptive-memory'
55
+ : '--adaptive-memory';
56
+ if (seen.has(conflicting)) {
57
+ throw new TypeError('--adaptive-memory and --no-adaptive-memory cannot be combined');
58
+ }
59
+ result.adaptiveMemory = ADAPTIVE_MEMORY_OPTIONS[flag];
60
+ seen.add(flag);
61
+ continue;
62
+ }
40
63
  if (PROVIDER_MODE_OPTIONS.has(flag)) {
41
64
  const conflicting = flag === '--personal-only' ? '--with-dev-public' : '--personal-only';
42
65
  if (seen.has(conflicting)) {
@@ -58,7 +81,7 @@ function parseSetupLikeOptions(args, command) {
58
81
  if (typeof value !== 'string' || !value.trim() || value.startsWith('--')) {
59
82
  throw new TypeError(`Missing value for ${flag}`);
60
83
  }
61
- result[valueKey] = valueKey === 'port' ? parsePort(value) : value.trim();
84
+ result[valueKey] = parseValue(valueKey, value);
62
85
  seen.add(flag);
63
86
  index += 1;
64
87
  }
@@ -95,3 +118,16 @@ function parsePort(value) {
95
118
  }
96
119
  return port;
97
120
  }
121
+
122
+ function parseValue(key, value) {
123
+ if (key === 'port') return parsePort(value);
124
+ if (key === 'memoryProfile') return normalizeNeo4jMemoryProfile(value);
125
+ if (key === 'runtimeMode') {
126
+ const mode = value.trim();
127
+ if (!['container', 'native'].includes(mode)) {
128
+ throw new TypeError('Runtime mode must be "container" or "native"');
129
+ }
130
+ return mode;
131
+ }
132
+ return value.trim();
133
+ }
@@ -14,12 +14,25 @@ export function resolveSetupPaths({
14
14
  } = {}) {
15
15
  const resolvedDataDir = resolve(cwd, dataDir ?? defaultDataDir({ platform, env, homeDir }));
16
16
  const root = resolve(packageRoot);
17
+ const nativeRuntimeDir = join(resolvedDataDir, 'native-runtime');
18
+ const runtimeConfigDir = join(resolvedDataDir, 'runtime-configs');
17
19
  return {
18
20
  dataDir: resolvedDataDir,
19
21
  graphEnvPath: join(resolvedDataDir, 'graph-provider.env'),
20
22
  graphRuntimeConfigPath: join(resolvedDataDir, 'graph-runtime.json'),
21
23
  graphRuntimeStatePath: join(resolvedDataDir, 'graph-runtime-state.json'),
24
+ adaptiveRuntimeSettingsPath: join(resolvedDataDir, 'adaptive-runtime-settings.json'),
25
+ adaptiveRuntimeStatePath: join(resolvedDataDir, 'adaptive-runtime-state.json'),
22
26
  runtimeSettingsPath: join(resolvedDataDir, 'runtime-settings.json'),
27
+ nativeRuntimeDir,
28
+ nativeRuntimeManifestPath: join(nativeRuntimeDir, 'manifest.json'),
29
+ nativeProcessStatePath: join(nativeRuntimeDir, 'processes.json'),
30
+ nativeNeo4jHome: join(nativeRuntimeDir, 'neo4j-community-5.26.28'),
31
+ nativeProviderVenvPath: join(nativeRuntimeDir, 'provider-venv'),
32
+ nativePersonalDir: join(nativeRuntimeDir, 'personal'),
33
+ nativeWorkspaceDir: join(nativeRuntimeDir, 'workspace'),
34
+ containerGraphConfigProfilePath: join(runtimeConfigDir, 'container.json'),
35
+ nativeGraphConfigProfilePath: join(runtimeConfigDir, 'native.json'),
23
36
  externalKnowledgeRegistryPath: join(
24
37
  resolvedDataDir,
25
38
  'external-knowledge',
@@ -36,6 +49,7 @@ export function resolveSetupPaths({
36
49
  'connectors'
37
50
  ),
38
51
  backupDir: join(resolvedDataDir, 'backups', 'agents'),
52
+ graphBackupDir: join(resolvedDataDir, 'backups', 'graph'),
39
53
  logPath: join(resolvedDataDir, 'logs', 'runtime.log'),
40
54
  serverPath: join(root, 'src', 'server.js'),
41
55
  mcpServerPath: join(root, 'src', 'mcp-server.js'),
@@ -1,4 +1,5 @@
1
1
  import { backupAgentConfig } from './config-backup.js';
2
+ import { totalmem } from 'node:os';
2
3
  import { inspectAgentInstallations } from './agent-installation-status.js';
3
4
  import { connectAgent, discoverAgents } from './agents.js';
4
5
  import { resolveSetupPaths } from './paths.js';
@@ -9,6 +10,11 @@ import {
9
10
  readRuntimeSettings,
10
11
  runtimeSettingsWithOverrides
11
12
  } from '../system/runtime-settings.js';
13
+ import {
14
+ DEFAULT_ADAPTIVE_RUNTIME_SETTINGS,
15
+ adaptiveRuntimeSettingsWithOverrides,
16
+ readAdaptiveRuntimeSettings
17
+ } from '../adaptive-runtime/settings.js';
12
18
 
13
19
  export function planLocalSetup(options, {
14
20
  env = process.env,
@@ -16,6 +22,9 @@ export function planLocalSetup(options, {
16
22
  discover = discoverAgents,
17
23
  inspectInstallations = inspectAgentInstallations,
18
24
  readSettings = readRuntimeSettings,
25
+ readAdaptiveSettings = readAdaptiveRuntimeSettings,
26
+ platform = process.platform,
27
+ hostTotalMemory = totalmem,
19
28
  nodePath = process.execPath
20
29
  } = {}) {
21
30
  const paths = resolvePaths({ dataDir: options.dataDir, env });
@@ -23,8 +32,16 @@ export function planLocalSetup(options, {
23
32
  ? readSettings(paths.runtimeSettingsPath)
24
33
  : DEFAULT_RUNTIME_SETTINGS;
25
34
  const runtimeSettings = runtimeSettingsWithOverrides(savedRuntimeSettings, {
26
- consolePort: options.port
35
+ consolePort: options.port,
36
+ graphRuntimeMode: options.runtimeMode
27
37
  });
38
+ const savedAdaptiveRuntimeSettings = paths.adaptiveRuntimeSettingsPath
39
+ ? readAdaptiveSettings(paths.adaptiveRuntimeSettingsPath)
40
+ : DEFAULT_ADAPTIVE_RUNTIME_SETTINGS;
41
+ const adaptiveRuntimeSettings = adaptiveRuntimeSettingsWithOverrides(
42
+ savedAdaptiveRuntimeSettings,
43
+ { enabled: options.adaptiveMemory ?? null }
44
+ );
28
45
  const discovered = options.skipAgents ? [] : discover({ env });
29
46
  const agents = options.codexOnly
30
47
  ? discovered.filter(({ id }) => id === 'codex')
@@ -32,6 +49,11 @@ export function planLocalSetup(options, {
32
49
  return {
33
50
  paths,
34
51
  runtimeSettings,
52
+ runtimeModeRecommendation: graphRuntimeModeRecommendation({
53
+ platform,
54
+ hostTotalBytes: hostTotalMemory()
55
+ }),
56
+ adaptiveRuntimeSettings,
35
57
  agents: inspectInstallations(agents, {
36
58
  nodePath,
37
59
  mcpServerPath: paths.mcpServerPath,
@@ -43,6 +65,17 @@ export function planLocalSetup(options, {
43
65
  };
44
66
  }
45
67
 
68
+ function graphRuntimeModeRecommendation({ platform, hostTotalBytes }) {
69
+ const lowMemoryMac = platform === 'darwin' &&
70
+ Number.isFinite(hostTotalBytes) &&
71
+ hostTotalBytes <= 16 * 1024 ** 3;
72
+ return {
73
+ recommendedMode: lowMemoryMac ? 'native' : 'container',
74
+ reason: lowMemoryMac ? 'low-memory-mac' : 'default',
75
+ hostTotalBytes
76
+ };
77
+ }
78
+
46
79
  export async function applyLocalSetup(plan, options, dependencies = {}) {
47
80
  const ensureRuntime = dependencies.ensureRuntime ?? ensureGraphRuntime;
48
81
  const backupConfig = dependencies.backupConfig ?? backupAgentConfig;
@@ -58,7 +91,10 @@ export async function applyLocalSetup(plan, options, dependencies = {}) {
58
91
  port: runtimeSettings.ports.console,
59
92
  lan: runtimeSettings.lanAccess,
60
93
  runtimeSettings,
94
+ runtimeMode: runtimeSettings.graphRuntimeMode,
61
95
  personalOnly: options.personalOnly,
96
+ memoryProfile: options.memoryProfile,
97
+ adaptiveRuntimeSettings: plan.adaptiveRuntimeSettings,
62
98
  noStart: options.noStart,
63
99
  env: dependencies.env ?? process.env,
64
100
  onProgress: dependencies.onProgress