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
@@ -7,6 +7,8 @@ import {
7
7
  stopGraphProviders
8
8
  } from '../setup/graph-runtime.js';
9
9
  import { readJsonFile } from '../storage/json-file.js';
10
+ import { readAdaptiveRuntimeSettings } from '../adaptive-runtime/settings.js';
11
+ import { readAdaptiveRuntimeState } from '../adaptive-runtime/state.js';
10
12
  import {
11
13
  DEFAULT_RUNTIME_SETTINGS,
12
14
  managedProviderUrls
@@ -28,6 +30,7 @@ export async function startLocalRuntime(input, dependencies = {}) {
28
30
  port: input.port,
29
31
  lan: input.lan === true,
30
32
  runtimeSettings,
33
+ previousRuntimeMode: input.previousRuntimeMode,
31
34
  personalOnly: !managesDevelopmentWorkspace,
32
35
  buildProviders,
33
36
  noStart: false,
@@ -51,11 +54,14 @@ export async function stopLocalRuntime(input, dependencies = {}) {
51
54
  const managesDevelopmentWorkspace = state?.managedProviders?.includes(
52
55
  'development-workspace'
53
56
  ) || hasManagedDevelopmentWorkspace(config, runtimeSettings);
57
+ const activeRuntimeMode = state?.runtimeSettings?.graphRuntimeMode ??
58
+ runtimeSettings.graphRuntimeMode;
54
59
 
55
60
  let providers = 'not_initialized';
56
61
  if (deps.fileExists(input.paths.graphEnvPath)) {
57
- deps.stopProviders(input.paths, input.paths.graphEnvPath, {
58
- personalOnly: !managesDevelopmentWorkspace
62
+ await deps.stopProviders(input.paths, input.paths.graphEnvPath, {
63
+ personalOnly: !managesDevelopmentWorkspace,
64
+ runtimeMode: activeRuntimeMode
59
65
  });
60
66
  providers = 'stopped';
61
67
  }
@@ -70,11 +76,14 @@ export async function stopLocalRuntime(input, dependencies = {}) {
70
76
  }
71
77
 
72
78
  export async function restartLocalRuntime(input, dependencies = {}) {
79
+ const deps = lifecycleDependencies(dependencies);
80
+ const previousRuntimeMode = deps.readState(input.paths.graphRuntimeStatePath)
81
+ ?.runtimeSettings?.graphRuntimeMode;
73
82
  const stopped = await stopLocalRuntime(input, dependencies);
74
83
  if (stopped.status === 'partial') {
75
84
  throw new Error('Could not safely verify the existing local UI process; restart was refused.');
76
85
  }
77
- const started = await startLocalRuntime(input, dependencies);
86
+ const started = await startLocalRuntime({ ...input, previousRuntimeMode }, dependencies);
78
87
  return { stopped, ...started, status: 'restarted' };
79
88
  }
80
89
 
@@ -90,19 +99,34 @@ export async function inspectLocalRuntime(input, dependencies = {}) {
90
99
  state.pid,
91
100
  state.version
92
101
  );
102
+ const adaptiveSettings = deps.readAdaptiveSettings(pathsValue(
103
+ input.paths.adaptiveRuntimeSettingsPath
104
+ ));
105
+ const adaptiveState = adaptiveSettings.enabled
106
+ ? deps.readAdaptiveState(pathsValue(input.paths.adaptiveRuntimeStatePath))
107
+ : null;
108
+ const intentionallySleeping = adaptiveSettings.enabled &&
109
+ ['provider-sleeping', 'sleeping'].includes(adaptiveState?.stage);
93
110
  const managedUrls = managedProviderUrls(runtimeSettings);
94
111
  const personalUrl = config?.personal?.providerUrl ?? managedUrls.personal;
95
112
  const personal = config
96
- ? await deps.providerHealth(personalUrl)
113
+ ? intentionallySleeping
114
+ ? { url: personalUrl, status: 'sleeping' }
115
+ : await deps.providerHealth(personalUrl)
97
116
  : { url: personalUrl, status: 'not_configured' };
98
- const workspaces = await Promise.all((config?.workspaces ?? []).map(async (workspace) => ({
99
- ...(await deps.providerHealth(workspace.providerUrl)),
100
- managedDevelopment: isManagedDevelopmentUrl(workspace, runtimeSettings)
101
- })));
117
+ const workspaces = await Promise.all((config?.workspaces ?? []).map(async (workspace) => {
118
+ const managedDevelopment = isManagedDevelopmentUrl(workspace, runtimeSettings);
119
+ return {
120
+ ...(intentionallySleeping && managedDevelopment
121
+ ? { url: workspace.providerUrl, status: 'sleeping' }
122
+ : await deps.providerHealth(workspace.providerUrl)),
123
+ managedDevelopment
124
+ };
125
+ }));
102
126
 
103
127
  let status = 'stopped';
104
128
  if (!config && !state) status = 'not_configured';
105
- else if (consoleReady && personal.status === 'ready') status = 'running';
129
+ else if (consoleReady && ['ready', 'sleeping'].includes(personal.status)) status = 'running';
106
130
  else if (processAlive || personal.status === 'ready') status = 'degraded';
107
131
 
108
132
  return {
@@ -120,6 +144,10 @@ export async function inspectLocalRuntime(input, dependencies = {}) {
120
144
  configured: workspaces.length > 0,
121
145
  status: publicProviderStatus(workspaces),
122
146
  providers: workspaces
147
+ },
148
+ adaptiveRuntime: {
149
+ enabled: adaptiveSettings.enabled,
150
+ stage: adaptiveSettings.enabled ? adaptiveState?.stage ?? 'unknown' : 'always-on'
123
151
  }
124
152
  };
125
153
  }
@@ -162,6 +190,10 @@ function lifecycleDependencies(overrides) {
162
190
  stopProviders: stopGraphProviders,
163
191
  consoleHealth: checkLocalConsoleHealth,
164
192
  providerHealth,
193
+ readAdaptiveSettings: (path) => path
194
+ ? readAdaptiveRuntimeSettings(path)
195
+ : { enabled: false },
196
+ readAdaptiveState: (path) => path ? readAdaptiveRuntimeState(path) : null,
165
197
  isProcessAlive,
166
198
  stopProcess,
167
199
  waitForExit,
@@ -173,6 +205,10 @@ function lifecycleDependencies(overrides) {
173
205
  };
174
206
  }
175
207
 
208
+ function pathsValue(value) {
209
+ return typeof value === 'string' && value ? value : null;
210
+ }
211
+
176
212
  function safeReadState(path) {
177
213
  try {
178
214
  return readJsonFile(path, null);
@@ -242,7 +278,7 @@ function isLoopbackConsoleUrl(value) {
242
278
 
243
279
  function publicProviderStatus(workspaces) {
244
280
  if (!workspaces.length) return 'not_connected';
245
- const ready = workspaces.filter(({ status }) => status === 'ready').length;
281
+ const ready = workspaces.filter(({ status }) => ['ready', 'sleeping'].includes(status)).length;
246
282
  if (ready === workspaces.length) return 'ready';
247
283
  return ready > 0 ? 'degraded' : 'unavailable';
248
284
  }
@@ -6,7 +6,9 @@ const RESOURCE_MIME_TYPE = 'text/markdown';
6
6
  const MAX_PROJECT_PURPOSE_LENGTH = 240;
7
7
  const MAX_RESOURCE_TEXT_BYTES = 32 * 1024;
8
8
 
9
- export function registerFuliContextResources(server, app) {
9
+ export function registerFuliContextResources(server, app, {
10
+ withRuntimeLease = (_owner, operation) => operation()
11
+ } = {}) {
10
12
  server.registerResource(
11
13
  'FULI global taste',
12
14
  GLOBAL_TASTE_RESOURCE_URI,
@@ -15,13 +17,17 @@ export function registerFuliContextResources(server, app) {
15
17
  description: 'The user\'s current evidence-backed global taste and working preferences.',
16
18
  mimeType: RESOURCE_MIME_TYPE
17
19
  },
18
- (uri) => readGlobalTasteResource(app, uri)
20
+ (uri) => withRuntimeLease(
21
+ 'mcp-resource:global-taste',
22
+ () => readGlobalTasteResource(app, uri)
23
+ )
19
24
  );
20
25
 
21
26
  const projectResources = new ResourceTemplate(PROJECT_RESOURCE_TEMPLATE, {
22
- list: async () => ({
23
- resources: await listPersonalProjectResources(app)
24
- })
27
+ list: () => withRuntimeLease(
28
+ 'mcp-resource:list-projects',
29
+ async () => ({ resources: await listPersonalProjectResources(app) })
30
+ )
25
31
  });
26
32
  server.registerResource(
27
33
  'fuli-personal-project',
@@ -31,7 +37,10 @@ export function registerFuliContextResources(server, app) {
31
37
  description: 'Select one exact local FULI personal project for this task.',
32
38
  mimeType: RESOURCE_MIME_TYPE
33
39
  },
34
- (uri, variables) => readPersonalProjectResource(app, uri, variables)
40
+ (uri, variables) => withRuntimeLease(
41
+ 'mcp-resource:project',
42
+ () => readPersonalProjectResource(app, uri, variables)
43
+ )
35
44
  );
36
45
  }
37
46
 
@@ -19,7 +19,8 @@ import { jsonSchemaToZod, openObjectSchema } from './tool-schema.js';
19
19
  import {
20
20
  mcpHostSessionId,
21
21
  nativeCodexThreadId,
22
- normalizeAgentSessionInput
22
+ normalizeAgentSessionInput,
23
+ normalizeMcpSourceApplication
23
24
  } from './session-id.js';
24
25
 
25
26
  const TOOL_RESULT_LIMIT_BYTES = Object.freeze({
@@ -33,6 +34,24 @@ const TOOL_RESULT_LIMIT_BYTES = Object.freeze({
33
34
  discover_personal_global_preference_candidates: 32 * 1024,
34
35
  preview_personal_global_preference_decision: 32 * 1024,
35
36
  list_personal_projects: 32 * 1024,
37
+ list_project_agents: 32 * 1024,
38
+ get_project_agent: 32 * 1024,
39
+ list_project_agent_assignments: 32 * 1024,
40
+ get_project_agent_context: 64 * 1024,
41
+ coordinate_project_agent_task: 256 * 1024,
42
+ acquire_runtime_lease: 4 * 1024,
43
+ refresh_runtime_lease: 4 * 1024,
44
+ release_runtime_lease: 4 * 1024,
45
+ view_project_agent_task: 64 * 1024,
46
+ view_project_agent_activity: 64 * 1024,
47
+ list_project_agent_recruitments: 32 * 1024,
48
+ list_executors: 32 * 1024,
49
+ get_executor: 32 * 1024,
50
+ preflight_executor: 32 * 1024,
51
+ record_project_agent_executor_actual: 32 * 1024,
52
+ list_executor_routing_rules: 32 * 1024,
53
+ get_executor_routing_rule: 32 * 1024,
54
+ list_project_agent_routing_learning: 64 * 1024,
36
55
  list_knowledge_review_candidates: 32 * 1024,
37
56
  list_workflow_candidates: 32 * 1024,
38
57
  recommend_next_workflow_steps: 32 * 1024
@@ -40,7 +59,12 @@ const TOOL_RESULT_LIMIT_BYTES = Object.freeze({
40
59
 
41
60
  export function createMcpServer(
42
61
  app,
43
- { env = process.env, clock = () => new Date() } = {}
62
+ {
63
+ env = process.env,
64
+ clock = () => new Date(),
65
+ sourceApplication = nativeCodexThreadId(env) ? 'codex' : 'other',
66
+ withRuntimeLease = (_owner, operation) => operation()
67
+ } = {}
44
68
  ) {
45
69
  const server = new McpServer(
46
70
  { name: 'fuli', version: FULI_VERSION },
@@ -48,15 +72,20 @@ export function createMcpServer(
48
72
  );
49
73
 
50
74
  const nativeThreadId = nativeCodexThreadId(env);
75
+ const authoritativeSourceApplication = normalizeMcpSourceApplication(
76
+ sourceApplication
77
+ );
51
78
  const tools = createToolMap(
52
79
  app,
53
80
  createProjectActionPreviewTokens(),
54
81
  nativeThreadId,
55
82
  mcpHostSessionId(env),
56
- clock
83
+ clock,
84
+ authoritativeSourceApplication,
85
+ withRuntimeLease
57
86
  );
58
87
  for (const tool of tools.values()) registerTool(server, tool);
59
- registerFuliContextResources(server, app);
88
+ registerFuliContextResources(server, app, { withRuntimeLease });
60
89
  registerCallHandler(server, tools);
61
90
  return server;
62
91
  }
@@ -66,24 +95,30 @@ function createToolMap(
66
95
  projectActionPreviews,
67
96
  nativeThreadId,
68
97
  hostSessionId,
69
- clock
98
+ clock,
99
+ sourceApplication,
100
+ withRuntimeLease
70
101
  ) {
71
102
  const commonKnowledgePreviews = createCommonKnowledgePreviewTokens();
72
103
  return new Map(listAgentTools().map((definition) => [definition.name, {
73
104
  definition,
74
105
  schema: jsonSchemaToZod(definition.inputSchema),
75
- invoke: (input) => invokeAgentTool(
76
- app,
77
- definition.name,
78
- normalizeAgentSessionInput(
106
+ invoke: (input) => withRuntimeLease(
107
+ `mcp-tool:${definition.name}`,
108
+ () => invokeAgentTool(
109
+ app,
79
110
  definition.name,
80
- input,
81
- nativeThreadId,
82
- hostSessionId,
83
- clock
84
- ),
85
- projectActionPreviews,
86
- commonKnowledgePreviews
111
+ normalizeAgentSessionInput(
112
+ definition.name,
113
+ input,
114
+ nativeThreadId,
115
+ hostSessionId,
116
+ clock,
117
+ sourceApplication
118
+ ),
119
+ projectActionPreviews,
120
+ commonKnowledgePreviews
121
+ )
87
122
  )
88
123
  }]));
89
124
  }
@@ -1,7 +1,9 @@
1
1
  export const MCP_INSTRUCTIONS = [
2
- 'Each user task: 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. Apply all effective_preferences: personal-global everywhere, matched project, authorized inheritable parent. For taste/judgment, call get_user_taste_skill; never replaces a user-authored Skill. FULI fuli:// exposes exact projects/global taste; an @ selection is explicit and adds no project. Resolve relevant deferred_conflict first; never guess personalProjectId. Writes enforce actual payload; final text is not compliance.',
3
- 'Before asking user to repeat a stable project fact or method, inspect task_knowledge_recall. On miss call search_current_project_knowledge with focused action, artifact, target-system, or identifier queries; never use the whole conversational request as the only query. Search active child first, then inheritable parent via authorized PART_OF/USES_KNOWLEDGE_FROM, 2 hops; parent prefs need descendants/selected_projects; local_only/RELATED_TO never inherit; authority > distance > weight; equal conflicts defer.',
4
- 'Before saying unknown, call search_knowledge_graph. Use exact IDs for extra projects. RELATED_TO suggests; ask first. Use all_local_confirmed only after consent, then rg only in safe current repo/workspace; never scan outside it. Live state: monitoring/Git MCP. External/public: search_connected_knowledge; agent_decide is response-only.',
5
- 'Use record_decision_trace; record_knowledge_feedback on rejected/outdated/failed items. Batch durable confirmed knowledge with capture_session_knowledge; no secrets/guesses. Finish with checkpoint_task_knowledge: capture_candidates or retain_nothing. Then record_knowledge_usage.',
6
- 'Terminal-safe Markdown. A match is a candidate only. If it supports the answer, MUST begin with sourceMarker.leadMarkdown and append sourceMarker.markdown; otherwise use noMatchSourceMarker (empty). Keep unchanged; never wrap in HTML.'
2
+ 'Each user task: hook-provided task context via begin_task_context; otherwise before other tools/answer call exactly get_collaboration_preferences(projectPath=cwd taskPrompt=current user request) as fallback; never substitute a project action. Apply all effective_preferences: personal-global everywhere; matched project, authorized inheritable parent. get_user_taste_skill never replaces a user-authored Skill. Resolve deferred_conflict; never guess personalProjectId. Writes use actual payload; final text not compliance.',
3
+ 'Before asking for stable project fact or method inspect task_knowledge_recall. On miss search_current_project_knowledge with focused action, artifact, target-system, or identifier queries; never use the whole conversational request as the only query. Search active child first, then authorized inheritable parent (PART_OF/USES_KNOWLEDGE_FROM); local_only/RELATED_TO never inherit.',
4
+ 'Before saying unknown call search_knowledge_graph; exact IDs for extra projects. RELATED_TO suggests; ask. all_local_confirmed only after consent; rg safe current repo/workspace; Never scan outside it. monitoring/Git MCP; search_connected_knowledge; agent_decide response-only.',
5
+ 'record_decision_trace; failures record_knowledge_feedback. Batch durable confirmed knowledge: capture_session_knowledge; no secrets. checkpoint_task_knowledge: capture_candidates or retain_nothing; record_knowledge_usage.',
6
+ 'Team: coordinate_project_agent_task gives isolated contexts; Fuli never spawns. Host: acquire_runtime_lease, start workers, report activity/executor, release_runtime_lease in finally.',
7
+ 'Non-empty Provider executionSummary: one row/worker: occupation emoji/label, actual executor/sourceApplication, work summary, workerStatus/status. Empty/absent executionSummary: omit. Configured/allowed/available are not evidence.',
8
+ 'Terminal-safe Markdown; never wrap in HTML. A match is a candidate only. If it supports the answer, MUST begin sourceMarker.leadMarkdown and append sourceMarker.markdown; otherwise use noMatchSourceMarker (empty). Keep unchanged.'
7
9
  ].join(' ');
@@ -1,16 +1,28 @@
1
1
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
2
 
3
+ import {
4
+ bindRuntimeLeaseAgentTools,
5
+ createRuntimeLeaseClient
6
+ } from '../adaptive-runtime/lease-client.js';
3
7
  import { openFederatedGraphApplication } from '../graphiti/federated-application.js';
4
8
  import { createMcpServer } from './create-mcp-server.js';
5
9
 
6
- export async function runStdio({ runtimeConfigPath }) {
10
+ export async function runStdio({ runtimeConfigPath, sourceApplication }) {
7
11
  const app = openFederatedGraphApplication({ runtimeConfigPath });
8
- const server = createServerOrClose(app);
12
+ const leaseClient = createRuntimeLeaseClient({ runtimeConfigPath });
13
+ bindRuntimeLeaseAgentTools(app, leaseClient);
14
+ const server = createServerOrClose(app, createMcpServer, {
15
+ sourceApplication,
16
+ withRuntimeLease: leaseClient.withGraphLease
17
+ });
9
18
  const transport = new StdioServerTransport();
10
19
  const signalHandlers = new Map();
11
20
  const close = createCloseOnce({
12
21
  closeServer: () => server.close(),
13
- closeApplication: () => app.close(),
22
+ closeApplication: async () => {
23
+ await leaseClient.close();
24
+ return app.close();
25
+ },
14
26
  afterClose: () => removeHandlers(signalHandlers)
15
27
  });
16
28
  const quietClose = createQuietCloser(close, () => { process.exitCode = 1; });
@@ -31,9 +43,9 @@ export async function runStdio({ runtimeConfigPath }) {
31
43
  }
32
44
  }
33
45
 
34
- export function createServerOrClose(app, factory = createMcpServer) {
46
+ export function createServerOrClose(app, factory = createMcpServer, options) {
35
47
  try {
36
- return factory(app);
48
+ return factory(app, options);
37
49
  } catch (error) {
38
50
  void app.close();
39
51
  throw error;
@@ -2,6 +2,17 @@ import { randomUUID } from 'node:crypto';
2
2
 
3
3
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
4
  const WORKFLOW_OBSERVATION_TOOL = 'record_workflow_transition_observation';
5
+ const PROJECT_AGENT_SOURCE_TOOLS = new Set([
6
+ 'create_project_agent_assignment',
7
+ 'replace_project_agent_assignment',
8
+ 'coordinate_project_agent_task',
9
+ 'submit_project_agent_task',
10
+ 'get_project_agent_context',
11
+ 'record_project_agent_task_activity',
12
+ 'preflight_executor',
13
+ 'report_executor_health',
14
+ 'record_project_agent_executor_actual'
15
+ ]);
5
16
 
6
17
  const SESSION_ID_TOOLS = new Set([
7
18
  'begin_task_context',
@@ -31,7 +42,8 @@ export function normalizeAgentSessionInput(
31
42
  input,
32
43
  nativeThreadId,
33
44
  hostSessionId = nativeThreadId,
34
- clock = () => new Date()
45
+ clock = () => new Date(),
46
+ sourceApplication = nativeThreadId ? 'codex' : 'other'
35
47
  ) {
36
48
  if (name === WORKFLOW_OBSERVATION_TOOL) {
37
49
  if (!hostSessionId) {
@@ -51,6 +63,24 @@ export function normalizeAgentSessionInput(
51
63
  hostObservedAt: clock().toISOString()
52
64
  };
53
65
  }
66
+ if (PROJECT_AGENT_SOURCE_TOOLS.has(name)) {
67
+ if (!hostSessionId) {
68
+ throw new TypeError('Project Agent calls require an MCP host session');
69
+ }
70
+ const safeSourceApplication = normalizeMcpSourceApplication(sourceApplication);
71
+ const {
72
+ sourceApplication: _sourceApplication,
73
+ source_application: _source_application,
74
+ sourceSessionId: _sourceSessionId,
75
+ source_session_id: _source_session_id,
76
+ ...safeInput
77
+ } = input ?? {};
78
+ return {
79
+ ...safeInput,
80
+ sourceApplication: safeSourceApplication,
81
+ sourceSessionId: hostSessionId
82
+ };
83
+ }
54
84
  if (!nativeThreadId || !SESSION_ID_TOOLS.has(name)) return input;
55
85
  if (!input || typeof input !== 'object' || Array.isArray(input)) return input;
56
86
  if (typeof input.sessionId !== 'string' || input.sessionId.trim() === nativeThreadId) {
@@ -58,3 +88,10 @@ export function normalizeAgentSessionInput(
58
88
  }
59
89
  return { ...input, sessionId: nativeThreadId };
60
90
  }
91
+
92
+ export function normalizeMcpSourceApplication(value) {
93
+ if (!['codex', 'claude_code', 'cursor', 'kiro', 'other'].includes(value)) {
94
+ throw new TypeError('Unsupported MCP source application');
95
+ }
96
+ return value;
97
+ }
@@ -7,6 +7,19 @@ const READ_TOOLS = new Set([
7
7
  'preview_common_knowledge_promotion',
8
8
  'list_knowledge_spaces',
9
9
  'list_personal_projects',
10
+ 'list_project_agents',
11
+ 'get_project_agent',
12
+ 'list_project_agent_assignments',
13
+ 'get_project_agent_context',
14
+ 'view_project_agent_task',
15
+ 'view_project_agent_activity',
16
+ 'get_project_agent_recruitment_policy',
17
+ 'list_project_agent_recruitments',
18
+ 'list_executors',
19
+ 'get_executor',
20
+ 'list_executor_routing_rules',
21
+ 'get_executor_routing_rule',
22
+ 'list_project_agent_routing_learning',
10
23
  'list_knowledge_review_candidates',
11
24
  'list_workflow_candidates',
12
25
  'recommend_next_workflow_steps',
@@ -36,6 +49,32 @@ const WRITE_TOOLS = new Set([
36
49
  'search_human_knowledge_changes',
37
50
  'review_human_knowledge_change',
38
51
  'upsert_personal_project',
52
+ 'upsert_project_agent',
53
+ 'delete_project_agent',
54
+ 'cleanup_test_project_agents',
55
+ 'create_project_agent_assignment',
56
+ 'end_project_agent_assignment',
57
+ 'replace_project_agent_assignment',
58
+ 'coordinate_project_agent_task',
59
+ 'acquire_runtime_lease',
60
+ 'refresh_runtime_lease',
61
+ 'release_runtime_lease',
62
+ 'submit_project_agent_task',
63
+ 'record_project_agent_task_activity',
64
+ 'update_project_agent_recruitment_policy',
65
+ 'decide_project_agent_recruitment',
66
+ 'upsert_executor',
67
+ 'delete_executor',
68
+ 'preflight_executor',
69
+ 'authorize_executor',
70
+ 'report_executor_health',
71
+ 'upsert_executor_routing_rule',
72
+ 'update_executor_routing_rule',
73
+ 'delete_executor_routing_rule',
74
+ 'record_project_agent_task_outcome',
75
+ 'record_project_agent_executor_actual',
76
+ 'ignore_project_agent_routing_learning',
77
+ 'reset_project_agent_routing_learning',
39
78
  'start_knowledge_review',
40
79
  'record_knowledge_review_progress',
41
80
  'finish_knowledge_review',
@@ -69,12 +108,42 @@ export function annotationsFor(name) {
69
108
  readOnlyHint: readOnly,
70
109
  destructiveHint: DESTRUCTIVE_TOOLS.has(name),
71
110
  idempotentHint: readOnly || name === 'upsert_personal_project' ||
111
+ name === 'upsert_project_agent' ||
112
+ name === 'delete_project_agent' ||
113
+ name === 'cleanup_test_project_agents' ||
114
+ name === 'create_project_agent_assignment' ||
115
+ name === 'coordinate_project_agent_task' ||
116
+ name === 'refresh_runtime_lease' ||
117
+ name === 'release_runtime_lease' ||
118
+ name === 'submit_project_agent_task' ||
119
+ name === 'record_project_agent_task_activity' ||
120
+ name === 'record_project_agent_task_outcome' ||
121
+ name === 'record_project_agent_executor_actual' ||
122
+ name === 'upsert_executor' ||
123
+ name === 'preflight_executor' ||
124
+ name === 'authorize_executor' ||
125
+ name === 'report_executor_health' ||
126
+ name === 'upsert_executor_routing_rule' ||
127
+ name === 'update_executor_routing_rule' ||
72
128
  name === 'publish_personal_project' || name === 'unsubscribe_public_project' ||
73
129
  name === 'record_knowledge_usage' || name === 'record_decision_trace' ||
74
130
  name === 'record_knowledge_feedback' ||
75
131
  name === 'record_workflow_transition_observation' ||
76
132
  name === 'checkpoint_task_knowledge',
77
133
  openWorldHint: name === 'capture_session_knowledge' ||
134
+ name === 'coordinate_project_agent_task' ||
135
+ name === 'submit_project_agent_task' ||
136
+ name === 'record_project_agent_task_activity' ||
137
+ name === 'preflight_executor' ||
138
+ name === 'record_project_agent_task_outcome' ||
139
+ name === 'record_project_agent_executor_actual' ||
140
+ name === 'upsert_executor' ||
141
+ name === 'delete_executor' ||
142
+ name === 'authorize_executor' ||
143
+ name === 'report_executor_health' ||
144
+ name === 'upsert_executor_routing_rule' ||
145
+ name === 'update_executor_routing_rule' ||
146
+ name === 'delete_executor_routing_rule' ||
78
147
  name === 'record_workflow_transition_observation' ||
79
148
  name === 'search_connected_knowledge' ||
80
149
  name === 'record_decision_trace' ||
package/src/mcp-server.js CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { callAgentTool, listAgentTools } from './agent-tools.js';
3
+ import {
4
+ bindRuntimeLeaseAgentTools,
5
+ createRuntimeLeaseClient
6
+ } from './adaptive-runtime/lease-client.js';
3
7
  import { ApplicationError } from './app/application-error.js';
4
8
  import { openFederatedGraphApplication } from './graphiti/federated-application.js';
5
9
  import {
@@ -7,6 +11,7 @@ import {
7
11
  resolveGraphRuntimeOptions
8
12
  } from './graphiti/runtime-config.js';
9
13
  import { runStdio } from './mcp/runtime.js';
14
+ import { normalizeMcpSourceApplication } from './mcp/session-id.js';
10
15
  import { applicationErrorMessage } from './mcp/tool-result.js';
11
16
 
12
17
  await main(process.argv.slice(2)).catch((error) => {
@@ -29,19 +34,30 @@ async function main(args) {
29
34
  await runCall({ args, runtimeConfigPath });
30
35
  return;
31
36
  }
32
- await runStdio({ runtimeConfigPath });
37
+ const configuredSourceApplication = args.includes('--source-application')
38
+ ? requiredOption(args, '--source-application')
39
+ : null;
40
+ await runStdio({
41
+ runtimeConfigPath,
42
+ sourceApplication: configuredSourceApplication
43
+ ? normalizeMcpSourceApplication(configuredSourceApplication)
44
+ : undefined
45
+ });
33
46
  }
34
47
 
35
48
  async function runCall({ args, runtimeConfigPath }) {
36
49
  const app = openFederatedGraphApplication({ runtimeConfigPath });
50
+ const leaseClient = createRuntimeLeaseClient({ runtimeConfigPath });
51
+ bindRuntimeLeaseAgentTools(app, leaseClient);
37
52
  try {
38
- const result = await callAgentTool(
39
- app,
40
- requiredOption(args, '--call'),
41
- parseInput(args)
53
+ const toolName = requiredOption(args, '--call');
54
+ const result = await leaseClient.withGraphLease(
55
+ `cli-call:${toolName}`,
56
+ () => callAgentTool(app, toolName, parseInput(args))
42
57
  );
43
58
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
44
59
  } finally {
60
+ await leaseClient.close();
45
61
  await app.close();
46
62
  }
47
63
  }
@@ -69,7 +85,8 @@ function startupMessage(error) {
69
85
  }
70
86
 
71
87
  function helpText() {
72
- return 'Usage: node src/mcp-server.js [--runtime-config <graph-runtime.json>]\n' +
88
+ return 'Usage: node src/mcp-server.js [--runtime-config <graph-runtime.json>] ' +
89
+ '[--source-application <codex|claude_code|cursor|kiro|other>]\n' +
73
90
  ' node src/mcp-server.js --tools\n' +
74
91
  ' node src/mcp-server.js --runtime-config <graph-runtime.json> ' +
75
92
  '--call <tool> [--input <json>]\n';