fuli-context 0.12.0 → 0.12.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.
Files changed (160) hide show
  1. package/README.md +14 -6
  2. package/README.zh-CN.md +9 -4
  3. package/dist/web/assets/{AboutPage-BAD0iPVX.js → AboutPage-Dized8Uz.js} +1 -1
  4. package/dist/web/assets/{AgentConversations-1iGTd4sz.js → AgentConversations-Ccy41mEA.js} +1 -1
  5. package/dist/web/assets/{AgentHand-D_RafKOM.js → AgentHand-UzVhbKUC.js} +1 -1
  6. package/dist/web/assets/{AgentName-B5LKQ09q.js → AgentName-DJpvkub2.js} +1 -1
  7. package/dist/web/assets/AgentProfilePage-CFViQ8kj.js +1 -0
  8. package/dist/web/assets/AgentsDirectoryPage-CBFTg_IE.js +1 -0
  9. package/dist/web/assets/AgentsDirectoryPage-Cv-vxw2C.css +1 -0
  10. package/dist/web/assets/{ConnectionsPage-Tr2rRVOZ.js → ConnectionsPage-uXcabab_.js} +1 -1
  11. package/dist/web/assets/EmployeeRecruitDialog-D8Hvhc-M.js +1 -0
  12. package/dist/web/assets/EmployeeWorkbenchPage-TBl_HNj6.js +3 -0
  13. package/dist/web/assets/KnowledgeEditDialog-HmiqLC-Y.js +1 -0
  14. package/dist/web/assets/KnowledgeInspector-Cj2yK4Qh.js +1 -0
  15. package/dist/web/assets/KnowledgeOrganizerPage-DT3BROZm.js +1 -0
  16. package/dist/web/assets/KnowledgePage-C4J4VueL.js +1 -0
  17. package/dist/web/assets/KnowledgeWorkspace-DA_dqVRG.js +4 -0
  18. package/dist/web/assets/{NotFoundPage-fetSnp0N.js → NotFoundPage-x_Yd59mm.js} +1 -1
  19. package/dist/web/assets/{OverviewPage-BD4yY2Xz.js → OverviewPage-3eZjQ26v.js} +1 -1
  20. package/dist/web/assets/OverviewPage-8LBN8Shi.css +1 -0
  21. package/dist/web/assets/PersonalProfilePage-CDQk096L.js +1 -0
  22. package/dist/web/assets/PersonalProjectsPage-CJxp4Wyz.js +1 -0
  23. package/dist/web/assets/{ProjectAgentFirstTask-BcxqmKXQ.js → ProjectAgentFirstTask-BkRJ0zwO.js} +1 -1
  24. package/dist/web/assets/{ProjectAgentsPage-BWDJBESb.js → ProjectAgentsPage-BuNNqSEy.js} +8 -8
  25. package/dist/web/assets/{ProjectScopePicker-yodM7lF_.js → ProjectScopePicker-CiDxM2Yx.js} +1 -1
  26. package/dist/web/assets/PublicProjectsPage-ByOT559r.js +1 -0
  27. package/dist/web/assets/{ReviewPage-Cy0Rt4CO.js → ReviewPage-C0Vdr9uq.js} +1 -1
  28. package/dist/web/assets/RoundtablesPage-Drou-B81.css +1 -0
  29. package/dist/web/assets/RoundtablesPage-L3ShEWS3.js +1 -0
  30. package/dist/web/assets/SettingsPage-BE8pWbI6.css +1 -0
  31. package/dist/web/assets/SettingsPage-McYuinGz.js +1 -0
  32. package/dist/web/assets/{SharedJefaReportPage-C56kPciB.js → SharedJefaReportPage-Bg6lIk3E.js} +1 -1
  33. package/dist/web/assets/UiButton-x8hiHNRM.js +1 -0
  34. package/dist/web/assets/UiDialog-mjletK0y.js +1 -0
  35. package/dist/web/assets/{UiDisclosure-D0iMTplF.js → UiDisclosure-_IxeiexW.js} +1 -1
  36. package/dist/web/assets/{UiSegmented-DIJW6wv3.js → UiSegmented-WFfz9Evl.js} +1 -1
  37. package/dist/web/assets/{UiSelect-CMR0QqF6.js → UiSelect-K29jnV-1.js} +1 -1
  38. package/dist/web/assets/{WritingTastePage-QgNzssfF.js → WritingTastePage-DCULB-d2.js} +1 -1
  39. package/dist/web/assets/agent-pins-DS4mNsNt.js +1 -0
  40. package/dist/web/assets/agent-profile-Bh3MJGvb.css +1 -0
  41. package/dist/web/assets/{attention-store-B1DI8wmd.js → attention-store-CA5y8j1R.js} +1 -1
  42. package/dist/web/assets/catalog-D0VEfOAV.js +1 -0
  43. package/dist/web/assets/{client-CR-W5Wzp.js → client-DnwnUBQV.js} +1 -1
  44. package/dist/web/assets/{console-D7GHOMEC.js → console-Cw7TzOSC.js} +1 -1
  45. package/dist/web/assets/conversation-launcher-settings-Yc13viwr.js +1 -0
  46. package/dist/web/assets/{i18n-CVFhT_k7.js → i18n-oJFlt8hW.js} +3 -3
  47. package/dist/web/assets/{identity-CM6Dmy00.js → identity-DSGS4CZc.js} +1 -1
  48. package/dist/web/assets/index-BfHCcUL2.css +1 -0
  49. package/dist/web/assets/index-CxNER7Oo.js +5 -0
  50. package/dist/web/assets/{judgment-types-CJBEyJs1.js → judgment-types-DR931cZ7.js} +1 -1
  51. package/dist/web/assets/{model-WFhQZ3v_.js → model-BJ-UF4cs.js} +1 -1
  52. package/dist/web/assets/{personal-profile-graph-BwCh2M9G.js → personal-profile-graph-SOK0BR0w.js} +1 -1
  53. package/dist/web/assets/project-team-api-C_0qJk_B.js +1 -0
  54. package/dist/web/assets/{shared-report-CeIvrL9a.js → shared-report-CiX64ZiD.js} +1 -1
  55. package/dist/web/assets/useAgentRoster-B84mzZIg.js +1 -0
  56. package/dist/web/assets/{useApi-CROJJdhE-BYhbjaZB.js → useApi-CROJJdhE-7gt0QyQ5.js} +1 -1
  57. package/dist/web/assets/{vue-router-DaxrQwX3.js → vue-router-CCvPGaLD.js} +1 -1
  58. package/dist/web/index.html +15 -14
  59. package/dist/web/licenses/ant-design-icons.txt +22 -0
  60. package/docs/agent-conversations-and-collaboration.md +3 -3
  61. package/docs/agent-roundtable.md +33 -0
  62. package/docs/client-connections.md +108 -0
  63. package/docs/employee-agents.md +3 -3
  64. package/docs/project-agent-memory.md +6 -6
  65. package/graph-provider/fuli_graph/default_project_lead.py +1 -1
  66. package/graph-provider/fuli_graph/project_agent_delegations.py +70 -2
  67. package/graph-provider/fuli_graph/project_agent_memory_routes.py +18 -1
  68. package/graph-provider/fuli_graph/project_agent_remote_origins.py +154 -0
  69. package/graph-provider/fuli_graph/project_agent_task_models.py +1 -1
  70. package/graph-provider/fuli_graph/runtime.py +7 -0
  71. package/graph-provider/fuli_graph/store_project_agent_context.py +3 -2
  72. package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +1 -1
  73. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +1 -1
  74. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +5 -5
  75. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +2 -2
  76. package/graph-provider/fuli_graph/store_project_agent_tasks.py +2 -2
  77. package/graph-provider/fuli_graph/store_project_agents.py +6 -6
  78. package/graph-provider/fuli_graph/system_agents.py +12 -2
  79. package/graph-provider/fuli_graph/system_hr_identity.py +2 -2
  80. package/graph-provider/pyproject.toml +2 -1
  81. package/npm-shrinkwrap.json +280 -5
  82. package/package.json +6 -2
  83. package/skills/operating-fuli/SKILL.md +3 -3
  84. package/src/agent-peer/coordinator-http.js +117 -0
  85. package/src/agent-peer/coordinator-service.js +258 -0
  86. package/src/agent-peer/coordinator-store.js +95 -0
  87. package/src/agent-peer/device-identity.js +70 -0
  88. package/src/agent-peer/http.js +47 -0
  89. package/src/agent-peer/node-store.js +127 -0
  90. package/src/agent-peer/origin-proof.js +56 -0
  91. package/src/agent-peer/peer-port.js +140 -0
  92. package/src/agent-peer/peer-runtime.js +298 -0
  93. package/src/agent-peer/peer-transport.js +78 -0
  94. package/src/agent-peer/protocol.js +161 -0
  95. package/src/agent-peer/runner.js +251 -0
  96. package/src/agent-roundtable/http.js +17 -3
  97. package/src/agent-roundtable/service.js +272 -43
  98. package/src/agent-roundtable/tools.js +15 -1
  99. package/src/agent-roundtable/wake.js +12 -3
  100. package/src/agent-tools/graph-definitions.js +14 -5
  101. package/src/agent-tools/graph-handlers.js +1 -0
  102. package/src/agent-tools/interface-catalog.js +10 -1
  103. package/src/agent-tools/project-agent-definitions.js +1 -1
  104. package/src/agents/identity-receipt.js +1 -1
  105. package/src/cli/command-registry.js +3 -0
  106. package/src/cli/connect-command.js +31 -0
  107. package/src/cli/mcp-command.js +101 -0
  108. package/src/cli/peer-command.js +100 -0
  109. package/src/cli/setup-agent-selection.js +2 -2
  110. package/src/cli/setup-command.js +12 -5
  111. package/src/cli.js +16 -0
  112. package/src/employees/catalog/bole.json +2 -2
  113. package/src/graphiti/federated-application.js +4 -1
  114. package/src/graphiti/project-agent-control-plane.js +7 -0
  115. package/src/graphiti/project-agent-task-entry.js +4 -4
  116. package/src/graphiti/project-agent-team-context.js +16 -18
  117. package/src/graphiti/project-team-view.js +82 -0
  118. package/src/graphiti/provider-client.js +9 -0
  119. package/src/graphiti/task-entry-preferences.js +1 -1
  120. package/src/http/api-router.js +10 -1
  121. package/src/http/graph-api-router.js +7 -0
  122. package/src/mcp/runtime.js +2 -1
  123. package/src/mcp/tool-annotations.js +4 -3
  124. package/src/mcp/tool-profiles.js +36 -0
  125. package/src/mcp-server.js +2 -100
  126. package/src/server.js +14 -2
  127. package/src/setup/agent-installation-status.js +5 -3
  128. package/src/setup/agents.js +23 -3
  129. package/src/setup/claude-desktop-config.js +60 -0
  130. package/src/setup/client-connection.js +25 -0
  131. package/src/setup/paths.js +3 -1
  132. package/src/setup/setup.js +1 -1
  133. package/src/setup/stable-package-root.js +29 -0
  134. package/src/system/system-service.js +5 -0
  135. package/dist/web/assets/AgentProfilePage-CioOdkBM.js +0 -1
  136. package/dist/web/assets/AgentsDirectoryPage--1A9nvNA.js +0 -1
  137. package/dist/web/assets/AgentsDirectoryPage-C7OUQH9v.css +0 -1
  138. package/dist/web/assets/EmployeeRecruitDialog-1QevD4Qv.js +0 -1
  139. package/dist/web/assets/EmployeeWorkbenchPage-B0vQIgwA.js +0 -3
  140. package/dist/web/assets/KnowledgeEditDialog-ClnrfPZi.js +0 -1
  141. package/dist/web/assets/KnowledgeInspector-Te1hJZbx.js +0 -1
  142. package/dist/web/assets/KnowledgeOrganizerPage-CCCrWl6Y.js +0 -1
  143. package/dist/web/assets/KnowledgePage-DGOcBwr8.js +0 -1
  144. package/dist/web/assets/KnowledgeWorkspace-cKn1NCRe.js +0 -4
  145. package/dist/web/assets/OverviewPage-By-fUzSG.css +0 -1
  146. package/dist/web/assets/PersonalProfilePage-DGJVP_VB.js +0 -1
  147. package/dist/web/assets/PersonalProjectsPage-D25HYMgD.js +0 -1
  148. package/dist/web/assets/PublicProjectsPage-CyNxVzW3.js +0 -1
  149. package/dist/web/assets/RoundtablesPage-BjVhvwMc.js +0 -1
  150. package/dist/web/assets/RoundtablesPage-Cb0R4GaM.css +0 -1
  151. package/dist/web/assets/SettingsPage-CCZqZtnH.js +0 -1
  152. package/dist/web/assets/SettingsPage-DYz5bTwa.css +0 -1
  153. package/dist/web/assets/UiDialog-CvDxg3B1.js +0 -1
  154. package/dist/web/assets/agent-pins-BcJ_qr8n.js +0 -1
  155. package/dist/web/assets/agent-profile-BZj3ezWe.css +0 -1
  156. package/dist/web/assets/catalog-DbXv0_bR.js +0 -1
  157. package/dist/web/assets/conversation-launcher-settings-Dj2GMzCI.js +0 -1
  158. package/dist/web/assets/index-CJ6Jmok9.css +0 -1
  159. package/dist/web/assets/index-CRa3GnYi.js +0 -5
  160. package/dist/web/assets/useAgentRoster-DCArHlxK.js +0 -1
@@ -0,0 +1,31 @@
1
+ import { resolveSetupPaths } from '../setup/paths.js';
2
+ import { resolveGraphRuntimeOptions } from '../graphiti/runtime-config.js';
3
+ import { clientConnectionConfig } from '../setup/client-connection.js';
4
+
5
+ export function runConnectCommand(args, { env = process.env, write = console.log } = {}) {
6
+ if (args.length === 1 && ['--help', '-h'].includes(args[0])) {
7
+ write('fuli connect [--format standard|vscode|server] [--profile full|chat] [--source-application codex|claude|claude_code|cursor|kiro|other] [--data-dir DIR | --runtime-config FILE]\nPrint local MCP configuration; merge the fuli entry into your client settings. No files are changed.\nUse --profile chat for chat apps that load every tool into each conversation. fuli setup configures Claude Desktop automatically.');
8
+ return;
9
+ }
10
+ const options = new Map();
11
+ for (let index = 0; index < args.length; index += 2) {
12
+ const flag = args[index];
13
+ if (!['--format', '--profile', '--source-application', '--data-dir', '--runtime-config'].includes(flag)) throw new TypeError(`Unknown connect option: ${flag}`);
14
+ if (options.has(flag)) throw new TypeError(`Duplicate ${flag}`);
15
+ const value = args[index + 1];
16
+ if (!value?.trim() || value.startsWith('--')) throw new TypeError(`Missing value for ${flag}`);
17
+ options.set(flag, value);
18
+ }
19
+ if (options.has('--data-dir') && options.has('--runtime-config')) {
20
+ throw new TypeError('Choose --data-dir or --runtime-config, not both');
21
+ }
22
+ const paths = resolveSetupPaths({ dataDir: options.get('--data-dir'), env });
23
+ const runtimeConfigPath = options.has('--data-dir') ? paths.graphRuntimeConfigPath
24
+ : resolveGraphRuntimeOptions(options.has('--runtime-config') ? ['--runtime-config', options.get('--runtime-config')] : [], env).runtimeConfigPath;
25
+ const config = clientConnectionConfig({ mcpServerPath: paths.mcpServerPath, runtimeConfigPath }, options.get('--format'), {
26
+ profile: options.get('--profile') ?? 'full',
27
+ sourceApplication: options.get('--source-application') ?? 'other'
28
+ });
29
+ write(JSON.stringify(config, null, 2));
30
+ return config;
31
+ }
@@ -0,0 +1,101 @@
1
+ import { callAgentTool, listAgentTools } from '../agent-tools.js';
2
+ import {
3
+ bindRuntimeLeaseAgentTools,
4
+ createRuntimeLeaseClient
5
+ } from '../adaptive-runtime/lease-client.js';
6
+ import { ApplicationError } from '../app/application-error.js';
7
+ import { openFederatedGraphApplication } from '../graphiti/federated-application.js';
8
+ import {
9
+ GraphRuntimeConfigurationError,
10
+ resolveGraphRuntimeOptions
11
+ } from '../graphiti/runtime-config.js';
12
+ import { runStdio } from '../mcp/runtime.js';
13
+ import { prepareDirectAgentToolCall } from '../mcp/direct-call.js';
14
+ import { normalizeMcpSourceApplication } from '../mcp/session-id.js';
15
+ import { toolNamesForProfile } from '../mcp/tool-profiles.js';
16
+ import { applicationErrorMessage } from '../mcp/tool-result.js';
17
+
18
+ export async function runMcpCommand(args, { env = process.env } = {}) {
19
+ if (args.includes('--tools')) {
20
+ process.stdout.write(`${JSON.stringify(listAgentTools(), null, 2)}\n`);
21
+ return;
22
+ }
23
+ if (args.includes('--help') || args.includes('-h')) {
24
+ process.stdout.write(helpText());
25
+ return;
26
+ }
27
+
28
+ const { runtimeConfigPath } = resolveGraphRuntimeOptions(args, env);
29
+ const configuredSourceApplication = args.includes('--source-application')
30
+ ? requiredOption(args, '--source-application')
31
+ : null;
32
+ if (args.includes('--call')) {
33
+ await runCall({
34
+ args,
35
+ runtimeConfigPath,
36
+ sourceApplication: configuredSourceApplication
37
+ ? normalizeMcpSourceApplication(configuredSourceApplication)
38
+ : 'other'
39
+ });
40
+ return;
41
+ }
42
+ await runStdio({
43
+ runtimeConfigPath,
44
+ sourceApplication: configuredSourceApplication
45
+ ? normalizeMcpSourceApplication(configuredSourceApplication)
46
+ : undefined,
47
+ toolNames: toolNamesForProfile(args.includes('--tool-profile') ? requiredOption(args, '--tool-profile') : 'full')
48
+ });
49
+ }
50
+
51
+ async function runCall({ args, runtimeConfigPath, sourceApplication }) {
52
+ const toolName = requiredOption(args, '--call');
53
+ const prepared = await prepareDirectAgentToolCall({
54
+ toolName,
55
+ input: parseInput(args),
56
+ sourceApplication
57
+ });
58
+ const app = openFederatedGraphApplication({ runtimeConfigPath });
59
+ const leaseClient = createRuntimeLeaseClient({ runtimeConfigPath });
60
+ bindRuntimeLeaseAgentTools(app, leaseClient);
61
+ try {
62
+ const result = await leaseClient.withGraphLease(
63
+ `cli-call:${toolName}`,
64
+ () => callAgentTool(app, toolName, prepared.input)
65
+ );
66
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
67
+ } finally {
68
+ await leaseClient.close();
69
+ await app.close();
70
+ }
71
+ }
72
+
73
+ function option(args, flag) {
74
+ const index = args.indexOf(flag);
75
+ return index === -1 ? null : args[index + 1];
76
+ }
77
+
78
+ function requiredOption(args, flag) {
79
+ const value = option(args, flag);
80
+ if (!value) throw new TypeError(`Missing value for ${flag}`);
81
+ return value;
82
+ }
83
+
84
+ function parseInput(args) {
85
+ const raw = option(args, '--input');
86
+ return raw ? JSON.parse(raw) : {};
87
+ }
88
+
89
+ export function startupMessage(error) {
90
+ if (error instanceof ApplicationError) return applicationErrorMessage(error);
91
+ if (error instanceof GraphRuntimeConfigurationError) return error.message;
92
+ return 'MCP server failed to start';
93
+ }
94
+
95
+ function helpText() {
96
+ return 'Usage: fuli mcp [--runtime-config <graph-runtime.json>] ' +
97
+ '[--source-application <codex|claude|claude_code|cursor|kiro|other>] [--tool-profile <full|chat>]\n' +
98
+ ' fuli mcp --tools\n' +
99
+ ' fuli mcp --runtime-config <graph-runtime.json> ' +
100
+ '--call <tool> [--input <json>]\n';
101
+ }
@@ -0,0 +1,100 @@
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ import { resolveSetupPaths } from '../setup/paths.js';
4
+ import { DEFAULT_RUNTIME_SETTINGS, readRuntimeSettings } from '../system/runtime-settings.js';
5
+ import { shortFingerprint } from '../agent-peer/protocol.js';
6
+
7
+ const USAGE = `fuli peer <action> (LAN roundtable Beta; the local console must be running)
8
+ status
9
+ host --address IP [--port PORT] [--name NAME] turn this computer into the coordinator
10
+ invite print a one-use pairing invitation (10 minutes)
11
+ join (--invitation TEXT | --invitation-file FILE) [--name NAME]
12
+ share --project ID --clients codex[,claude_code] [--folder DIR] (repeat --project for more; replaces the list)
13
+ revoke --device FINGERPRINT coordinator only
14
+ off leave: stop listening, sharing and trusting devices`;
15
+
16
+ // The owner's LAN roundtable controls through the local console, so the CLI
17
+ // and Settings share one runtime and one set of local-only checks.
18
+ export async function runPeerCommand(args, { env = process.env, fetchImpl = fetch, write = console.log } = {}) {
19
+ const [action, ...rest] = args;
20
+ if (!action || action === '--help' || action === '-h') { write(USAGE); return; }
21
+ const paths = resolveSetupPaths({ dataDir: option(rest, '--data-dir'), env });
22
+ const settings = paths.runtimeSettingsPath ? readRuntimeSettings(paths.runtimeSettingsPath) : DEFAULT_RUNTIME_SETTINGS;
23
+ const base = `http://127.0.0.1:${settings.ports.console}`;
24
+ const call = async (method, path, body) => {
25
+ let response;
26
+ try {
27
+ response = await fetchImpl(`${base}${path}`, { method, headers: { 'content-type': 'application/json' },
28
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
29
+ } catch {
30
+ throw new Error('The local FULI console is not running; start it with `fuli start`.');
31
+ }
32
+ const value = await response.json().catch(() => ({}));
33
+ if (!response.ok) throw new Error(value.message ?? `Request failed (${response.status})`);
34
+ return value;
35
+ };
36
+ switch (action) {
37
+ case 'status': return printStatus(await call('GET', '/api/peer'), write);
38
+ case 'host': {
39
+ const address = option(rest, '--address');
40
+ if (!address) throw new Error('Pass --address with one of this computer\'s LAN addresses (see `fuli peer status`).');
41
+ return printStatus(await call('POST', '/api/peer/coordinator', { host: address,
42
+ port: Number(option(rest, '--port') ?? 0), name: option(rest, '--name') }), write);
43
+ }
44
+ case 'invite': {
45
+ const result = await call('POST', '/api/peer/invitations', {});
46
+ write(`Coordinator fingerprint: ${shortFingerprint(result.fingerprint)}`);
47
+ write(`Invitation (one use, until ${result.expiresAt}):\n${result.invitation}`);
48
+ write('Compare this fingerprint with the one shown on the joining device before you share it.');
49
+ return;
50
+ }
51
+ case 'join': {
52
+ const file = option(rest, '--invitation-file');
53
+ const invitation = option(rest, '--invitation') ?? (file ? readFileSync(file, 'utf8').trim() : null);
54
+ if (!invitation) throw new Error('Pass --invitation or --invitation-file.');
55
+ return printStatus(await call('POST', '/api/peer/join', { invitation, name: option(rest, '--name') }), write);
56
+ }
57
+ case 'share': {
58
+ const projects = options(rest, '--project');
59
+ const clients = (option(rest, '--clients') ?? '').split(',').map((client) => client.trim()).filter(Boolean);
60
+ const folder = option(rest, '--folder');
61
+ if (projects.length > 1 && folder) throw new Error('--folder applies to a single --project.');
62
+ return printStatus(await call('PUT', '/api/peer/shares', { shares: projects.map((projectId) => ({ projectId, clients,
63
+ ...(folder ? { workingDirectory: folder } : {}) })) }), write);
64
+ }
65
+ case 'revoke': {
66
+ const device = option(rest, '--device');
67
+ if (!/^[0-9a-f]{64}$/.test(device ?? '')) throw new Error('Pass --device with the full fingerprint from `fuli peer status`.');
68
+ return printStatus(await call('POST', `/api/peer/devices/${device}/revoke`, {}), write);
69
+ }
70
+ case 'off': return printStatus(await call('POST', '/api/peer/disable', {}), write);
71
+ default: throw new Error(`Unknown peer action: ${action}\n${USAGE}`);
72
+ }
73
+ }
74
+
75
+ function printStatus(status, write) {
76
+ if (!status.role) {
77
+ write('LAN roundtable (Beta): off.');
78
+ if (status.remoteRemovalPending) write('This device has stopped sharing. Pairing removal will retry when the coordinator is available.');
79
+ if (status.addresses.length) write(`LAN addresses: ${status.addresses.join(', ')}`);
80
+ return;
81
+ }
82
+ write(`LAN roundtable (Beta): ${status.role === 'coordinator' ? 'coordinator' : 'paired device'}`);
83
+ write(`This device: ${status.device.shortFingerprint}`);
84
+ write(`Coordinator: ${status.coordinator.name} ${status.coordinator.url} (${status.coordinator.shortFingerprint})`);
85
+ for (const device of status.devices.filter((entry) => !entry.self)) {
86
+ write(` device ${device.name} ${device.shortFingerprint} ${device.status}${device.online ? ' online' : ''} ${device.nodeId}`);
87
+ }
88
+ for (const share of status.shares) {
89
+ write(` shared project lead: ${share.projectName} · ${share.clients.join(', ')} · ${share.state}${share.workingDirectory ? '' : ' · folder from recent sessions'}`);
90
+ }
91
+ }
92
+
93
+ function option(args, name) {
94
+ const index = args.indexOf(name);
95
+ return index >= 0 && index + 1 < args.length ? args[index + 1] : undefined;
96
+ }
97
+
98
+ function options(args, name) {
99
+ return args.flatMap((value, index) => (value === name && index + 1 < args.length ? [args[index + 1]] : []));
100
+ }
@@ -86,7 +86,7 @@ export function selectedAgentIds(agents, state) {
86
86
 
87
87
  export function formatAgentCheckboxSelection(agents, state) {
88
88
  return [
89
- 'Multiple supported Agents were detected:',
89
+ 'Clients available for automatic setup:',
90
90
  ...agents.map((agent, index) => {
91
91
  const pointer = index === state.activeIndex ? '❯' : ' ';
92
92
  const checkbox = state.selected[index] ? '[x]' : '[ ]';
@@ -161,7 +161,7 @@ async function promptCheckboxAgentSelection(agents, { input, output }) {
161
161
 
162
162
  async function promptTextAgentSelection(agents, { input, output }) {
163
163
  const terminal = createInterface({ input, output });
164
- output.write('Multiple supported Agents were detected:\n');
164
+ output.write('Clients available for automatic setup:\n');
165
165
  for (const [index, agent] of agents.entries()) {
166
166
  output.write(` ${index + 1}. ${agent.label}\n`);
167
167
  }
@@ -67,7 +67,8 @@ export function formatSetupPreview(plan, options) {
67
67
  `Management UI: ${options.noStart
68
68
  ? 'will not start'
69
69
  : `http://127.0.0.1:${runtimeSettings.ports.console}`}`,
70
- `Agents: ${agentSummary}`
70
+ `Automatic client setup: ${agentSummary}`,
71
+ 'Other MCP clients: connect after setup with fuli connect (no model required)'
71
72
  ];
72
73
  if (
73
74
  runtimeMode !== 'native' &&
@@ -80,7 +81,7 @@ export function formatSetupPreview(plan, options) {
80
81
  return lines.join('\n');
81
82
  }
82
83
 
83
- export function formatSetupResult(result) {
84
+ export function formatSetupResult(result, plan) {
84
85
  const lines = [result.status === 'ready'
85
86
  ? 'Fuli is ready.'
86
87
  : 'Fuli started, but one or more Agents are not connected.'];
@@ -101,11 +102,17 @@ export function formatSetupResult(result) {
101
102
  lines.push(`${agent.label}: ${state}${agent.message ? ` (${agent.message})` : ''}; retry fuli setup`);
102
103
  continue;
103
104
  }
104
- lines.push(agent.newTaskRequired
105
- ? `${agent.label}: connected; create or reopen a task to load the new configuration`
106
- : `${agent.label}: connected`);
105
+ lines.push(agent.restartRequired
106
+ ? `${agent.label}: connected; quit and reopen the app to load Fuli`
107
+ : agent.newTaskRequired
108
+ ? `${agent.label}: connected; create or reopen a task to load the new configuration`
109
+ : `${agent.label}: connected`);
107
110
  for (const step of agent.nextSteps ?? []) lines.push(`${agent.label}: ${step}`);
108
111
  }
112
+ const dataDir = plan?.paths?.dataDir;
113
+ lines.push('Other MCP clients: run fuli connect and merge the fuli entry into your MCP settings.');
114
+ if (dataDir) lines.push(`For this installation, use --data-dir ${JSON.stringify(dataDir)}.`);
115
+ lines.push('MCP provides tools and memory. Automatic conversation capture and wake/resume require a client adapter.');
109
116
  return lines.join('\n');
110
117
  }
111
118
 
package/src/cli.js CHANGED
@@ -11,6 +11,9 @@ import { runRemoteMcpCommand } from './cli/remote-mcp-command.js';
11
11
  import { runGraphDataCommand } from './cli/graph-data-command.js';
12
12
  import { runEmployeeCommand } from './cli/employee-command.js';
13
13
  import { runSetupCommand } from './cli/setup-command.js';
14
+ import { runConnectCommand } from './cli/connect-command.js';
15
+ import { runPeerCommand } from './cli/peer-command.js';
16
+ import { runMcpCommand, startupMessage } from './cli/mcp-command.js';
14
17
  import { runUninstallCommand } from './cli/uninstall-command.js';
15
18
  import { runUpdateCommand } from './cli/update-command.js';
16
19
  import { runWorkspaceConnectionCommand } from './cli/workspace-connection-command.js';
@@ -32,6 +35,19 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
32
35
  await runSetupCommand(commandArgs, { env });
33
36
  return;
34
37
  }
38
+ if (command === 'connect') {
39
+ assertSupportedNodeVersion();
40
+ return runConnectCommand(commandArgs, { env });
41
+ }
42
+ if (command === 'peer') {
43
+ assertSupportedNodeVersion();
44
+ return runPeerCommand(commandArgs, { env });
45
+ }
46
+ if (command === 'mcp') {
47
+ assertSupportedNodeVersion();
48
+ try { return await runMcpCommand(commandArgs, { env }); }
49
+ catch (error) { throw new Error(startupMessage(error)); }
50
+ }
35
51
  if (command === 'graph') {
36
52
  assertSupportedNodeVersion();
37
53
  const [action, ...graphArgs] = commandArgs;
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "bole",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "name": "Bole",
6
- "role": "HR",
6
+ "role": "AR",
7
7
  "description": "查看 Agent 人员分布、当前工作和带时间与原因的招募记录。",
8
8
  "occupationEmoji": "🔎",
9
9
  "defaultProjectScope": "all",
@@ -2,6 +2,7 @@ import { dirname, join } from 'node:path';
2
2
  import { createEmployeeService } from '../employees/service.js';
3
3
  import { createAgentRoundtable } from '../agent-roundtable/service.js';
4
4
  import { createRoundtableStore } from '../agent-roundtable/store.js';
5
+ import { createPeerPort } from '../agent-peer/peer-port.js';
5
6
  import { createJudgmentService } from '../judgment/service.js';
6
7
  import { JudgmentStore } from '../judgment/store.js';
7
8
  import { createJudgmentEngine } from '../judgment/engine.js';
@@ -125,6 +126,8 @@ export function openFederatedGraphApplication({
125
126
  app.employees = createEmployeeService({ app, runtimeConfigPath });
126
127
  app.judgment = createJudgmentService({ app, engine: createJudgmentEngine({ app }),
127
128
  openStore: () => new JudgmentStore(join(dirname(runtimeConfigPath), 'judgment.sqlite')) });
129
+ app.peer = createPeerPort({ dataDir: dirname(runtimeConfigPath), provider: app.personal,
130
+ spaceId: () => app.config.personal.spaceId });
128
131
  app.roundtable = createAgentRoundtable({ app, env,
129
132
  openStore: () => createRoundtableStore(join(dirname(runtimeConfigPath), 'agent-roundtable.sqlite')) });
130
133
  }
@@ -1169,7 +1172,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
1169
1172
  };
1170
1173
  }
1171
1174
 
1172
- close() { this.judgment?.close(); this.roundtable?.close(); return this.employees?.close(); }
1175
+ close() { this.judgment?.close(); this.roundtable?.close(); this.peer?.close(); return this.employees?.close(); }
1173
1176
 
1174
1177
  async #recordAgentViews(items, toolName) {
1175
1178
  const unique = new Map(items.map((item) => [
@@ -2,6 +2,7 @@ import { planAgentCollaboration, agentLoan, agentVerification } from './agent-co
2
2
  import { queryConversations, resumeConversation, updateConversationPolicy } from './agent-conversations.js';
3
3
  import { agentProjectResolution } from './agent-knowledge-workflows.js';
4
4
  import { listAgentAttention, changeAgentAttention } from './agent-attention.js';
5
+ import { loadProjectTeam } from './project-team-view.js';
5
6
  import {
6
7
  getProjectAgentMemory,
7
8
  agentMemoryView,
@@ -205,6 +206,12 @@ export class ProjectAgentControlPlaneApplication {
205
206
  return getProjectAgentCoordinationPolicyWorkflow(this, input);
206
207
  }
207
208
 
209
+ async getProjectTeam(input) {
210
+ this.#assertSpace(input.personalSpaceId);
211
+ if (!input.personalProjectId) throw new TypeError('personalProjectId is required');
212
+ return loadProjectTeam(this, input.personalSpaceId, input.personalProjectId);
213
+ }
214
+
208
215
  async updateProjectAgentCoordinationPolicy(input) {
209
216
  this.#assertSpace(input.personalSpaceId);
210
217
  return updateProjectAgentCoordinationPolicyWorkflow(this, input);
@@ -2,6 +2,7 @@ import { projectAgentRecord } from './project-agent-mapping.js';
2
2
  import { agentMemoryView } from './project-agent-memory.js';
3
3
  import { planTaskKnowledgeRecall } from './task-knowledge-recall.js';
4
4
  import { loadProjectTeamContext } from './project-agent-team-context.js';
5
+ import { isPeerRoleAgent } from './project-team-view.js';
5
6
  import { resolveTaskAgentMention } from './project-agent-mention.js';
6
7
 
7
8
  const ENTRY_TOOLS = new Set(['begin_task_context', 'get_collaboration_preferences']);
@@ -49,14 +50,14 @@ export async function resolveTaskEntryAgent(application, resolution, input) {
49
50
  const implicit = !mention?.agentId && !input.projectAgentId;
50
51
  const peerOnly = agent && implicit && unrelatedPeer(agent, workKind, input.requiredCapabilities);
51
52
  if (implicit && value.reason !== 'project_lead_required' && (peerOnly || value.status === 'unassigned')) {
52
- // Every task gets a FULI Agent: a project without a lead has HR hire one.
53
+ // Every task gets a FULI Agent: a project without a lead has AR hire one.
53
54
  const staffed = await application.personal.staffDefaultProjectLead(scope).catch(() => null);
54
55
  if (staffed?.agent) {
55
56
  value = staffed;
56
57
  agent = projectAgentRecord(staffed.agent);
57
58
  } else if (peerOnly) {
58
59
  return { status: 'unassigned', reason: 'specialist_required', worker_started: false,
59
- required_action: 'Use coordinate_project_agent_task to select or recruit a qualified durable owner before implementation. HR and project managers collaborate in their own roles; do not attribute development to them by fallback.' };
60
+ required_action: 'Use coordinate_project_agent_task to select or recruit a qualified durable owner before implementation. AR and project managers collaborate in their own roles; do not attribute development to them by fallback.' };
60
61
  }
61
62
  }
62
63
  const employeeId = /^employee\.([a-z][a-z0-9-]{0,63})$/.exec(agent?.agentId ?? '')?.[1];
@@ -159,8 +160,7 @@ function boundedRoleText(value, limit) {
159
160
  function unrelatedPeer(agent, workKind, required = []) {
160
161
  const profile = agent.profile;
161
162
  const capabilities = (profile.capabilities ?? []).map(value => value.toLowerCase());
162
- const peer = ['employee.jefa', 'employee.bole', 'employee.tonborg', 'fuli-project-hr'].includes(agent.agentId)
163
- || profile.agentType === 'hr' || capabilities.some(value => ['fuli.employee:jefa', 'fuli.employee:bole', 'fuli.employee:tonborg'].includes(value));
163
+ const peer = isPeerRoleAgent({ agentId: agent.agentId, agentType: profile.agentType, capabilities });
164
164
  return peer && !(profile.workKinds ?? []).some(kind => kind.toLowerCase() === workKind.toLowerCase())
165
165
  && !(required.length && required.every(value => capabilities.includes(value.toLowerCase())));
166
166
  }
@@ -1,24 +1,22 @@
1
+ import { loadProjectTeam } from './project-team-view.js';
2
+
1
3
  // Only public role/assignment data crosses the team boundary. Member memory
2
4
  // is restored separately inside each authorized worker's isolated context.
3
5
  export async function loadProjectTeamContext(application, personalSpaceId, projectId) {
4
6
  if (typeof application.personal.getProjectAgentCoordinationPolicy !== 'function') return null;
5
- const policy = await application.personal.getProjectAgentCoordinationPolicy(personalSpaceId, projectId);
6
- if (!policy?.team_lead_agent_id) return null;
7
- const agents = await application.personal.listProjectAgents(personalSpaceId, projectId, { status: 'active' });
8
- const ids = [policy.team_lead_agent_id, ...(policy.team_member_agent_ids ?? [])];
9
- const roster = (Array.isArray(agents) ? agents : []).filter(agent => ids.includes(agent.agent_id)
10
- && agent.assignments?.some(assignment => assignment.personal_project_id === projectId
11
- && assignment.status === 'active'))
12
- .map(agent => ({ agent_id: agent.agent_id,
13
- name: agent.profile?.display_name || agent.profile?.name,
14
- responsibility: agent.assignments?.find(assignment => assignment.personal_project_id === projectId
15
- && assignment.status === 'active')?.responsibility || agent.profile?.responsibility,
16
- role: agent.agent_id === policy.team_lead_agent_id ? 'lead' : 'member' }));
17
- return { lead_agent_id: policy.team_lead_agent_id, roster,
18
- user_facing_agent_id: policy.team_lead_agent_id,
19
- reporting_lines: roster.filter(agent => agent.role === 'member')
20
- .map(agent => ({ agent_id: agent.agent_id, reports_to_agent_id: policy.team_lead_agent_id })),
21
- unavailable_agent_ids: ids.filter(id => !roster.some(agent => agent.agent_id === id)),
7
+ const team = await loadProjectTeam(application, personalSpaceId, projectId);
8
+ if (team.status === 'no_lead') return null;
9
+ const leadId = team.lead?.agentId ?? team.unavailableLead.agentId;
10
+ const entry = (person, role) => ({ agent_id: person.agentId, name: person.name,
11
+ responsibility: person.responsibility, role });
12
+ const roster = [...(team.lead ? [entry(team.lead, 'lead')] : []),
13
+ ...team.members.map(member => entry(member, 'member'))];
14
+ return { lead_agent_id: leadId, roster,
15
+ user_facing_agent_id: leadId,
16
+ reporting_lines: team.members.map(member => ({ agent_id: member.agentId, reports_to_agent_id: leadId })),
17
+ unavailable_agent_ids: [...(team.lead ? [] : [leadId]), ...team.unavailableMemberIds],
18
+ assigned_collaborators: team.collaborators.map(person => entry(person, 'collaborator')),
19
+ assigned_peers: team.peers.map(person => entry(person, 'peer')),
22
20
  peer_roles: ['hr', 'project_manager'], worker_started: false,
23
- guidance: 'The project lead is the sole user-facing owner. Members report results and blockers to this lead; do not skip reporting levels or replace the lead when a member is named. The lead integrates and verifies member reports before reporting to the user. Split bounded work through an authorized coordination plan; report real worker evidence. HR and project managers remain peers. Team membership grants no new tool, data, executor, or publication permissions.' };
21
+ guidance: 'The project lead is the sole user-facing owner. Members report results and blockers to this lead; do not skip reporting levels or replace the lead when a member is named. Assigned collaborators outside the explicit team work through the lead and are not direct reports unless the policy names them. The lead integrates and verifies member reports before reporting to the user. Split bounded work through an authorized coordination plan; report real worker evidence. AR and project managers remain peers. Team membership grants no new tool, data, executor, or publication permissions.' };
24
22
  }
@@ -0,0 +1,82 @@
1
+ // One authoritative reading of a project's people. The coordination policy
2
+ // owns the explicit hierarchy; active assignments decide who currently works
3
+ // on the exact project. Assigned Agents outside the explicit team are shown as
4
+ // collaborators and peer roles stay peers, never converted into direct reports.
5
+ const PEER_AGENT_IDS = new Set(['employee.jefa', 'employee.bole', 'employee.tonborg', 'fuli-project-hr']);
6
+ const PEER_CAPABILITIES = new Set(['fuli.employee:jefa', 'fuli.employee:bole', 'fuli.employee:tonborg']);
7
+
8
+ export function isPeerRoleAgent({ agentId, agentType, capabilities = [] }) {
9
+ return PEER_AGENT_IDS.has(agentId) || agentType === 'hr'
10
+ || capabilities.some(value => PEER_CAPABILITIES.has(String(value).toLowerCase()));
11
+ }
12
+
13
+ export function buildProjectTeam({ policy, agents, projectId }) {
14
+ const leadId = policy?.team_lead_agent_id ?? null;
15
+ const memberIds = [...new Set(policy?.team_member_agent_ids ?? [])].filter(id => id !== leadId);
16
+ const all = Array.isArray(agents) ? agents : [];
17
+ const assigned = new Map();
18
+ for (const agent of all) {
19
+ const assignment = activeAssignment(agent, projectId);
20
+ if (assignment && isActive(agent)) assigned.set(agent.agent_id, person(agent, assignment));
21
+ }
22
+ const lead = leadId ? assigned.get(leadId) ?? null : null;
23
+ // Legacy or reclassified policies may still name a peer role as a member;
24
+ // it stays a peer and never gains a reporting line.
25
+ const members = memberIds.filter(id => assigned.has(id) && !assigned.get(id).peerRole)
26
+ .map(id => ({ ...assigned.get(id), reportsToAgentId: lead ? leadId : null }));
27
+ const reporting = new Set([leadId, ...members.map(member => member.agentId)]);
28
+ const collaborators = [];
29
+ const peers = [];
30
+ for (const [id, entry] of assigned) {
31
+ if (reporting.has(id)) continue;
32
+ (entry.peerRole ? peers : collaborators).push(entry);
33
+ }
34
+ const byName = (left, right) => left.name.localeCompare(right.name);
35
+ const former = leadId && !lead ? all.find(agent => agent.agent_id === leadId) : null;
36
+ return {
37
+ personalProjectId: projectId,
38
+ status: !leadId ? 'no_lead' : lead ? 'ready' : 'lead_unavailable',
39
+ lead,
40
+ unavailableLead: leadId && !lead ? { agentId: leadId, name: former ? displayName(former) : null } : null,
41
+ members,
42
+ collaborators: collaborators.sort(byName),
43
+ peers: peers.sort(byName),
44
+ unavailableMemberIds: memberIds.filter(id => !assigned.has(id))
45
+ };
46
+ }
47
+
48
+ function activeAssignment(agent, projectId) {
49
+ return (agent.assignments ?? []).find(assignment => assignment.personal_project_id === projectId
50
+ && assignment.status === 'active') ?? null;
51
+ }
52
+
53
+ function isActive(agent) {
54
+ const status = agent.profile?.status;
55
+ return status === undefined || status === null || status === 'active';
56
+ }
57
+
58
+ function displayName(agent) {
59
+ return agent.profile?.display_name || agent.profile?.name || agent.agent_id;
60
+ }
61
+
62
+ function person(agent, assignment) {
63
+ const profile = agent.profile ?? {};
64
+ return {
65
+ agentId: agent.agent_id,
66
+ name: displayName(agent),
67
+ employeeNumber: agent.employee_number ?? null,
68
+ occupationEmoji: profile.occupation_emoji ?? profile.occupationEmoji ?? null,
69
+ responsibility: assignment.responsibility || profile.responsibility || '',
70
+ peerRole: isPeerRoleAgent({ agentId: agent.agent_id, agentType: profile.agent_type,
71
+ capabilities: profile.capabilities ?? [] })
72
+ };
73
+ }
74
+
75
+ export async function loadProjectTeam(application, personalSpaceId, projectId) {
76
+ const [policy, agents] = await Promise.all([
77
+ application.personal.getProjectAgentCoordinationPolicy(personalSpaceId, projectId),
78
+ application.personal.listProjectAgents(personalSpaceId, projectId, { status: 'active' })
79
+ ]);
80
+ const list = Array.isArray(agents) ? agents : agents?.agents ?? agents?.items ?? [];
81
+ return buildProjectTeam({ policy, agents: list, projectId });
82
+ }
@@ -156,6 +156,15 @@ export class GraphitiProviderClient {
156
156
  issueAgentDelegation(input) {
157
157
  return this.#request('/v1/project-agent-context/delegations', { method: 'POST', body: input });
158
158
  }
159
+ issueRemoteAgentDelegation(input) {
160
+ return this.#request('/v1/project-agent-context/remote-delegations', { method: 'POST', body: input });
161
+ }
162
+ issueRemoteAgentOrigin(input) {
163
+ return this.#request('/v1/project-agent-context/remote-origins', { method: 'POST', body: input });
164
+ }
165
+ getRemoteOriginAuthority(personalSpaceId) {
166
+ return this.#request(`/v1/project-agent-context/remote-origin-authority?${new URLSearchParams({ personal_space_id: personalSpaceId })}`);
167
+ }
159
168
  verifyAgentDelegation(input) {
160
169
  return this.#request('/v1/project-agent-context/delegations/verify', { method: 'POST', body: input });
161
170
  }
@@ -42,7 +42,7 @@ export async function taskEntryPreferences(application, projectResolution, {
42
42
  }) : selection;
43
43
  if (context && selection?.requested_agent_id) {
44
44
  context.requested_agent_id = selection.requested_agent_id;
45
- context.requested_agent_guidance = 'The user requested this project specialist. Keep the project lead as accountable owner; coordinate a scoped workstream for the specialist and receive its report before replying. This request is not evidence that a worker ran. Do not load the specialist’s private memory into the lead context.';
45
+ context.requested_agent_guidance = 'The user requested this project specialist, but this session runs as the selected project lead. Keep the lead as accountable owner; coordinate a scoped workstream for the specialist and receive its report before replying. This request is not evidence that a worker ran. Do not load the specialist’s private memory, write memory or checkpoints as the specialist, or reuse another session’s task context. If this session was opened to act as the specialist, tell the user it is running as the lead.';
46
46
  }
47
47
  if (context && selection?.reporting_lead_agent_id) {
48
48
  context.reporting_lead_agent_id = selection.reporting_lead_agent_id;
@@ -3,6 +3,7 @@ import { handleExternalKnowledgeApiRequest } from './external-knowledge-api-rout
3
3
  import { handleGraphApiRequest } from './graph-api-router.js';
4
4
  import { handleEmployeeApiRequest } from './employee-api-router.js';
5
5
  import { handleRoundtableApiRequest } from '../agent-roundtable/http.js';
6
+ import { handlePeerApiRequest } from '../agent-peer/http.js';
6
7
  import { handleJudgmentApiRequest } from '../judgment/http.js';
7
8
  import { FULI_VERSION } from '../package-metadata.js';
8
9
 
@@ -12,9 +13,11 @@ export async function handleApiRequest({
12
13
  app,
13
14
  system = null,
14
15
  externalKnowledge = null,
15
- connectedKnowledge = null
16
+ connectedKnowledge = null,
17
+ peer = null
16
18
  }) {
17
19
  const url = new URL(request.url, 'http://127.0.0.1');
20
+ if (await handlePeerApiRequest({ request, response, url, peer })) return true;
18
21
  if (await handleRoundtableApiRequest({ request, response, url, roundtable: app.roundtable })) return true;
19
22
 
20
23
  if (url.pathname === '/api/health' && request.method === 'GET') {
@@ -32,6 +35,12 @@ export async function handleApiRequest({
32
35
  return true;
33
36
  }
34
37
 
38
+ if (system && url.pathname === '/api/system/client-connection' && request.method === 'GET') {
39
+ response.setHeader('Cache-Control', 'no-store');
40
+ sendJson(response, 200, system.clientConnection());
41
+ return true;
42
+ }
43
+
35
44
  if (system && url.pathname === '/api/system/settings' && request.method === 'PUT') {
36
45
  sendJson(response, 200, system.updateSettings(await readJson(request)));
37
46
  return true;
@@ -320,6 +320,13 @@ export async function handleGraphApiRequest({
320
320
  }));
321
321
  return true;
322
322
  }
323
+ if (url.pathname === '/api/project-team' && request.method === 'GET') {
324
+ sendJson(response, 200, await app.getProjectTeam({
325
+ personalSpaceId: url.searchParams.get('personalSpaceId'),
326
+ personalProjectId: url.searchParams.get('personalProjectId')
327
+ }));
328
+ return true;
329
+ }
323
330
  if (url.pathname === '/api/project-agent-coordination-policy' &&
324
331
  (request.method === 'PUT' || request.method === 'PATCH')) {
325
332
  sendJson(response, 200, await app.updateProjectAgentCoordinationPolicy(await readJson(request)));
@@ -7,12 +7,13 @@ import {
7
7
  import { openFederatedGraphApplication } from '../graphiti/federated-application.js';
8
8
  import { createMcpServer } from './create-mcp-server.js';
9
9
 
10
- export async function runStdio({ runtimeConfigPath, sourceApplication }) {
10
+ export async function runStdio({ runtimeConfigPath, sourceApplication, toolNames = null }) {
11
11
  const app = openFederatedGraphApplication({ runtimeConfigPath });
12
12
  const leaseClient = createRuntimeLeaseClient({ runtimeConfigPath });
13
13
  bindRuntimeLeaseAgentTools(app, leaseClient);
14
14
  const server = createServerOrClose(app, createMcpServer, {
15
15
  sourceApplication,
16
+ toolNames,
16
17
  withRuntimeLease: leaseClient.withGraphLease
17
18
  });
18
19
  const transport = new StdioServerTransport();