fuli-context 0.7.4-beta.2 → 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 (133) hide show
  1. package/README.md +93 -7
  2. package/README.zh-CN.md +84 -7
  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-DMvHxKf9.js → console-BlAhujsH.js} +1 -1
  23. package/dist/web/assets/{conversation-launcher-settings-Dnlcw7Rj.js → conversation-launcher-settings-DTYxiLJt.js} +1 -1
  24. package/dist/web/assets/i18n-Ci3heuiL.js +9 -0
  25. package/dist/web/assets/{identity-C9mLdR8L.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-CsYAR8O3.js → model-yG0YqUyZ.js} +1 -1
  29. package/dist/web/assets/{useApi-CROJJdhE-BLQiPVw0.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/docs/public-personal-architecture.md +73 -0
  33. package/graph-provider/fuli_graph/app.py +8 -0
  34. package/graph-provider/fuli_graph/collaboration_context.py +69 -6
  35. package/graph-provider/fuli_graph/knowledge_search.py +84 -5
  36. package/graph-provider/fuli_graph/model_validation.py +134 -0
  37. package/graph-provider/fuli_graph/models.py +24 -5
  38. package/graph-provider/fuli_graph/project_agent_access.py +54 -0
  39. package/graph-provider/fuli_graph/project_agent_executor_models.py +635 -0
  40. package/graph-provider/fuli_graph/project_agent_models.py +278 -0
  41. package/graph-provider/fuli_graph/project_agent_routes.py +597 -0
  42. package/graph-provider/fuli_graph/project_agent_task_models.py +542 -0
  43. package/graph-provider/fuli_graph/runtime.py +259 -0
  44. package/graph-provider/fuli_graph/search_projection.py +3 -0
  45. package/graph-provider/fuli_graph/store.py +6 -0
  46. package/graph-provider/fuli_graph/store_knowledge.py +62 -9
  47. package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +756 -0
  48. package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +601 -0
  49. package/graph-provider/fuli_graph/store_project_agent_executors.py +1087 -0
  50. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +458 -0
  51. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +699 -0
  52. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +409 -0
  53. package/graph-provider/fuli_graph/store_project_agent_tasks.py +1144 -0
  54. package/graph-provider/fuli_graph/store_project_agents.py +965 -0
  55. package/graph-provider/pyproject.toml +2 -1
  56. package/npm-shrinkwrap.json +2 -2
  57. package/package.json +2 -1
  58. package/skills/capturing-session-knowledge/SKILL.md +43 -2
  59. package/src/adaptive-runtime/executor-pool.js +127 -0
  60. package/src/adaptive-runtime/graph-services.js +166 -0
  61. package/src/adaptive-runtime/lease-client.js +335 -0
  62. package/src/adaptive-runtime/runtime-broker.js +318 -0
  63. package/src/adaptive-runtime/settings.js +111 -0
  64. package/src/adaptive-runtime/state.js +74 -0
  65. package/src/agent-tools/graph-definitions.js +396 -0
  66. package/src/agent-tools/graph-handlers.js +61 -0
  67. package/src/agent-tools/project-agent-definitions.js +307 -0
  68. package/src/backup/graph-backup.js +289 -0
  69. package/src/backup/identity-reconciliation.js +208 -0
  70. package/src/backup/runtime-adapters.js +269 -0
  71. package/src/cli/command-registry.js +7 -2
  72. package/src/cli/graph-data-command.js +112 -0
  73. package/src/cli/local-runtime-command.js +16 -2
  74. package/src/cli/setup-command.js +19 -3
  75. package/src/cli/update-command.js +4 -0
  76. package/src/cli/workspace-connection-command.js +333 -0
  77. package/src/cli.js +11 -0
  78. package/src/graphiti/collaboration-preference-projection.js +29 -1
  79. package/src/graphiti/collaboration-preference-workflow.js +121 -0
  80. package/src/graphiti/federated-application.js +74 -112
  81. package/src/graphiti/knowledge-provider-mapping.js +7 -1
  82. package/src/graphiti/project-agent-control-plane.js +263 -0
  83. package/src/graphiti/project-agent-mapping.js +1003 -0
  84. package/src/graphiti/project-agent-workflows.js +557 -0
  85. package/src/graphiti/provider-client.js +270 -1
  86. package/src/graphiti/runtime-config.js +6 -1
  87. package/src/graphiti/workspace-provider-client.js +403 -0
  88. package/src/http/api-router.js +63 -9
  89. package/src/http/graph-api-router.js +278 -0
  90. package/src/local-runtime/lifecycle.js +46 -10
  91. package/src/mcp/context-resources.js +15 -6
  92. package/src/mcp/create-mcp-server.js +51 -16
  93. package/src/mcp/instructions.js +7 -5
  94. package/src/mcp/runtime.js +17 -5
  95. package/src/mcp/session-id.js +38 -1
  96. package/src/mcp/tool-annotations.js +69 -0
  97. package/src/mcp-server.js +23 -6
  98. package/src/native-runtime/runtime.js +717 -0
  99. package/src/server.js +6 -2
  100. package/src/setup/agent-installation-status.js +7 -1
  101. package/src/setup/agents.js +3 -1
  102. package/src/setup/claude-code-config.js +3 -1
  103. package/src/setup/codex-bootstrap.js +11 -10
  104. package/src/setup/codex-config.js +7 -1
  105. package/src/setup/cursor-config.js +3 -1
  106. package/src/setup/graph-runtime.js +158 -13
  107. package/src/setup/neo4j-memory-profile.js +43 -0
  108. package/src/setup/options.js +38 -2
  109. package/src/setup/paths.js +14 -0
  110. package/src/setup/setup.js +37 -1
  111. package/src/system/resource-monitor.js +181 -16
  112. package/src/system/runtime-settings.js +10 -1
  113. package/src/system/system-service.js +27 -2
  114. package/dist/web/assets/AboutPage-CftRexSY.js +0 -1
  115. package/dist/web/assets/ConnectionsPage-D5J91x6d.js +0 -1
  116. package/dist/web/assets/KnowledgeEditDialog-wotVahvr.js +0 -1
  117. package/dist/web/assets/KnowledgeInspector-BzkW0mVs.js +0 -1
  118. package/dist/web/assets/KnowledgeOrganizerPage-BebUDiHt.js +0 -1
  119. package/dist/web/assets/KnowledgePage-OUT07aKH.js +0 -1
  120. package/dist/web/assets/KnowledgeWorkspace-1pHJ0MNZ.js +0 -4
  121. package/dist/web/assets/OverviewPage-CHE6XhdS.js +0 -1
  122. package/dist/web/assets/PersonalProfilePage-CXXiabUy.js +0 -1
  123. package/dist/web/assets/PersonalProjectsPage-d2wYGW8n.js +0 -1
  124. package/dist/web/assets/PublicProjectsPage-hxLShtnZ.js +0 -1
  125. package/dist/web/assets/ReviewPage-DUeyu7E8.js +0 -1
  126. package/dist/web/assets/SearchableSelect-C5RiB5F8.js +0 -1
  127. package/dist/web/assets/SettingsPage-BMuASSzQ.js +0 -1
  128. package/dist/web/assets/SettingsPage-CAfz4qiV.css +0 -1
  129. package/dist/web/assets/WritingTastePage-B9BCYIwQ.js +0 -1
  130. package/dist/web/assets/i18n-CWfd8Dtt.js +0 -5
  131. package/dist/web/assets/index-X2j29j8i.css +0 -1
  132. package/dist/web/assets/index-nP5p5OaY.js +0 -2
  133. package/dist/web/assets/vue-router-DN8x6iY1.js +0 -1
@@ -0,0 +1,333 @@
1
+ import {
2
+ chmodSync,
3
+ lstatSync,
4
+ readFileSync,
5
+ writeFileSync
6
+ } from 'node:fs';
7
+
8
+ import {
9
+ canonicalProviderUrl,
10
+ GraphRuntimeConfigurationError
11
+ } from '../graphiti/runtime-config.js';
12
+ import { resolveSetupPaths } from '../setup/paths.js';
13
+ import { readJsonFile, writeJsonFileAtomic } from '../storage/json-file.js';
14
+
15
+ const HELP_FLAGS = new Set(['--help', '-h']);
16
+ const VALUE_FLAGS = new Set(['--url', '--token-file', '--data-dir']);
17
+ const WORKSPACE_ROLES = new Set(['reader', 'contributor', 'maintainer', 'admin']);
18
+ const MAX_HANDSHAKE_BYTES = 64 * 1024;
19
+ const MAX_TOKEN_BYTES = 4096;
20
+
21
+ export class WorkspaceConnectionError extends Error {}
22
+
23
+ export async function runWorkspaceConnectionCommand(args, dependencies = {}) {
24
+ const options = parseWorkspaceConnectionOptions(args);
25
+ const write = dependencies.write ?? writeLine;
26
+ if (options.help) {
27
+ write(workspaceConnectionHelp());
28
+ return { status: 'help' };
29
+ }
30
+
31
+ const providerUrl = normalizeProviderUrl(options.url);
32
+ const token = readSecureToken(options.tokenFile, dependencies);
33
+ const fetchImpl = dependencies.fetch ?? globalThis.fetch;
34
+ if (typeof fetchImpl !== 'function') {
35
+ throw new WorkspaceConnectionError('This Node.js runtime does not provide fetch.');
36
+ }
37
+
38
+ const discovery = await requestJson({
39
+ fetchImpl,
40
+ url: new URL('/.well-known/fuli-workspace', `${providerUrl}/`),
41
+ expectedOrigin: new URL(providerUrl).origin,
42
+ timeoutMs: dependencies.timeoutMs
43
+ });
44
+ const provider = validateDiscovery(discovery, providerUrl);
45
+ const session = await requestJson({
46
+ fetchImpl,
47
+ url: provider.authSessionUrl,
48
+ expectedOrigin: new URL(providerUrl).origin,
49
+ token,
50
+ timeoutMs: dependencies.timeoutMs
51
+ });
52
+ const identity = validateSession(session);
53
+
54
+ const resolvePaths = dependencies.resolvePaths ?? resolveSetupPaths;
55
+ const paths = resolvePaths({ dataDir: options.dataDir, env: dependencies.env ?? process.env });
56
+ const runtimeConfigPath = paths.graphRuntimeConfigPath;
57
+ const readConfig = dependencies.readConfig ?? readJsonFile;
58
+ let runtimeConfig;
59
+ try {
60
+ runtimeConfig = readConfig(runtimeConfigPath, null);
61
+ } catch {
62
+ throw new WorkspaceConnectionError('Could not read the Fuli runtime configuration.');
63
+ }
64
+ if (runtimeConfig?.version !== 1 || !runtimeConfig.personal
65
+ || !Array.isArray(runtimeConfig.workspaces)) {
66
+ throw new WorkspaceConnectionError('Fuli is not initialized. Run `fuli setup` first.');
67
+ }
68
+
69
+ const workspace = {
70
+ protocol: 'fuli-workspace-v1',
71
+ providerUrl,
72
+ providerId: provider.id,
73
+ accessToken: token,
74
+ principalId: identity.principalId,
75
+ role: identity.role,
76
+ workspaceIds: identity.workspaceIds
77
+ };
78
+ const nextConfig = {
79
+ ...runtimeConfig,
80
+ workspaces: upsertWorkspace(runtimeConfig.workspaces, workspace)
81
+ };
82
+ const writeConfig = dependencies.writeConfig ?? writeSecureJson;
83
+ const secureFile = dependencies.secureFile ?? ((path) => chmodSync(path, 0o600));
84
+ try {
85
+ writeConfig(runtimeConfigPath, nextConfig);
86
+ secureFile(runtimeConfigPath);
87
+ } catch {
88
+ throw new WorkspaceConnectionError('Could not update the Fuli runtime configuration.');
89
+ }
90
+
91
+ write([
92
+ `Shared workspace service configured: ${providerUrl}`,
93
+ 'Restart required: run `fuli restart` to apply this connection.'
94
+ ].join('\n'));
95
+ return { status: 'configured', providerUrl, restartRequired: true };
96
+ }
97
+
98
+ export function parseWorkspaceConnectionOptions(args) {
99
+ if (!Array.isArray(args)) throw new WorkspaceConnectionError('Command arguments are invalid.');
100
+ if (args.some((argument) => HELP_FLAGS.has(argument))) {
101
+ if (args.length !== 1) {
102
+ throw new WorkspaceConnectionError('The help flag cannot be combined with other options.');
103
+ }
104
+ return { help: true, url: null, tokenFile: null, dataDir: null };
105
+ }
106
+
107
+ const values = new Map();
108
+ for (let index = 0; index < args.length; index += 2) {
109
+ const flag = args[index];
110
+ if (!VALUE_FLAGS.has(flag)) {
111
+ throw new WorkspaceConnectionError('Unknown option for connect-workspace.');
112
+ }
113
+ if (values.has(flag)) {
114
+ throw new WorkspaceConnectionError(`Duplicate connect-workspace option: ${flag}`);
115
+ }
116
+ const value = args[index + 1];
117
+ if (typeof value !== 'string' || !value.trim() || value.startsWith('--')) {
118
+ throw new WorkspaceConnectionError(`Missing value for ${flag}`);
119
+ }
120
+ values.set(flag, value.trim());
121
+ }
122
+
123
+ if (!values.has('--url')) {
124
+ throw new WorkspaceConnectionError('Missing required option: --url');
125
+ }
126
+ if (!values.has('--token-file')) {
127
+ throw new WorkspaceConnectionError('Missing required option: --token-file');
128
+ }
129
+ return {
130
+ help: false,
131
+ url: values.get('--url'),
132
+ tokenFile: values.get('--token-file'),
133
+ dataDir: values.get('--data-dir') ?? null
134
+ };
135
+ }
136
+
137
+ export function workspaceConnectionHelp() {
138
+ return [
139
+ 'Usage: fuli connect-workspace --url URL --token-file FILE [--data-dir DIR]',
140
+ '',
141
+ 'Validates a Fuli Workspace service and stores its bearer token in the local',
142
+ 'Fuli runtime configuration. Restart Fuli after the command succeeds.'
143
+ ].join('\n');
144
+ }
145
+
146
+ function normalizeProviderUrl(value) {
147
+ try {
148
+ return new URL(canonicalProviderUrl(value)).origin;
149
+ } catch (error) {
150
+ if (error instanceof GraphRuntimeConfigurationError) {
151
+ throw new WorkspaceConnectionError(error.message);
152
+ }
153
+ throw new WorkspaceConnectionError('Workspace service URL is invalid.');
154
+ }
155
+ }
156
+
157
+ function readSecureToken(path, dependencies) {
158
+ const inspect = dependencies.lstat ?? lstatSync;
159
+ const readText = dependencies.readText ?? ((filePath) => readFileSync(filePath, 'utf8'));
160
+ let metadata;
161
+ try {
162
+ metadata = inspect(path);
163
+ } catch {
164
+ throw new WorkspaceConnectionError('The token file could not be inspected.');
165
+ }
166
+ if (metadata.isSymbolicLink() || !metadata.isFile()) {
167
+ throw new WorkspaceConnectionError('The token file must be a regular file, not a symlink.');
168
+ }
169
+ const platform = dependencies.platform ?? process.platform;
170
+ if (platform !== 'win32' && (metadata.mode & 0o077) !== 0) {
171
+ throw new WorkspaceConnectionError('The token file must not be readable by group or other users.');
172
+ }
173
+ if (!Number.isFinite(metadata.size) || metadata.size < 1 || metadata.size > MAX_TOKEN_BYTES) {
174
+ throw new WorkspaceConnectionError('The token file has an invalid size.');
175
+ }
176
+
177
+ let token;
178
+ try {
179
+ token = readText(path).trim();
180
+ } catch {
181
+ throw new WorkspaceConnectionError('The token file could not be read.');
182
+ }
183
+ if (token.length < 20 || token.length > MAX_TOKEN_BYTES || !/^[\x21-\x7e]+$/u.test(token)) {
184
+ throw new WorkspaceConnectionError('The token file does not contain a valid bearer token.');
185
+ }
186
+ return token;
187
+ }
188
+
189
+ async function requestJson({ fetchImpl, url, expectedOrigin, token = null, timeoutMs = 5000 }) {
190
+ let response;
191
+ try {
192
+ response = await fetchImpl(url, {
193
+ method: 'GET',
194
+ headers: {
195
+ accept: 'application/json',
196
+ ...(token ? { authorization: `Bearer ${token}` } : {})
197
+ },
198
+ redirect: 'manual',
199
+ cache: 'no-store',
200
+ signal: AbortSignal.timeout(timeoutMs)
201
+ });
202
+ } catch {
203
+ throw new WorkspaceConnectionError('Could not reach the workspace service.');
204
+ }
205
+ if (response.status >= 300 && response.status < 400) {
206
+ throw new WorkspaceConnectionError('Workspace service redirects are not allowed during connection.');
207
+ }
208
+ if (response.url) {
209
+ let responseOrigin;
210
+ try {
211
+ responseOrigin = new URL(response.url).origin;
212
+ } catch {
213
+ throw new WorkspaceConnectionError('Workspace service returned an invalid response URL.');
214
+ }
215
+ if (responseOrigin !== expectedOrigin) {
216
+ throw new WorkspaceConnectionError('Workspace service returned a cross-origin response.');
217
+ }
218
+ }
219
+ if (!response.ok) {
220
+ throw new WorkspaceConnectionError(`Workspace service rejected the connection (HTTP ${response.status}).`);
221
+ }
222
+
223
+ let text;
224
+ try {
225
+ text = await response.text();
226
+ } catch {
227
+ throw new WorkspaceConnectionError('Workspace service returned an unreadable response.');
228
+ }
229
+ if (Buffer.byteLength(text, 'utf8') > MAX_HANDSHAKE_BYTES) {
230
+ throw new WorkspaceConnectionError('Workspace service response is too large.');
231
+ }
232
+ try {
233
+ return JSON.parse(text);
234
+ } catch {
235
+ throw new WorkspaceConnectionError('Workspace service did not return valid JSON.');
236
+ }
237
+ }
238
+
239
+ function validateDiscovery(discovery, providerUrl) {
240
+ if (String(discovery?.protocolVersion ?? '') !== '1') {
241
+ throw new WorkspaceConnectionError('Workspace service protocol version is not supported.');
242
+ }
243
+ const id = safeIdentifier(discovery?.provider?.id, 'Workspace service provider ID is invalid.');
244
+ const advertised = discovery?.endpoints?.authSession ?? '/v1/auth/session';
245
+ const authSessionUrl = sameOriginEndpoint(advertised, providerUrl);
246
+ return { id, authSessionUrl };
247
+ }
248
+
249
+ function validateSession(session) {
250
+ if (session?.authenticated !== true) {
251
+ throw new WorkspaceConnectionError('The workspace service did not authenticate this token.');
252
+ }
253
+ const principalId = safeIdentifier(
254
+ session?.principal?.id,
255
+ 'Workspace service returned an invalid principal.'
256
+ );
257
+ const grant = session?.grant && typeof session.grant === 'object' ? session.grant : session;
258
+ const role = String(grant?.role ?? '');
259
+ if (!WORKSPACE_ROLES.has(role)) {
260
+ throw new WorkspaceConnectionError('Workspace service returned an unsupported role.');
261
+ }
262
+ const workspaceIds = normalizeWorkspaceIds(grant?.workspaceIds);
263
+ return { principalId, role, workspaceIds };
264
+ }
265
+
266
+ function normalizeWorkspaceIds(value) {
267
+ if (value === '*') return '*';
268
+ if (!Array.isArray(value)) {
269
+ throw new WorkspaceConnectionError('Workspace service returned an invalid workspace grant.');
270
+ }
271
+ return value.map((id) => safeIdentifier(id, 'Workspace service returned an invalid workspace grant.'));
272
+ }
273
+
274
+ function safeIdentifier(value, message) {
275
+ if (typeof value !== 'string') throw new WorkspaceConnectionError(message);
276
+ const normalized = value.trim();
277
+ if (!normalized || normalized.length > 256 || /[\u0000-\u001f\u007f]/u.test(normalized)) {
278
+ throw new WorkspaceConnectionError(message);
279
+ }
280
+ return normalized;
281
+ }
282
+
283
+ function sameOriginEndpoint(value, providerUrl) {
284
+ let endpoint;
285
+ try {
286
+ endpoint = new URL(String(value), `${providerUrl}/`);
287
+ } catch {
288
+ throw new WorkspaceConnectionError('Workspace service advertised an invalid authentication endpoint.');
289
+ }
290
+ const providerOrigin = new URL(providerUrl).origin;
291
+ if (endpoint.origin !== providerOrigin || endpoint.username || endpoint.password
292
+ || endpoint.search || endpoint.hash) {
293
+ throw new WorkspaceConnectionError('Workspace service advertised a cross-origin authentication endpoint.');
294
+ }
295
+ return endpoint;
296
+ }
297
+
298
+ function upsertWorkspace(workspaces, workspace) {
299
+ const next = [];
300
+ let replaced = false;
301
+ for (const existing of workspaces) {
302
+ if (!sameProvider(existing?.providerUrl, workspace.providerUrl)) {
303
+ next.push(existing);
304
+ continue;
305
+ }
306
+ if (!replaced) next.push({ ...existing, ...workspace });
307
+ replaced = true;
308
+ }
309
+ if (!replaced) next.push(workspace);
310
+ return next;
311
+ }
312
+
313
+ function sameProvider(left, right) {
314
+ try {
315
+ return new URL(canonicalProviderUrl(left)).origin === right;
316
+ } catch {
317
+ return false;
318
+ }
319
+ }
320
+
321
+ function writeSecureJson(path, value) {
322
+ writeJsonFileAtomic(path, value, {
323
+ writeFileSync: (temporaryPath, payload, options) => writeFileSync(
324
+ temporaryPath,
325
+ payload,
326
+ { ...options, mode: 0o600 }
327
+ )
328
+ });
329
+ }
330
+
331
+ function writeLine(value) {
332
+ process.stdout.write(`${value}\n`);
333
+ }
package/src/cli.js CHANGED
@@ -7,9 +7,11 @@ import {
7
7
  printHelp
8
8
  } from './cli/command-registry.js';
9
9
  import { runLocalRuntimeCommand } from './cli/local-runtime-command.js';
10
+ import { runGraphDataCommand } from './cli/graph-data-command.js';
10
11
  import { runSetupCommand } from './cli/setup-command.js';
11
12
  import { runUninstallCommand } from './cli/uninstall-command.js';
12
13
  import { runUpdateCommand } from './cli/update-command.js';
14
+ import { runWorkspaceConnectionCommand } from './cli/workspace-connection-command.js';
13
15
  import { FULI_VERSION } from './package-metadata.js';
14
16
  import { assertSupportedNodeVersion } from './setup/node-runtime.js';
15
17
 
@@ -28,6 +30,15 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
28
30
  await runSetupCommand(commandArgs, { env });
29
31
  return;
30
32
  }
33
+ if (command === 'graph') {
34
+ assertSupportedNodeVersion();
35
+ const [action, ...graphArgs] = commandArgs;
36
+ return runGraphDataCommand(action, graphArgs, { env });
37
+ }
38
+ if (command === 'connect-workspace') {
39
+ assertSupportedNodeVersion();
40
+ return runWorkspaceConnectionCommand(commandArgs, { env });
41
+ }
31
42
  if (command === 'uninstall') {
32
43
  await runUninstallCommand(commandArgs, { env });
33
44
  return;
@@ -10,6 +10,9 @@ export function agentCollaborationPreference(item) {
10
10
  if (item.preference_project_id) {
11
11
  preference.preference_project_id = item.preference_project_id;
12
12
  }
13
+ if (item.preference_agent_id) {
14
+ preference.preference_agent_id = item.preference_agent_id;
15
+ }
13
16
  for (const field of [
14
17
  'attributes',
15
18
  'weight',
@@ -27,10 +30,31 @@ export function agentCollaborationPreference(item) {
27
30
  return preference;
28
31
  }
29
32
 
33
+ export function activePreferenceConflicts(result) {
34
+ const items = [
35
+ ...(result.global_preferences ?? []),
36
+ ...(result.project_preferences ?? []),
37
+ ...(result.agent_preferences ?? [])
38
+ ];
39
+ const itemById = new Map(items.map((item) => [item.id, item]));
40
+ return (result.conflicts ?? []).map((conflict) => ({
41
+ preference_key: conflict.preference_key,
42
+ preference_scope: conflict.preference_scope,
43
+ preference_project_id: conflict.preference_project_id ?? null,
44
+ preference_agent_id: conflict.preference_agent_id ?? null,
45
+ alternatives: (conflict.item_ids ?? [])
46
+ .map((id) => itemById.get(id))
47
+ .filter(Boolean)
48
+ .map(agentCollaborationPreference),
49
+ required_action: 'Do not apply any alternative until the conflict is resolved.'
50
+ }));
51
+ }
52
+
30
53
  export function deferredPreferenceConflicts(result, queuedConflicts) {
31
54
  const items = [
32
55
  ...(result.global_preferences ?? []),
33
- ...(result.project_preferences ?? [])
56
+ ...(result.project_preferences ?? []),
57
+ ...(result.agent_preferences ?? [])
34
58
  ];
35
59
  const itemById = new Map(items.map((item) => [item.id, item]));
36
60
  const activeConflictPairs = new Set(
@@ -121,6 +145,9 @@ export function agentDeferredPreferenceConflict(conflict) {
121
145
  ...(conflict.preference_project_id
122
146
  ? { preference_project_id: conflict.preference_project_id }
123
147
  : {}),
148
+ ...(conflict.preference_agent_id
149
+ ? { preference_agent_id: conflict.preference_agent_id }
150
+ : {}),
124
151
  status: conflict.status,
125
152
  deferred_at: conflict.deferred_at,
126
153
  reason: conflict.reason,
@@ -142,6 +169,7 @@ function agentConflictPreference(item) {
142
169
  };
143
170
  for (const field of [
144
171
  'preference_project_id',
172
+ 'preference_agent_id',
145
173
  'inherited_from_project_id',
146
174
  'scope_distance',
147
175
  'scope_path',
@@ -0,0 +1,121 @@
1
+ import {
2
+ agentProjectResolution
3
+ } from './agent-knowledge-workflows.js';
4
+ import {
5
+ activePreferenceConflicts,
6
+ agentCollaborationPreference,
7
+ agentDeferredPreferenceConflict,
8
+ deferredPreferenceConflicts
9
+ } from './collaboration-preference-projection.js';
10
+ import {
11
+ recallTaskKnowledge,
12
+ TASK_KNOWLEDGE_RETRIEVAL_GUIDANCE
13
+ } from './task-knowledge-recall.js';
14
+
15
+ export async function getCollaborationPreferences(
16
+ application,
17
+ projectResolution,
18
+ {
19
+ projectAgentId = null,
20
+ taskPrompt = null,
21
+ limit = 100,
22
+ agentInvocation = false,
23
+ agentToolName = 'get_collaboration_preferences'
24
+ },
25
+ recordAgentViews
26
+ ) {
27
+ const resolvedProjectId = projectResolution.personalProjectId;
28
+ const [result, queuedConflicts] = await Promise.all([
29
+ application.personal.collaborationPreferences(
30
+ application.config.personal.spaceId,
31
+ resolvedProjectId,
32
+ limit,
33
+ projectAgentId
34
+ ),
35
+ application.personal.listPreferenceConflicts(
36
+ application.config.personal.spaceId,
37
+ 'ai_pending',
38
+ limit
39
+ )
40
+ ]);
41
+ const deferredConflicts = deferredPreferenceConflicts(
42
+ result,
43
+ queuedConflicts
44
+ );
45
+ const taskKnowledgeRecall = taskPrompt === null
46
+ ? null
47
+ : await recallTaskKnowledge(application, projectResolution, taskPrompt);
48
+ if (agentInvocation) {
49
+ const items = [
50
+ ...(result.global_preferences ?? []),
51
+ ...(result.project_preferences ?? []),
52
+ ...(result.agent_preferences ?? [])
53
+ ];
54
+ await recordAgentViews(items.map(({ id, item_kind: itemKind }) => ({
55
+ item_id: id,
56
+ item_kind: itemKind
57
+ })), agentToolName);
58
+ }
59
+ const applicationGuidance = guidanceFor(
60
+ resolvedProjectId,
61
+ projectAgentId
62
+ );
63
+ if (!agentInvocation) {
64
+ return {
65
+ ...result,
66
+ deferred_conflicts: deferredConflicts,
67
+ application_guidance: applicationGuidance,
68
+ project_resolution: agentProjectResolution(projectResolution),
69
+ ...(taskKnowledgeRecall ? { task_knowledge_recall: taskKnowledgeRecall } : {})
70
+ };
71
+ }
72
+ return {
73
+ effective_preferences: (result.effective_preferences ?? [])
74
+ .map(agentCollaborationPreference),
75
+ deferred_conflicts: deferredConflicts.map(agentDeferredPreferenceConflict),
76
+ active_conflicts: activePreferenceConflicts(result),
77
+ application_guidance: applicationGuidance,
78
+ ...(taskKnowledgeRecall ? { task_knowledge_recall: taskKnowledgeRecall } : {}),
79
+ context: preferenceContext(result, deferredConflicts, projectResolution)
80
+ };
81
+ }
82
+
83
+ function guidanceFor(projectId, projectAgentId) {
84
+ return {
85
+ apply: 'effective_preferences',
86
+ global_scope: 'Apply personal-global preferences in every user task.',
87
+ project_scope: projectId
88
+ ? `Also apply preferences scoped to ${projectId} and confirmed ancestor preferences whose descendants or selected-projects mode explicitly reaches it.`
89
+ : 'No exact personal project matched; do not apply project-scoped preferences.',
90
+ agent_scope: projectAgentId
91
+ ? `Also apply preferences scoped to project Agent ${projectAgentId}; exclude every other project Agent.`
92
+ : 'No project Agent was selected; do not apply Agent-scoped preferences.',
93
+ inheritance: 'Project preference inheritance follows only PART_OF or USES_KNOWLEDGE_FROM for at most two hops. Preserve inherited_from_project_id, scope_path, and scope_distance. An exact item wins only after confirmation authority is compared; weight never expands scope or resolves a conflict.',
94
+ related_projects: 'RELATED_TO preferences never auto-apply. Use structured related-project suggestions to ask before a one-time read-only expansion.',
95
+ conflicts: 'Do not apply entries listed in conflicts until the user resolves them.',
96
+ deferred_conflicts: 'If the current task would use a deferred_conflict, call resolve_deferred_preference_conflict before applying either side. Ignore unrelated deferred conflicts. The resolution must preserve the AI audit marker.',
97
+ authority: 'Human or authoritative-source confirmed preferences outrank agent-confirmed preferences. Agent-confirmed preferences are usable but lower priority and remain explicitly marked.',
98
+ pending: 'Pending preferences are available only through on-demand knowledge search; invalid and unrelated-project preferences are excluded. Automatic preference injection does not count as usage evidence.',
99
+ knowledge_retrieval: TASK_KNOWLEDGE_RETRIEVAL_GUIDANCE
100
+ };
101
+ }
102
+
103
+ function preferenceContext(result, deferredConflicts, projectResolution) {
104
+ return {
105
+ personal_space_id: result.personal_space_id,
106
+ personal_project_id: result.personal_project_id,
107
+ project_agent_id: result.project_agent_id ?? null,
108
+ global_preference_count: (result.global_preferences ?? []).length,
109
+ project_preference_count: (result.project_preferences ?? []).length,
110
+ inherited_project_preference_count: (result.project_preferences ?? [])
111
+ .filter(({ inherited_from_project_id: projectId }) => Boolean(projectId))
112
+ .length,
113
+ agent_preference_count: (result.agent_preferences ?? []).length,
114
+ conflict_count: (result.conflicts ?? []).length,
115
+ ai_deferred_conflict_count: deferredConflicts.length,
116
+ overridden_global_count: (result.overridden_global_ids ?? []).length,
117
+ overridden_project_count: (result.overridden_project_ids ?? []).length,
118
+ source_truncated: result.truncated === true,
119
+ project_resolution: agentProjectResolution(projectResolution)
120
+ };
121
+ }