fuli-context 0.7.6 → 0.7.8

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 (150) hide show
  1. package/README.md +69 -13
  2. package/README.zh-CN.md +60 -14
  3. package/dist/web/assets/{AboutPage-sLV9WChi.js → AboutPage-9CVX91tr.js} +1 -1
  4. package/dist/web/assets/{ConnectionsPage-BWn4ywGJ.js → ConnectionsPage-CWbCVFdv.js} +1 -1
  5. package/dist/web/assets/EmployeeRecruitDialog-CQgNpGJ_.js +1 -0
  6. package/dist/web/assets/EmployeeRecruitDialog-CfFlyKSh.css +1 -0
  7. package/dist/web/assets/EmployeeWorkbenchPage-CWg4WJhr.css +1 -0
  8. package/dist/web/assets/EmployeeWorkbenchPage-CwU4qBgD.js +1 -0
  9. package/dist/web/assets/KnowledgeEditDialog-B6TGAcFH.js +1 -0
  10. package/dist/web/assets/{KnowledgeEditDialog-DWUV_NPp.css → KnowledgeEditDialog-iv-dgUeD.css} +1 -1
  11. package/dist/web/assets/{KnowledgeInspector-CRQWOaos.js → KnowledgeInspector-M2pLY0bR.js} +1 -1
  12. package/dist/web/assets/{KnowledgeOrganizerPage-Dfj3vIpU.js → KnowledgeOrganizerPage-DzeAlk9b.js} +1 -1
  13. package/dist/web/assets/KnowledgePage-BJu9w0fF.js +1 -0
  14. package/dist/web/assets/{KnowledgeWorkspace-ChEB5Bwk.js → KnowledgeWorkspace-CCt8rnBb.js} +1 -1
  15. package/dist/web/assets/{OverviewPage-Cd66M0RT.js → OverviewPage-CZswVqui.js} +1 -1
  16. package/dist/web/assets/{PersonalProfilePage-RCFHL0Wz.js → PersonalProfilePage-f1nSF6kx.js} +1 -1
  17. package/dist/web/assets/PersonalProjectsPage-D__eJf0t.js +1 -0
  18. package/dist/web/assets/ProjectAgentsPage-C3vW6wmk.css +1 -0
  19. package/dist/web/assets/ProjectAgentsPage-LrwagMGj.js +11 -0
  20. package/dist/web/assets/{PublicProjectsPage-mstPEjHe.js → PublicProjectsPage-BdC2Qk4n.js} +1 -1
  21. package/dist/web/assets/{ReviewPage-6Y1SmfZy.js → ReviewPage-Cbgl8_Si.js} +1 -1
  22. package/dist/web/assets/SearchableSelect-ERm-k4KI.js +1 -0
  23. package/dist/web/assets/{SettingsPage--LdTMUm7.js → SettingsPage-DWso4x29.js} +1 -1
  24. package/dist/web/assets/{WritingTastePage-CIpKswvs.js → WritingTastePage-CFpCjmIY.js} +1 -1
  25. package/dist/web/assets/catalog-WGLn7ERJ.js +1 -0
  26. package/dist/web/assets/{console-BT1P2Db4.js → console-DQgu0JIA.js} +1 -1
  27. package/dist/web/assets/conversation-launcher-settings-0KtlG_LV.js +1 -0
  28. package/dist/web/assets/{i18n-js6gfc7j.js → i18n-ByRiA3pJ.js} +3 -3
  29. package/dist/web/assets/{identity-CfDfanpa.js → identity-CxdPRMwH.js} +1 -1
  30. package/dist/web/assets/index-JAZe6lfX.js +2 -0
  31. package/dist/web/assets/index-wcXzzggp.css +1 -0
  32. package/dist/web/assets/{model-DJkLAJsg.js → model-Db4-KefQ.js} +1 -1
  33. package/dist/web/assets/{useApi-CROJJdhE-BdSti7Y2.js → useApi-CROJJdhE-CePMjc08.js} +1 -1
  34. package/dist/web/assets/{vue-router-Kk67EaCP.js → vue-router-CzX-hn3P.js} +1 -1
  35. package/dist/web/index.html +7 -6
  36. package/docs/employee-agents.md +99 -0
  37. package/docs/project-agent-memory.md +479 -0
  38. package/graph-provider/fuli_graph/knowledge_audit_models.py +171 -0
  39. package/graph-provider/fuli_graph/model_base.py +5 -0
  40. package/graph-provider/fuli_graph/model_validation.py +32 -0
  41. package/graph-provider/fuli_graph/models.py +26 -170
  42. package/graph-provider/fuli_graph/project_agent_context_models.py +29 -0
  43. package/graph-provider/fuli_graph/project_agent_executor_models.py +35 -7
  44. package/graph-provider/fuli_graph/project_agent_memory_models.py +79 -0
  45. package/graph-provider/fuli_graph/project_agent_memory_routes.py +46 -0
  46. package/graph-provider/fuli_graph/project_agent_models.py +9 -0
  47. package/graph-provider/fuli_graph/project_agent_recruitment_values.py +48 -0
  48. package/graph-provider/fuli_graph/project_agent_routes.py +21 -1
  49. package/graph-provider/fuli_graph/project_agent_task_models.py +217 -29
  50. package/graph-provider/fuli_graph/runtime.py +37 -1
  51. package/graph-provider/fuli_graph/store.py +6 -0
  52. package/graph-provider/fuli_graph/store_project_agent_context.py +65 -0
  53. package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +175 -11
  54. package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +33 -10
  55. package/graph-provider/fuli_graph/store_project_agent_executors.py +181 -14
  56. package/graph-provider/fuli_graph/store_project_agent_memory.py +174 -0
  57. package/graph-provider/fuli_graph/store_project_agent_task_activity.py +423 -0
  58. package/graph-provider/fuli_graph/store_project_agent_task_persistence.py +152 -0
  59. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +187 -21
  60. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +748 -162
  61. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +230 -18
  62. package/graph-provider/fuli_graph/store_project_agent_tasks.py +261 -468
  63. package/graph-provider/fuli_graph/store_project_agents.py +214 -46
  64. package/graph-provider/fuli_graph/store_task_contexts.py +217 -0
  65. package/graph-provider/fuli_graph/store_transactions.py +40 -0
  66. package/graph-provider/fuli_graph/task_context_models.py +50 -0
  67. package/graph-provider/fuli_graph/task_context_routes.py +31 -0
  68. package/graph-provider/fuli_graph/workflow_observation_models.py +1 -1
  69. package/graph-provider/pyproject.toml +1 -1
  70. package/npm-shrinkwrap.json +8 -8
  71. package/package.json +5 -2
  72. package/skills/capturing-session-knowledge/SKILL.md +18 -6
  73. package/src/adaptive-runtime/executor-pool.js +46 -5
  74. package/src/adaptive-runtime/graph-services.js +10 -3
  75. package/src/adaptive-runtime/lease-client.js +66 -14
  76. package/src/adaptive-runtime/runtime-broker.js +24 -9
  77. package/src/agent-tools/employee-definitions.js +43 -0
  78. package/src/agent-tools/graph-definitions.js +25 -6
  79. package/src/agent-tools/graph-handlers.js +11 -0
  80. package/src/agent-tools/project-agent-definitions.js +35 -4
  81. package/src/agent-tools/project-agent-memory-definitions.js +36 -0
  82. package/src/agent-tools.js +6 -2
  83. package/src/agents/codex/lifecycle-hook.js +107 -0
  84. package/src/agents/codex/lifecycle-hooks.js +231 -0
  85. package/src/agents/cursor/lifecycle-hook.js +133 -0
  86. package/src/agents/cursor/lifecycle-hooks.js +50 -0
  87. package/src/agents/lifecycle-message.js +19 -0
  88. package/src/app/agent-request-context.js +23 -0
  89. package/src/cli/command-registry.js +2 -0
  90. package/src/cli/employee-command.js +17 -0
  91. package/src/cli/remote-mcp-command.js +282 -0
  92. package/src/cli/setup-command.js +5 -0
  93. package/src/cli/update-command.js +6 -43
  94. package/src/cli.js +10 -0
  95. package/src/employees/catalog/jefa.json +21 -0
  96. package/src/employees/install-package.js +77 -0
  97. package/src/employees/management-policy.js +88 -0
  98. package/src/employees/manifest.js +53 -0
  99. package/src/employees/package-registry.js +107 -0
  100. package/src/employees/project-selection.js +50 -0
  101. package/src/employees/recruitment.js +199 -0
  102. package/src/employees/service.js +83 -0
  103. package/src/employees/task-entry.js +39 -0
  104. package/src/graphiti/agent-knowledge-workflows.js +135 -25
  105. package/src/graphiti/federated-application.js +78 -35
  106. package/src/graphiti/project-agent-control-plane.js +20 -3
  107. package/src/graphiti/project-agent-mapping.js +155 -5
  108. package/src/graphiti/project-agent-memory.js +115 -0
  109. package/src/graphiti/project-agent-task-entry.js +126 -0
  110. package/src/graphiti/project-agent-workflows.js +30 -14
  111. package/src/graphiti/provider-client.js +66 -4
  112. package/src/http/api-router.js +30 -5
  113. package/src/http/employee-api-router.js +64 -0
  114. package/src/http/error-mapping.js +4 -0
  115. package/src/http/graph-api-router.js +2 -0
  116. package/src/http/request-policy.js +6 -3
  117. package/src/http/static-handler.js +4 -3
  118. package/src/mcp/create-mcp-server.js +98 -37
  119. package/src/mcp/direct-call.js +38 -0
  120. package/src/mcp/instructions.js +8 -7
  121. package/src/mcp/provider-task-context-registry.js +74 -0
  122. package/src/mcp/remote-http-server.js +761 -0
  123. package/src/mcp/session-id.js +29 -3
  124. package/src/mcp/task-context-registry.js +88 -29
  125. package/src/mcp/tool-annotations.js +10 -2
  126. package/src/mcp/tool-result.js +8 -3
  127. package/src/mcp-server.js +20 -8
  128. package/src/native-runtime/runtime.js +58 -8
  129. package/src/semantic-version.js +42 -0
  130. package/src/server.js +76 -28
  131. package/src/setup/agent-installation-status.js +38 -6
  132. package/src/setup/agents.js +20 -8
  133. package/src/setup/claude-code-config.js +55 -13
  134. package/src/setup/codex-bootstrap.js +11 -10
  135. package/src/setup/codex-config.js +74 -7
  136. package/src/setup/config-backup.js +18 -2
  137. package/src/setup/cursor-config.js +22 -5
  138. package/src/setup/setup.js +30 -22
  139. package/src/storage/json-file.js +11 -3
  140. package/src/system/package-version-checker.js +72 -0
  141. package/src/system/system-service.js +3 -0
  142. package/dist/web/assets/KnowledgeEditDialog-CZ5ziPrD.js +0 -1
  143. package/dist/web/assets/KnowledgePage-BRYC9S5Y.js +0 -1
  144. package/dist/web/assets/PersonalProjectsPage-DEh8XWby.js +0 -1
  145. package/dist/web/assets/ProjectAgentsPage-Crh7PrXa.css +0 -1
  146. package/dist/web/assets/ProjectAgentsPage-DPnaH-TR.js +0 -11
  147. package/dist/web/assets/SearchableSelect-D3HM8YF8.js +0 -1
  148. package/dist/web/assets/conversation-launcher-settings-DrJEotdf.js +0 -1
  149. package/dist/web/assets/index-BnH5mcZx.css +0 -1
  150. package/dist/web/assets/index-GqJf1xqg.js +0 -2
@@ -14,13 +14,15 @@ export function createRuntimeLeaseClient({
14
14
  clearHeartbeat = clearInterval
15
15
  }) {
16
16
  const leases = new Map();
17
+ const acquisitions = new Set();
18
+ let closed = false;
17
19
 
18
- async function withGraphLease(owner, operation) {
19
- return withLease({ kind: 'graph', owner }, operation);
20
+ async function withGraphLease(owner, operation, requestContext = null) {
21
+ return withLease({ kind: 'graph', owner }, operation, requestContext);
20
22
  }
21
23
 
22
- async function withExecutorLease(executorId, owner, operation) {
23
- return withLease({ kind: 'executor', executorId, owner }, operation);
24
+ async function withExecutorLease(executorId, owner, operation, requestContext = null) {
25
+ return withLease({ kind: 'executor', executorId, owner }, operation, requestContext);
24
26
  }
25
27
 
26
28
  async function acquireGraphLease(owner) {
@@ -31,7 +33,19 @@ export function createRuntimeLeaseClient({
31
33
  return acquireLease({ kind: 'executor', executorId, owner });
32
34
  }
33
35
 
34
- async function acquireLease(input) {
36
+ async function acquireLease(input, signal = null) {
37
+ assertOpen();
38
+ signal?.throwIfAborted();
39
+ const acquisition = acquireOpenLease(input, signal);
40
+ acquisitions.add(acquisition);
41
+ try {
42
+ return await acquisition;
43
+ } finally {
44
+ acquisitions.delete(acquisition);
45
+ }
46
+ }
47
+
48
+ async function acquireOpenLease(input, signal = null) {
35
49
  const settings = readSettings(paths.adaptiveRuntimeSettingsPath);
36
50
  if (!settings.enabled) return disabledLease();
37
51
 
@@ -40,10 +54,24 @@ export function createRuntimeLeaseClient({
40
54
  method: 'POST',
41
55
  body: input,
42
56
  timeoutMs: 180_000,
43
- fetchImpl
57
+ fetchImpl,
58
+ signal
44
59
  });
45
60
  const handle = publicLease(lease);
46
- if (!handle.enabled) return handle;
61
+ if (!handle.enabled) {
62
+ const explicitlyDisabled = lease?.enabled === false && lease.leaseId === null;
63
+ if (!explicitlyDisabled) {
64
+ // Preserve a usable ID before rejecting a malformed success response.
65
+ // Without one, remote TTL is the only available cleanup mechanism.
66
+ if (typeof lease?.leaseId === 'string' && lease.leaseId.trim()) {
67
+ await releaseRemote({ consoleUrl, handle: { leaseId: lease.leaseId } }).catch(() => {});
68
+ }
69
+ throw new Error('Invalid runtime lease response');
70
+ }
71
+ assertOpen();
72
+ signal?.throwIfAborted();
73
+ return handle;
74
+ }
47
75
 
48
76
  const entry = {
49
77
  handle,
@@ -55,6 +83,8 @@ export function createRuntimeLeaseClient({
55
83
  };
56
84
  leases.set(handle.leaseId, entry);
57
85
  try {
86
+ assertOpen();
87
+ signal?.throwIfAborted();
58
88
  startHeartbeat(entry, settings);
59
89
  return { ...entry.handle };
60
90
  } catch (error) {
@@ -89,10 +119,13 @@ export function createRuntimeLeaseClient({
89
119
  }
90
120
  }
91
121
 
92
- async function withLease(input, operation) {
122
+ async function withLease(input, operation, requestContext = null) {
93
123
  if (typeof operation !== 'function') throw new TypeError('Runtime operation is required');
94
- const handle = await acquireLease(input);
124
+ const signal = agentRequestSignal(requestContext);
125
+ signal?.throwIfAborted();
126
+ const handle = await acquireLease(input, signal);
95
127
  try {
128
+ signal?.throwIfAborted();
96
129
  return await operation();
97
130
  } finally {
98
131
  await releaseLease(handle);
@@ -100,8 +133,16 @@ export function createRuntimeLeaseClient({
100
133
  }
101
134
 
102
135
  async function close() {
136
+ closed = true;
103
137
  const handles = [...leases.values()].map(({ handle }) => ({ ...handle }));
104
- await Promise.allSettled(handles.map((handle) => releaseLease(handle)));
138
+ await Promise.allSettled([
139
+ ...acquisitions,
140
+ ...handles.map((handle) => releaseLease(handle))
141
+ ]);
142
+ }
143
+
144
+ function assertOpen() {
145
+ if (closed) throw new Error('Runtime lease client is closed');
105
146
  }
106
147
 
107
148
  function startHeartbeat(entry, settings) {
@@ -184,7 +225,8 @@ export function createRuntimeLeaseClient({
184
225
  }
185
226
  return { ...entry.handle, refreshed };
186
227
  } catch (error) {
187
- stopHeartbeat(entry);
228
+ // A transient request failure must not abandon a still-running operation.
229
+ // The next heartbeat retries; release/close or refreshed:false stops it.
188
230
  throw error;
189
231
  }
190
232
  })();
@@ -269,7 +311,7 @@ function publicLease(value, fallback = null) {
269
311
  ...(fallback && typeof fallback === 'object' ? fallback : {}),
270
312
  ...(value && typeof value === 'object' ? value : {})
271
313
  };
272
- if (source.enabled !== true || typeof source.leaseId !== 'string' || !source.leaseId) {
314
+ if (source.enabled !== true || typeof source.leaseId !== 'string' || !source.leaseId.trim()) {
273
315
  return disabledLease();
274
316
  }
275
317
  const handle = {
@@ -311,13 +353,15 @@ async function requestJson(url, {
311
353
  method,
312
354
  body,
313
355
  timeoutMs,
314
- fetchImpl
356
+ fetchImpl,
357
+ signal = null
315
358
  }) {
359
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
316
360
  const response = await fetchImpl(url, {
317
361
  method,
318
362
  headers: body === undefined ? undefined : { 'content-type': 'application/json' },
319
363
  body: body === undefined ? undefined : JSON.stringify(body),
320
- signal: AbortSignal.timeout(timeoutMs)
364
+ signal: signal ? AbortSignal.any([timeoutSignal, signal]) : timeoutSignal
321
365
  });
322
366
  if (!response.ok) {
323
367
  let detail = null;
@@ -333,3 +377,11 @@ async function requestJson(url, {
333
377
  }
334
378
  return response.json();
335
379
  }
380
+
381
+ function agentRequestSignal(value) {
382
+ const signal = value?.signal;
383
+ return signal && typeof signal.addEventListener === 'function' &&
384
+ typeof signal.throwIfAborted === 'function'
385
+ ? signal
386
+ : null;
387
+ }
@@ -9,6 +9,8 @@ import {
9
9
  writeAdaptiveRuntimeState
10
10
  } from './state.js';
11
11
 
12
+ const IDLE_FAILURE_RETRY_MS = 30_000;
13
+
12
14
  export function createAdaptiveRuntimeBroker({
13
15
  paths,
14
16
  settings,
@@ -33,12 +35,14 @@ export function createAdaptiveRuntimeBroker({
33
35
  let databaseTimer = null;
34
36
  let transition = Promise.resolve();
35
37
  let closed = false;
38
+ let closePromise = null;
39
+ let idleRetryAt = 0;
36
40
 
37
41
  scheduleIdle();
38
42
 
39
43
  async function acquire(input = {}) {
40
44
  if (!configured.enabled) return disabledLease();
41
- if (closed) throw new Error('Adaptive runtime coordinator is closed');
45
+ assertOpen();
42
46
  const kind = input.kind ?? 'graph';
43
47
  if (!['graph', 'executor'].includes(kind)) {
44
48
  throw new TypeError('Runtime lease kind must be graph or executor');
@@ -56,18 +60,22 @@ export function createAdaptiveRuntimeBroker({
56
60
  };
57
61
  leases.set(leaseId, lease);
58
62
  clearIdleTimers();
59
- armExpiry(lease);
60
63
  try {
61
64
  await exclusive(async () => {
65
+ assertOpen();
62
66
  await ensureAwake();
63
67
  if (executorId) await executorPool.acquire(executorId, leaseId);
64
68
  });
69
+ assertOpen();
70
+ idleRetryAt = 0;
71
+ // The caller can only heartbeat after startup returns its lease handle.
72
+ armExpiry(lease);
65
73
  return publicLease(lease);
66
74
  } catch (error) {
67
75
  leases.delete(leaseId);
68
76
  if (lease.timer) clearTimer(lease.timer);
69
77
  if (executorId) executorPool.release(executorId, leaseId);
70
- touch();
78
+ touch(errorMessage(error));
71
79
  scheduleIdle();
72
80
  throw error;
73
81
  }
@@ -126,14 +134,19 @@ export function createAdaptiveRuntimeBroker({
126
134
  }
127
135
 
128
136
  function close() {
129
- if (closed) return;
137
+ if (closed) return closePromise;
130
138
  closed = true;
131
139
  clearIdleTimers();
132
140
  for (const lease of leases.values()) {
133
141
  if (lease.timer) clearTimer(lease.timer);
134
142
  }
135
143
  leases.clear();
136
- executorPool.close();
144
+ closePromise = executorPool.close();
145
+ return closePromise;
146
+ }
147
+
148
+ function assertOpen() {
149
+ if (closed) throw new Error('Adaptive runtime coordinator is closed');
137
150
  }
138
151
 
139
152
  async function ensureAwake() {
@@ -175,9 +188,11 @@ export function createAdaptiveRuntimeBroker({
175
188
  await operation();
176
189
  }).catch((error) => {
177
190
  persist('degraded', errorMessage(error));
191
+ // A missed idle deadline must not become a zero-delay failure loop.
192
+ idleRetryAt = now() + IDLE_FAILURE_RETRY_MS;
178
193
  scheduleIdle();
179
194
  });
180
- }, Math.max(0, timestamp - now()));
195
+ }, Math.max(0, timestamp - now(), idleRetryAt - now()));
181
196
  timer?.unref?.();
182
197
  return timer;
183
198
  }
@@ -241,8 +256,8 @@ export function createAdaptiveRuntimeBroker({
241
256
  return initial;
242
257
  }
243
258
 
244
- function touch() {
245
- state = persistValue({ ...state, lastActivityAt: isoNow(), lastError: null });
259
+ function touch(lastError = null) {
260
+ state = persistValue({ ...state, lastActivityAt: isoNow(), lastError });
246
261
  }
247
262
 
248
263
  function persist(stage, lastError = null) {
@@ -260,7 +275,7 @@ export function createAdaptiveRuntimeBroker({
260
275
 
261
276
  function nextSleepAt(lastActivityMs, idleSeconds, stages) {
262
277
  if (!configured.enabled || leases.size > 0 || !stages.includes(state.stage)) return null;
263
- return new Date(lastActivityMs + idleSeconds * 1000).toISOString();
278
+ return new Date(Math.max(lastActivityMs + idleSeconds * 1000, idleRetryAt)).toISOString();
264
279
  }
265
280
 
266
281
  function clearIdleTimers() {
@@ -0,0 +1,43 @@
1
+ import { booleanSchema, objectSchema, stringSchema } from './schema.js';
2
+
3
+ const id = { ...stringSchema(), minLength: 1, maxLength: 256 };
4
+ const projectPath = { ...stringSchema(), minLength: 1, maxLength: 4096 };
5
+ const target = { projectPath, templateId: id };
6
+
7
+ export const EMPLOYEE_TOOL_DEFINITIONS = [
8
+ {
9
+ name: 'list_employee_templates', title: 'READ · Employee catalog',
10
+ description: 'List reusable employee Agent templates, recruitment state, assignments and installed workbench availability. This does not recruit or start an executor.',
11
+ inputSchema: objectSchema({})
12
+ },
13
+ {
14
+ name: 'recruit_employee', title: 'WRITE · Recruit an employee',
15
+ description: 'On an explicit user request recruit an employee. New recruitment without a scope uses the template default (Jefa: all projects, including future projects). Existing identities retain their saved scope. Use management.mode=all plus excludedProjectIds for a persistent all-except rule, or mode=selected plus projectIds for a fixed selection. Never combine management with projectPath/personalProjectIds. Changing existing management requires replaceAssignments and expectedAssignmentsVersion from the catalog. Legacy projectPath or personalProjectIds remains a current-project selection. Exclusions take effect before assignment synchronization; data and history are preserved. Reload after conflicts or incomplete updates. No models or workers start. Reactivation must be explicitly requested.',
16
+ inputSchema: objectSchema({
17
+ ...target, reactivate: booleanSchema(),
18
+ personalProjectIds: { type: 'array', items: id, maxItems: 500 },
19
+ replaceAssignments: booleanSchema(),
20
+ expectedAssignmentsVersion: { ...stringSchema(), minLength: 1, maxLength: 128 },
21
+ management: objectSchema({
22
+ mode: { type: 'string', enum: ['all', 'selected'] },
23
+ projectIds: { type: 'array', items: id, maxItems: 500 },
24
+ excludedProjectIds: { type: 'array', items: id, maxItems: 500 },
25
+ titleMode: { type: 'string', enum: ['off', 'suggest', 'auto'] },
26
+ titleStyle: { type: 'string', enum: ['text', 'emoji'] },
27
+ }, ['mode']),
28
+ }, ['templateId'])
29
+ },
30
+ {
31
+ name: 'list_employee_tools', title: 'READ · Employee workbench tools',
32
+ description: 'Discover the exact input schemas and permissions for a recruited employee in the current project. Read these schemas before calling a tool; a workbench has to be installed first.',
33
+ inputSchema: objectSchema(target, ['projectPath', 'templateId'])
34
+ },
35
+ {
36
+ name: 'call_employee_tool', title: 'WRITE · Call an employee workbench tool',
37
+ description: 'Call a discovered employee tool inside the exact assigned project. May read or change data according to the tool schema. Preserve optimistic concurrency and idempotency keys. Does not grant cross-project access, execute an external model, or confirm task completion for the human.',
38
+ inputSchema: objectSchema({
39
+ ...target, tool: id,
40
+ arguments: { type: 'object', additionalProperties: true }
41
+ }, ['projectPath', 'templateId', 'tool', 'arguments'])
42
+ }
43
+ ];
@@ -31,6 +31,8 @@ import {
31
31
  recruitmentStatus,
32
32
  routingRuleScope
33
33
  } from './project-agent-definitions.js';
34
+ import { PROJECT_AGENT_MEMORY_DEFINITIONS, workingMemorySchema } from './project-agent-memory-definitions.js';
35
+ import { EMPLOYEE_TOOL_DEFINITIONS } from './employee-definitions.js';
34
36
 
35
37
  const id = boundedString(256);
36
38
  const label = boundedString(512);
@@ -98,7 +100,7 @@ const profileAspect = {
98
100
  };
99
101
  const sourceApplication = {
100
102
  type: ['string', 'null'],
101
- enum: ['codex', 'claude_code', 'cursor', 'kiro', 'other', null]
103
+ enum: ['codex', 'claude', 'claude_code', 'cursor', 'kiro', 'other', null]
102
104
  };
103
105
  const epistemicFields = {
104
106
  originQuadrant: epistemicQuadrant,
@@ -276,13 +278,19 @@ const workflowObservedStep = objectSchema({
276
278
  }, ['actionId', 'name']);
277
279
 
278
280
  export const GRAPH_TOOL_DEFINITIONS = [
281
+ ...EMPLOYEE_TOOL_DEFINITIONS,
279
282
  {
280
283
  name: 'begin_task_context',
281
284
  title: 'LIFECYCLE · Begin a Fuli-aware task',
282
- description: 'Lifecycle entry used by supported Agent hooks before the model processes a user prompt. It resolves the exact local project from projectPath, loads effective collaboration preferences, and uses taskPrompt transiently for bounded automatic recall when the request signals a stable prior fact, runbook, URL, decision, release, deployment, or authentication method. It never stores or returns projectPath or taskPrompt. Inspect task_knowledge_recall before asking the user to repeat stable context. Returns an opaque taskContextToken. Claude Code setup installs this as a deterministic UserPromptSubmit hook; manual Agent calls are a fallback.',
285
+ description: 'Lifecycle entry used by supported Agent hooks before the model processes a user prompt. It resolves the exact local project from projectPath, loads effective collaboration preferences, and uses taskPrompt transiently for bounded automatic recall when the request signals a stable prior fact, runbook, URL, decision, release, deployment, or authentication method. It never stores or returns projectPath or taskPrompt. Inspect task_knowledge_recall before asking the user to repeat stable context. Returns an opaque taskContextToken. Fuli setup generates Claude Code and Codex UserPromptSubmit hook configuration and a Cursor task-entry adapter. Configuration is not proof of host loading, trust, or execution. Use supplied hook context without repeating the preference fallback; Cursor retrieves context through the Agent when its hook cannot inject it.',
283
286
  inputSchema: objectSchema({
284
287
  sessionId: id,
288
+ turnId: id,
285
289
  projectPath: boundedString(4096),
290
+ personalProjectId: nullableStringSchema(),
291
+ projectAgentId: nullableStringSchema(),
292
+ workKind: boundedString(128),
293
+ requiredCapabilities: arraySchema(boundedString(512), { maxItems: 16 }),
286
294
  taskPrompt: boundedString(8192)
287
295
  }, ['sessionId', 'projectPath'])
288
296
  },
@@ -294,7 +302,11 @@ export const GRAPH_TOOL_DEFINITIONS = [
294
302
  taskContextToken: id,
295
303
  disposition: enumSchema(['capture_candidates', 'retain_nothing']),
296
304
  reason: boundedString(2000),
297
- capture: objectSchema(captureEpisodeFields, captureEpisodeRequired)
305
+ capture: objectSchema(captureEpisodeFields, captureEpisodeRequired),
306
+ agentMemory: objectSchema({
307
+ expectedRevision: integerSchema({ minimum: 0 }),
308
+ memory: workingMemorySchema
309
+ }, ['expectedRevision', 'memory'])
298
310
  }, ['taskContextToken', 'disposition', 'reason'])
299
311
  },
300
312
  {
@@ -308,10 +320,13 @@ export const GRAPH_TOOL_DEFINITIONS = [
308
320
  title: 'READ FIRST · Load task collaboration preferences',
309
321
  description: 'Fallback for Agents or tasks without begin_task_context hook context. When no hook context was supplied, call this exact tool name at the start of every user task before any other tool or answer; never substitute a project action tool. Do not call it redundantly when the entry hook already supplied preferences. Pass projectPath as the current working directory and taskPrompt as the current user request. Fuli uses both transiently, never stores or returns them, resolves one exact registered local project, and performs bounded automatic recall only when the request signals stable prior context. Inspect task_knowledge_recall before asking the user to repeat a project fact or method. On a miss, search_current_project_knowledge with one to four focused action, artifact, target-system, or identifier queries; never use the full conversational request as the only query. Apply effective_preferences before answering or constructing tool arguments. Resolve only relevant deferred_conflicts first. For write tools, enforce preferences in the actual payload; the final answer is not compliance. Personal-global preferences always apply; conflicted, pending, invalid, ambiguous-project, and unrelated-project items never auto-apply. Automatic injection does not count as usage evidence. personalProjectId remains an explicit compatibility override.',
310
322
  inputSchema: objectSchema({
323
+ sessionId: id,
311
324
  projectPath: boundedString(4096),
312
325
  taskPrompt: boundedString(8192),
313
326
  personalProjectId: nullableStringSchema(),
314
327
  projectAgentId: nullableStringSchema(),
328
+ workKind: boundedString(128),
329
+ requiredCapabilities: arraySchema(boundedString(512), { maxItems: 16 }),
315
330
  limit: integerSchema({ minimum: 1, maximum: 200 })
316
331
  }, ['projectPath'])
317
332
  },
@@ -514,6 +529,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
514
529
  description: 'High-level project search for normal Agent work. Pass projectPath plus one or more focused queries; Fuli resolves the exact local project, searches its local knowledge first, then follows outgoing PART_OF or USES_KNOWLEDGE_FROM relations to authorized parent/source knowledge. An exact current-project item with the same stable key overrides an inherited item. The tool never guesses an ambiguous project and never traverses RELATED_TO. Instead it returns structured related_project_suggestions so the Agent can ask whether to add exactly one related project to this read-only search; explicit human confirmation is required before any expansion.',
515
530
  inputSchema: objectSchema({
516
531
  projectPath: boundedString(4096),
532
+ personalProjectId: nullableStringSchema(),
517
533
  queries: arraySchema(shortText, { minItems: 1, maxItems: 10 }),
518
534
  limitPerQuery: integerSchema({ minimum: 1, maximum: 50 }),
519
535
  includeHistorical: booleanSchema(),
@@ -671,6 +687,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
671
687
  agentId: id
672
688
  }, ['personalSpaceId', 'agentId'])
673
689
  },
690
+ ...PROJECT_AGENT_MEMORY_DEFINITIONS,
674
691
  {
675
692
  name: 'delete_project_agent',
676
693
  title: 'WRITE · Archive one Agent identity',
@@ -759,6 +776,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
759
776
  description: 'View one task with lead/collaborator assignments, routing decision, effective model strategy, source client/session, event history, actual executor/model, routing rule, and fallback audit. Missing connected state is not synthesized.',
760
777
  inputSchema: objectSchema({
761
778
  personalSpaceId: id,
779
+ personalProjectId: id,
762
780
  taskId: id,
763
781
  includeEvents: booleanSchema()
764
782
  }, ['personalSpaceId', 'taskId'])
@@ -766,7 +784,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
766
784
  {
767
785
  name: 'record_project_agent_task_activity',
768
786
  title: 'WRITE · Record task activity',
769
- description: 'Record one explicit task event, including real terminal activity and actual executor/model/client attribution. Terminal activity is the only source for per-Agent activity summaries.',
787
+ description: 'Record one explicit task event, including real terminal activity, actual executor/model/client attribution, and an optional source-labelled cumulative token snapshot for the concrete worker. Never estimate missing usage. Terminal activity is the only source for per-Agent activity summaries.',
770
788
  inputSchema: projectAgentTaskActivityInput
771
789
  },
772
790
  {
@@ -775,6 +793,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
775
793
  description: 'Return actual per-Agent daily completed/failed/cancelled activity and verifiable summaries. No history is fabricated and configured Agent status is not treated as running state.',
776
794
  inputSchema: objectSchema({
777
795
  personalSpaceId: id,
796
+ personalProjectId: id,
778
797
  agentId: id,
779
798
  fromDate: nullableStringSchema(),
780
799
  toDate: nullableStringSchema()
@@ -806,13 +825,13 @@ export const GRAPH_TOOL_DEFINITIONS = [
806
825
  {
807
826
  name: 'get_project_agent_recruitment_policy',
808
827
  title: 'READ · Get recruitment policy',
809
- description: 'Read the persisted recruitment confirmation mode. The default is automatic; require_confirmation creates only a pending recruitment event.',
828
+ description: 'Deprecated: read the legacy stored space setting, which does not control recruitment. Use get_project_agent_coordination_policy for the effective exact-project switches.',
810
829
  inputSchema: objectSchema({ personalSpaceId: id }, ['personalSpaceId'])
811
830
  },
812
831
  {
813
832
  name: 'update_project_agent_recruitment_policy',
814
833
  title: 'WRITE · Set recruitment policy',
815
- description: 'Persist automatic or require_confirmation recruitment authorization. This changes policy only; it does not create an Agent or recruitment record by itself.',
834
+ description: 'Deprecated compatibility storage only; this does not change recruitment authorization. Use update_project_agent_coordination_policy with the exact project and askBeforeRecruitment instead. The response warns that this legacy value is not effective.',
816
835
  inputSchema: objectSchema({
817
836
  personalSpaceId: id,
818
837
  confirmationMode: recruitmentConfirmationMode
@@ -1,6 +1,10 @@
1
1
  import { ApplicationError } from '../app/application-error.js';
2
2
 
3
3
  const HANDLERS = Object.freeze({
4
+ list_employee_templates: (app, input) => employeeService(app).list(input),
5
+ recruit_employee: (app, input) => employeeService(app).recruit(input),
6
+ list_employee_tools: (app, input) => employeeService(app).describeTools(input),
7
+ call_employee_tool: (app, input) => employeeService(app).callTool(input),
4
8
  begin_task_context: (app, input) => app.beginTaskContext(input),
5
9
  checkpoint_task_knowledge: (app, input) =>
6
10
  app.checkpointTaskKnowledge(input),
@@ -82,6 +86,8 @@ const HANDLERS = Object.freeze({
82
86
  replace_project_agent_assignment: (app, input) =>
83
87
  app.replaceProjectAgentAssignment(input),
84
88
  get_project_agent_context: (app, input) => app.getProjectAgentContext(input),
89
+ get_project_agent_memory: (app, input) => app.getProjectAgentMemory(input),
90
+ checkpoint_project_agent_memory: (app, input) => app.checkpointProjectAgentMemory(input),
85
91
  coordinate_project_agent_task: (app, input) =>
86
92
  app.coordinateProjectAgentTask(input),
87
93
  acquire_runtime_lease: (app, input) => app.acquireRuntimeLease(input),
@@ -171,6 +177,11 @@ const HANDLERS = Object.freeze({
171
177
  get_graphiti_status: (app) => app.getGraphitiStatus()
172
178
  });
173
179
 
180
+ function employeeService(app) {
181
+ if (!app.employees) throw new Error('Employee runtime is unavailable');
182
+ return app.employees;
183
+ }
184
+
174
185
  export function dispatchGraphTool(app, name, input) {
175
186
  const handler = HANDLERS[name];
176
187
  if (!handler) throw new Error(`Unknown agent tool: ${name}`);
@@ -14,8 +14,26 @@ const idempotencyKey = { ...boundedString(256), minLength: 8 };
14
14
  const dateTime = { ...boundedString(64), format: 'date-time' };
15
15
  const sourceApplication = {
16
16
  type: ['string', 'null'],
17
- enum: ['codex', 'claude_code', 'cursor', 'kiro', 'other', null]
17
+ enum: ['codex', 'claude', 'claude_code', 'cursor', 'kiro', 'other', null]
18
18
  };
19
+ const nullableTokenCount = {
20
+ ...integerSchema({ minimum: 0 }),
21
+ type: ['integer', 'null']
22
+ };
23
+ export const projectAgentTokenUsage = objectSchema({
24
+ source: enumSchema(['executor', 'host', 'dingdong']),
25
+ totalTokens: integerSchema({ minimum: 0 }),
26
+ inputTokens: nullableTokenCount,
27
+ outputTokens: nullableTokenCount,
28
+ cachedInputTokens: nullableTokenCount,
29
+ cacheWriteInputTokens: nullableTokenCount,
30
+ reasoningOutputTokens: nullableTokenCount
31
+ }, ['source', 'totalTokens']);
32
+ export const projectAgentWorkerRuntime = objectSchema({
33
+ application: enumSchema(['codex', 'claude', 'claude_code', 'cursor', 'kiro', 'other']),
34
+ sessionId: { ...nullableStringSchema(), minLength: 1, maxLength: 256 },
35
+ sessionUrl: { ...nullableStringSchema(), maxLength: 2048 }
36
+ }, ['application']);
19
37
 
20
38
  const projectAgentStatus = enumSchema(['active', 'inactive', 'archived']);
21
39
  export const nullableProjectAgentStatus = {
@@ -43,8 +61,8 @@ export const projectAgentProfile = objectSchema({
43
61
  defaultModelStrategy: projectAgentModelStrategy,
44
62
  executorPolicy: projectAgentExecutorPolicy,
45
63
  allowedClients: arraySchema(
46
- enumSchema(['codex', 'claude_code', 'cursor', 'kiro', 'other']),
47
- { maxItems: 5 }
64
+ enumSchema(['codex', 'claude', 'claude_code', 'cursor', 'kiro', 'other']),
65
+ { maxItems: 6 }
48
66
  ),
49
67
  testSource: nullableStringSchema(),
50
68
  cleanupEligible: booleanSchema(),
@@ -116,6 +134,7 @@ export const projectAgentTaskSubmitInput = objectSchema({
116
134
  objective: boundedString(4096),
117
135
  workKind: boundedString(128),
118
136
  requiredCapabilities: arraySchema(boundedString(512), { maxItems: 16 }),
137
+ executorCapabilityHints: arraySchema(boundedString(512), { maxItems: 16 }),
119
138
  duration: projectAgentTaskDuration,
120
139
  staffingIntent: projectAgentStaffingIntent,
121
140
  leadAgentId: nullableStringSchema(),
@@ -140,6 +159,7 @@ export const projectAgentTaskCoordinateInput = objectSchema({
140
159
  objective: boundedString(4096),
141
160
  workKind: boundedString(128),
142
161
  requiredCapabilities: arraySchema(boundedString(512), { maxItems: 16 }),
162
+ executorCapabilityHints: arraySchema(boundedString(512), { maxItems: 16 }),
143
163
  duration: projectAgentTaskDuration,
144
164
  staffingIntent: projectAgentStaffingIntent,
145
165
  leadAgentId: nullableStringSchema(),
@@ -170,6 +190,11 @@ export const projectAgentTaskActivityInput = objectSchema({
170
190
  actorKind: enumSchema(['agent', 'human']),
171
191
  sourceApplication,
172
192
  sourceSessionId: nullableStringSchema(),
193
+ sourceSessionUrl: { ...nullableStringSchema(), maxLength: 2048 },
194
+ toolsUsed: {
195
+ ...arraySchema(boundedString(512), { maxItems: 32 }),
196
+ type: ['array', 'null']
197
+ },
173
198
  actualExecutor: nullableStringSchema(),
174
199
  actualModelProvider: nullableStringSchema(),
175
200
  actualModel: nullableStringSchema(),
@@ -183,7 +208,13 @@ export const projectAgentTaskActivityInput = objectSchema({
183
208
  workerId: { ...nullableStringSchema(), minLength: 1, maxLength: 128 },
184
209
  workerLabel: { ...nullableStringSchema(), minLength: 1, maxLength: 160 },
185
210
  workerOccupationEmoji: { ...nullableStringSchema(), minLength: 1, maxLength: 32 },
186
- workerStatus: { ...projectAgentTaskStatus, type: ['string', 'null'] }
211
+ workerStatus: { ...projectAgentTaskStatus, type: ['string', 'null'] },
212
+ tokenUsage: { ...projectAgentTokenUsage, type: ['object', 'null'] },
213
+ workerRuntime: {
214
+ ...projectAgentWorkerRuntime,
215
+ type: ['object', 'null'],
216
+ description: 'Observed worker application and session, separate from the reporting host sourceApplication/sourceSessionId. Requires workerId and a participating Agent; never infer a session URL or actual model. Token usage is the latest cumulative snapshot for this worker session, not a sum across retries.'
217
+ }
187
218
  }, [
188
219
  'personalSpaceId', 'personalProjectId', 'taskId', 'idempotencyKey', 'status', 'summary'
189
220
  ]);
@@ -0,0 +1,36 @@
1
+ import {
2
+ arraySchema, integerSchema, nullableStringSchema, objectSchema, stringSchema
3
+ } from './schema.js';
4
+
5
+ const id = { ...stringSchema(), minLength: 1, maxLength: 128 };
6
+ const note = { ...stringSchema(), minLength: 1, maxLength: 1024 };
7
+ export const workingMemorySchema = objectSchema({
8
+ summary: { ...stringSchema(), minLength: 1, maxLength: 4000 },
9
+ decisions: arraySchema(note, { maxItems: 12 }),
10
+ openThreads: arraySchema(note, { maxItems: 12 }),
11
+ nextActions: arraySchema(note, { maxItems: 12 })
12
+ }, ['summary']);
13
+
14
+ export const PROJECT_AGENT_MEMORY_DEFINITIONS = [
15
+ {
16
+ name: 'get_project_agent_memory',
17
+ title: 'READ · Restore private Agent working memory',
18
+ description: 'Read the latest versioned working notes for exactly one assigned Agent and project from the shared local Provider. This works across hosts connected to that Provider. Working notes are not confirmed facts or user instructions; do not load another Agent’s private memory.',
19
+ inputSchema: objectSchema({
20
+ personalSpaceId: id, personalProjectId: id, agentId: id,
21
+ limit: integerSchema({ minimum: 1, maximum: 10 })
22
+ }, ['personalSpaceId', 'personalProjectId', 'agentId'])
23
+ },
24
+ {
25
+ name: 'checkpoint_project_agent_memory',
26
+ title: 'WRITE · Checkpoint private Agent working memory',
27
+ description: 'Save a bounded, distilled working-context snapshot after meaningful work: summary, decisions, unresolved threads and next actions. Read the current revision first, preserve still-relevant notes, and use expectedRevision for optimistic concurrency. On conflict reload and merge; never blindly retry an overwrite. Reuse one idempotency key for the same logical write. Never store raw chats, credentials, unsupported facts, or another Agent’s memory. Shared confirmed facts belong in capture_session_knowledge. Honors disabled capture.',
28
+ inputSchema: objectSchema({
29
+ personalSpaceId: id, personalProjectId: id, agentId: id,
30
+ expectedRevision: integerSchema({ minimum: 0 }),
31
+ idempotencyKey: { ...stringSchema(), minLength: 8, maxLength: 256 },
32
+ memory: workingMemorySchema,
33
+ taskId: { ...nullableStringSchema(), minLength: 1, maxLength: 128 }
34
+ }, ['personalSpaceId', 'personalProjectId', 'agentId', 'expectedRevision', 'idempotencyKey', 'memory'])
35
+ }
36
+ ];
@@ -1,5 +1,6 @@
1
1
  import { GRAPH_TOOL_DEFINITIONS } from './agent-tools/graph-definitions.js';
2
2
  import { dispatchGraphTool } from './agent-tools/graph-handlers.js';
3
+ import { runWithAgentRequestContext } from './app/agent-request-context.js';
3
4
 
4
5
  const TOOL_DEFINITIONS = [...GRAPH_TOOL_DEFINITIONS];
5
6
 
@@ -7,8 +8,11 @@ export function listAgentTools() {
7
8
  return clone(TOOL_DEFINITIONS);
8
9
  }
9
10
 
10
- export function callAgentTool(app, name, input = {}) {
11
- return dispatchGraphTool(app, name, input);
11
+ export function callAgentTool(app, name, input = {}, requestContext = null) {
12
+ return runWithAgentRequestContext(
13
+ requestContext,
14
+ () => dispatchGraphTool(app, name, input)
15
+ );
12
16
  }
13
17
 
14
18
  function clone(value) {