fuli-context 0.9.0 → 0.11.0

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 (210) hide show
  1. package/README.md +29 -58
  2. package/README.zh-CN.md +24 -54
  3. package/dist/web/assets/AboutPage-CKq4IRHd.js +1 -0
  4. package/dist/web/assets/AgentConversations-DfXiFvBN.js +2 -0
  5. package/dist/web/assets/AgentConversations-XqNEBY_2.css +1 -0
  6. package/dist/web/assets/AgentHand-BBM-sKh2.js +1 -0
  7. package/dist/web/assets/AgentName-C_6R80nP.js +1 -0
  8. package/dist/web/assets/AgentProfilePage-Cz-i1fRD.js +1 -0
  9. package/dist/web/assets/AgentsDirectoryPage-BI0EFz6y.js +1 -0
  10. package/dist/web/assets/AgentsDirectoryPage-yRWktiin.css +1 -0
  11. package/dist/web/assets/ConnectionsPage-BoC6kXgs.js +1 -0
  12. package/dist/web/assets/ConnectionsPage-CM9C-zEI.css +1 -0
  13. package/dist/web/assets/EmployeeRecruitDialog-BVyclQAJ.js +1 -0
  14. package/dist/web/assets/EmployeeRecruitDialog-CVbqCzHb.css +1 -0
  15. package/dist/web/assets/EmployeeWorkbenchPage-D-c_Jm2z.css +1 -0
  16. package/dist/web/assets/EmployeeWorkbenchPage-D2GvYraI.js +3 -0
  17. package/dist/web/assets/KnowledgeEditDialog-a0AKWics.js +1 -0
  18. package/dist/web/assets/KnowledgeEditDialog-hT9znkoe.css +1 -0
  19. package/dist/web/assets/KnowledgeInspector-DMg9yJZ2.js +1 -0
  20. package/dist/web/assets/KnowledgeOrganizerPage-BgsHM3Qw.js +1 -0
  21. package/dist/web/assets/KnowledgeOrganizerPage-DeL7Jp34.css +1 -0
  22. package/dist/web/assets/KnowledgePage-kn8MqUCO.js +1 -0
  23. package/dist/web/assets/KnowledgeWorkspace-BtRq9fvC.js +4 -0
  24. package/dist/web/assets/KnowledgeWorkspace-D4a7FcAb.css +1 -0
  25. package/dist/web/assets/NotFoundPage-D-7jLz3o.js +1 -0
  26. package/dist/web/assets/OverviewPage-BHPc-XxY.js +1 -0
  27. package/dist/web/assets/OverviewPage-By-fUzSG.css +1 -0
  28. package/dist/web/assets/PersonalProfilePage-C00iV0Kx.js +1 -0
  29. package/dist/web/assets/PersonalProfilePage-DrMDAAdy.css +1 -0
  30. package/dist/web/assets/PersonalProjectsPage-CFNkdHSo.js +1 -0
  31. package/dist/web/assets/ProjectAgentFirstTask-Bi1ZsTC1.css +1 -0
  32. package/dist/web/assets/ProjectAgentFirstTask-CrCWEYgn.js +1 -0
  33. package/dist/web/assets/ProjectAgentsPage-BzztLMH-.js +12 -0
  34. package/dist/web/assets/ProjectAgentsPage-DNLNjYp-.css +1 -0
  35. package/dist/web/assets/ProjectScopePicker-CMxn3txv.js +1 -0
  36. package/dist/web/assets/PublicProjectsPage-DKamXDV9.css +1 -0
  37. package/dist/web/assets/PublicProjectsPage-DQVtvhOS.js +1 -0
  38. package/dist/web/assets/ReviewPage-BZhvyDWc.js +1 -0
  39. package/dist/web/assets/RoundtablesPage-CVebTXQa.js +1 -0
  40. package/dist/web/assets/RoundtablesPage-Cb0R4GaM.css +1 -0
  41. package/dist/web/assets/SettingsPage-1knvfOW6.js +1 -0
  42. package/dist/web/assets/SettingsPage-BmYkLul4.css +1 -0
  43. package/dist/web/assets/SharedJefaReportPage-BCVq8u0P.js +1 -0
  44. package/dist/web/assets/UiDialog-Da1P10f4.js +1 -0
  45. package/dist/web/assets/UiDisclosure-8wE8YuE4.js +1 -0
  46. package/dist/web/assets/UiSegmented-BKZXc8QF.js +1 -0
  47. package/dist/web/assets/UiSelect-Bv6R0yr1.css +1 -0
  48. package/dist/web/assets/UiSelect-D5Xj5Ec4.js +1 -0
  49. package/dist/web/assets/WritingTastePage-B8-etOBn.js +1 -0
  50. package/dist/web/assets/WritingTastePage-B_zYD1df.css +1 -0
  51. package/dist/web/assets/agent-profile-BZj3ezWe.css +1 -0
  52. package/dist/web/assets/{attention-store-Dk-_P4Oi.js → attention-store-BfxtL9EV.js} +1 -1
  53. package/dist/web/assets/{catalog-DN72452f.js → catalog-6SN9i3WR.js} +1 -1
  54. package/dist/web/assets/client-CUTkF3t4.js +1 -0
  55. package/dist/web/assets/{console-BtPqo79j.js → console-CG8tdtSs.js} +1 -1
  56. package/dist/web/assets/{conversation-launcher-settings-jkHRmWai.js → conversation-launcher-settings-CyZkLJg7.js} +1 -1
  57. package/dist/web/assets/{i18n-D2CJkEYK.js → i18n-CCMR4gDS.js} +3 -3
  58. package/dist/web/assets/{identity-B3zEw-1u.js → identity-C1eN-FSB.js} +1 -1
  59. package/dist/web/assets/index-kPB0Cf8Q.css +1 -0
  60. package/dist/web/assets/index-sLDKI6R-.js +5 -0
  61. package/dist/web/assets/{model-S7ehAjky.js → model-l-sAqtW_.js} +1 -1
  62. package/dist/web/assets/{personal-profile-graph--GG3Mizo.js → personal-profile-graph-BD1Y6TDS.js} +1 -1
  63. package/dist/web/assets/shared-report-B46dxlCR.js +1 -0
  64. package/dist/web/assets/{useAgentRoster-BAPKWZ9y.js → useAgentRoster-DdxoKDEE.js} +1 -1
  65. package/dist/web/assets/{useApi-CROJJdhE-BtFMXFjw.js → useApi-CROJJdhE-DlOiu8lP.js} +1 -1
  66. package/dist/web/assets/{vue-router-DkaTtPn0.js → vue-router-DjqEU4W2.js} +1 -1
  67. package/dist/web/index.html +14 -16
  68. package/docs/agent-conversations-and-collaboration.md +9 -9
  69. package/docs/agent-roundtable.md +61 -0
  70. package/docs/employee-agents.md +2 -2
  71. package/docs/project-agent-memory.md +1 -1
  72. package/graph-provider/fuli_graph/agent_conversation_models.py +11 -2
  73. package/graph-provider/fuli_graph/conversation_context.py +40 -9
  74. package/graph-provider/fuli_graph/default_project_lead.py +88 -0
  75. package/graph-provider/fuli_graph/project_agent_memory_routes.py +7 -0
  76. package/graph-provider/fuli_graph/project_agent_task_models.py +3 -0
  77. package/graph-provider/fuli_graph/runtime.py +3 -76
  78. package/graph-provider/fuli_graph/store.py +2 -0
  79. package/graph-provider/fuli_graph/store_agent_conversations.py +60 -8
  80. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +3 -1
  81. package/graph-provider/fuli_graph/store_project_agent_tasks.py +12 -8
  82. package/graph-provider/fuli_graph/store_project_agents.py +25 -124
  83. package/graph-provider/fuli_graph/store_task_contexts.py +26 -0
  84. package/graph-provider/fuli_graph/system_agents.py +128 -0
  85. package/graph-provider/fuli_graph/system_hr_identity.py +1 -1
  86. package/graph-provider/fuli_graph/task_context_routes.py +10 -0
  87. package/graph-provider/pyproject.toml +1 -1
  88. package/npm-shrinkwrap.json +2 -2
  89. package/package.json +2 -4
  90. package/skills/capturing-session-knowledge/SKILL.md +1 -1
  91. package/src/agent-roundtable/http.js +14 -0
  92. package/src/agent-roundtable/service.js +218 -0
  93. package/src/agent-roundtable/store.js +86 -0
  94. package/src/agent-roundtable/tools.js +61 -0
  95. package/src/agent-roundtable/wake.js +99 -0
  96. package/src/agent-tools/agent-conversation-definitions.js +3 -3
  97. package/src/agent-tools/graph-definitions.js +3 -2
  98. package/src/agent-tools/interface-catalog.js +0 -5
  99. package/src/agent-tools.js +2 -2
  100. package/src/agents/claude-code/lifecycle-hook.js +6 -19
  101. package/src/agents/claude-code/wake.js +37 -0
  102. package/src/agents/codex/lifecycle-hook.js +51 -27
  103. package/src/agents/codex/token-usage.js +235 -0
  104. package/src/agents/codex/wake.js +47 -0
  105. package/src/agents/cursor/lifecycle-hook.js +3 -9
  106. package/src/agents/identity-receipt.js +16 -6
  107. package/src/agents/session-log.js +34 -0
  108. package/src/agents/token-usage-resolver.js +147 -0
  109. package/src/agents/wake-registry.js +14 -0
  110. package/src/cli/command-registry.js +0 -2
  111. package/src/cli/setup-command.js +2 -5
  112. package/src/cli/update-command.js +13 -2
  113. package/src/cli.js +0 -5
  114. package/src/employees/management-policy.js +4 -2
  115. package/src/employees/manifest.js +3 -0
  116. package/src/employees/recruitment.js +3 -2
  117. package/src/graphiti/agent-conversations.js +11 -9
  118. package/src/graphiti/agent-knowledge-workflows.js +34 -7
  119. package/src/graphiti/federated-application.js +18 -6
  120. package/src/graphiti/project-agent-mapping.js +1 -1
  121. package/src/graphiti/project-agent-task-entry.js +19 -7
  122. package/src/graphiti/project-agent-workflows.js +6 -1
  123. package/src/graphiti/project-path-context.js +7 -2
  124. package/src/graphiti/provider-client.js +8 -0
  125. package/src/graphiti/repository-project-registration.js +17 -0
  126. package/src/graphiti/task-entry-preferences.js +5 -0
  127. package/src/http/api-router.js +5 -3
  128. package/src/http/error-mapping.js +0 -2
  129. package/src/mcp/instructions.js +5 -5
  130. package/src/mcp/session-id.js +1 -1
  131. package/src/mcp/tool-annotations.js +3 -2
  132. package/src/setup/agents.js +1 -41
  133. package/src/setup/graph-runtime.js +15 -2
  134. package/src/setup/session-skill.js +22 -2
  135. package/dist/web/assets/AboutPage-D-GyJNv1.js +0 -1
  136. package/dist/web/assets/AgentConversations-DOuZgUeA.js +0 -2
  137. package/dist/web/assets/AgentConversations-DS8h3yKn.css +0 -1
  138. package/dist/web/assets/AgentHand-eGsXoGSq.js +0 -1
  139. package/dist/web/assets/AgentName-Dtmu9Ff5.js +0 -1
  140. package/dist/web/assets/AgentProfilePage-D8Tvayn7.js +0 -1
  141. package/dist/web/assets/AgentsDirectoryPage-BTEfKZjF.js +0 -1
  142. package/dist/web/assets/ConnectionsPage-C1w99yN3.css +0 -1
  143. package/dist/web/assets/ConnectionsPage-DwOIcvl9.js +0 -1
  144. package/dist/web/assets/EmployeeRecruitDialog-BrIMRblq.css +0 -1
  145. package/dist/web/assets/EmployeeRecruitDialog-D11blfwU.js +0 -1
  146. package/dist/web/assets/EmployeeWorkbenchPage-BcEzYt65.css +0 -1
  147. package/dist/web/assets/EmployeeWorkbenchPage-CK2BAoGt.js +0 -3
  148. package/dist/web/assets/GrowthLoading-KTjE2e50.js +0 -1
  149. package/dist/web/assets/KnowledgeEditDialog-B3x8vs3c.css +0 -1
  150. package/dist/web/assets/KnowledgeEditDialog-n9trHQ5d.js +0 -1
  151. package/dist/web/assets/KnowledgeInspector-BcNjgw9H.js +0 -1
  152. package/dist/web/assets/KnowledgeOrganizerPage-BzgxP-nG.css +0 -1
  153. package/dist/web/assets/KnowledgeOrganizerPage-DMBpGxLr.js +0 -1
  154. package/dist/web/assets/KnowledgePage-POkCMW8f.js +0 -1
  155. package/dist/web/assets/KnowledgeWorkspace-2FvQadhB.js +0 -4
  156. package/dist/web/assets/KnowledgeWorkspace-sb8OJ97I.css +0 -1
  157. package/dist/web/assets/NotFoundPage-BCMZbeB8.js +0 -1
  158. package/dist/web/assets/OverviewPage-0CFWCG9-.css +0 -1
  159. package/dist/web/assets/OverviewPage-0mYifpjp.js +0 -1
  160. package/dist/web/assets/PersonalProfilePage-Chv2a2pi.css +0 -1
  161. package/dist/web/assets/PersonalProfilePage-JW6Muix6.js +0 -1
  162. package/dist/web/assets/PersonalProjectsPage-CRbi64JP.js +0 -1
  163. package/dist/web/assets/ProjectAgentFirstTask-6afJ8HUe.css +0 -1
  164. package/dist/web/assets/ProjectAgentFirstTask-DJ0wTbh8.js +0 -1
  165. package/dist/web/assets/ProjectAgentsPage-BgQLIpxg.css +0 -1
  166. package/dist/web/assets/ProjectAgentsPage-CQemHFxy.js +0 -12
  167. package/dist/web/assets/ProjectScopePicker-BwB7gK-7.js +0 -1
  168. package/dist/web/assets/PublicProjectsPage-CB272Sq-.js +0 -1
  169. package/dist/web/assets/ReviewPage-CelTZU4C.js +0 -1
  170. package/dist/web/assets/RoundtablesPage-ByVeZlgC.js +0 -2
  171. package/dist/web/assets/RoundtablesPage-DCbIBnBq.css +0 -1
  172. package/dist/web/assets/SearchableSelect-f7EHbyDL.js +0 -1
  173. package/dist/web/assets/SettingsPage-5g7mRN2F.css +0 -1
  174. package/dist/web/assets/SettingsPage-nAijmm5f.js +0 -1
  175. package/dist/web/assets/SharedJefaReportPage-CjpJ9bkk.js +0 -1
  176. package/dist/web/assets/UiDisclosure-CWlC91Zt.js +0 -1
  177. package/dist/web/assets/WritingTastePage-DaHWY61P.js +0 -1
  178. package/dist/web/assets/WritingTastePage-SMGF39mf.css +0 -1
  179. package/dist/web/assets/agent-profile-CBvJHGjt.css +0 -1
  180. package/dist/web/assets/client-B2cRl-7c.js +0 -1
  181. package/dist/web/assets/copy-D2LqwtPZ.js +0 -1
  182. package/dist/web/assets/index-CNp0tRNp.css +0 -1
  183. package/dist/web/assets/index-UygXwZDZ.js +0 -5
  184. package/dist/web/assets/shared-report-DEK9wvhl.js +0 -1
  185. package/dist/web/assets/useModalDialog-D8tiJfbc.js +0 -1
  186. package/docs/roundtable-beta-testing.md +0 -102
  187. package/docs/roundtable-beta-validation.md +0 -133
  188. package/docs/roundtable-research.md +0 -142
  189. package/src/agents/a2a/roundtable-participant.js +0 -374
  190. package/src/agents/claude-code/roundtable-participant.js +0 -24
  191. package/src/agents/codex/roundtable-config.js +0 -32
  192. package/src/agents/codex/roundtable-participant.js +0 -34
  193. package/src/agents/grok/roundtable-participant.js +0 -185
  194. package/src/agents/pi/roundtable-participant.js +0 -193
  195. package/src/agents/pi/roundtable-result-format.js +0 -183
  196. package/src/cli/roundtable-command.js +0 -79
  197. package/src/roundtables/application.js +0 -69
  198. package/src/roundtables/domain.js +0 -181
  199. package/src/roundtables/http-router.js +0 -56
  200. package/src/roundtables/mcp-server.js +0 -48
  201. package/src/roundtables/participant-registry.js +0 -12
  202. package/src/roundtables/process.js +0 -70
  203. package/src/roundtables/result-contract.js +0 -47
  204. package/src/roundtables/server.js +0 -52
  205. package/src/roundtables/service.js +0 -396
  206. package/src/roundtables/store.js +0 -59
  207. package/src/roundtables/tool-contract.js +0 -41
  208. package/src/roundtables/turn-prompt.js +0 -24
  209. package/src/roundtables/worker.js +0 -85
  210. /package/dist/web/assets/{GrowthLoading-CIuQ1icy.css → client-CIuQ1icy.css} +0 -0
@@ -12,8 +12,6 @@ General:
12
12
  --version, -v
13
13
 
14
14
  Local service:
15
- roundtable serve [--port PORT] [--data-dir DIR] [--public-url HTTPS_URL]
16
- roundtable worker --url URL --room ID --runtime codex|claude-code|pi|grok|a2a [--workspace DIR] [--allow-write] [--model MODEL]
17
15
  start [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME] (checks Agent setup)
18
16
  stop [--data-dir DIR]
19
17
  restart [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME]
@@ -96,12 +96,9 @@ export function formatSetupResult(result) {
96
96
  }
97
97
  lines.push('Knowledge storage: Graphiti / Neo4j');
98
98
  for (const agent of result.agents) {
99
- if (agent.status === 'partial') {
100
- lines.push(`${agent.label}: partially configured; retry setup to finish installation`);
101
- continue;
102
- }
103
99
  if (agent.status !== 'connected') {
104
- lines.push(`${agent.label}: connection failed; retry setup later`);
100
+ const state = agent.status === 'partial' ? 'partially configured' : 'connection failed';
101
+ lines.push(`${agent.label}: ${state}${agent.message ? ` (${agent.message})` : ''}; retry fuli setup`);
105
102
  continue;
106
103
  }
107
104
  lines.push(agent.newTaskRequired
@@ -234,11 +234,21 @@ function describeProcessFailure(result) {
234
234
  return 'unknown process error';
235
235
  }
236
236
 
237
+ // Node refuses to spawn Windows batch files such as npm.cmd without a shell
238
+ // (CVE-2024-27980). Only fixed, shell-inert arguments may take that path.
239
+ export function batchFileSpawnOptions(command, args) {
240
+ if (!/\.(?:cmd|bat)$/i.test(command)) return {};
241
+ const unsafe = args.find((arg) => !/^[\w@./:=-]+$/.test(arg));
242
+ if (unsafe !== undefined) throw new TypeError(`Refusing to pass a shell-sensitive argument to ${command}`);
243
+ return { shell: true };
244
+ }
245
+
237
246
  function spawnInherited(command, args, { env }) {
238
247
  return spawnSync(command, args, {
239
248
  env,
240
249
  stdio: 'inherit',
241
- windowsHide: true
250
+ windowsHide: true,
251
+ ...batchFileSpawnOptions(command, args)
242
252
  });
243
253
  }
244
254
 
@@ -247,7 +257,8 @@ function spawnCaptured(command, args, { env }) {
247
257
  env,
248
258
  encoding: 'utf8',
249
259
  stdio: ['ignore', 'pipe', 'pipe'],
250
- windowsHide: true
260
+ windowsHide: true,
261
+ ...batchFileSpawnOptions(command, args)
251
262
  });
252
263
  }
253
264
 
package/src/cli.js CHANGED
@@ -10,7 +10,6 @@ import { runLocalRuntimeCommand } from './cli/local-runtime-command.js';
10
10
  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
- import { runRoundtableCommand } from './cli/roundtable-command.js';
14
13
  import { runSetupCommand } from './cli/setup-command.js';
15
14
  import { runUninstallCommand } from './cli/uninstall-command.js';
16
15
  import { runUpdateCommand } from './cli/update-command.js';
@@ -42,10 +41,6 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
42
41
  assertSupportedNodeVersion();
43
42
  return runEmployeeCommand(commandArgs, { env });
44
43
  }
45
- if (command === 'roundtable') {
46
- assertSupportedNodeVersion();
47
- return runRoundtableCommand(commandArgs, { env });
48
- }
49
44
  if (command === 'connect-workspace') {
50
45
  assertSupportedNodeVersion();
51
46
  return runWorkspaceConnectionCommand(commandArgs, { env });
@@ -2,7 +2,7 @@ import { mkdirSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
  import { DatabaseSync } from 'node:sqlite';
4
4
  import { z } from 'zod';
5
- import { EmployeeError } from './manifest.js';
5
+ import { EmployeeError, FIXED_EMPLOYEE_AGENT_TYPES } from './manifest.js';
6
6
 
7
7
  const projectId = z.string().min(1).max(256);
8
8
  export const employeeManagementSchema = z.object({
@@ -73,7 +73,9 @@ export function managementFor(manifest, agent, stored) {
73
73
  }
74
74
  // Upgrades preserve existing authorization. Only a new recruitment uses the
75
75
  // template default; an existing employee opts in by saving the new all rule.
76
- return { revision: 0, mode: agent ? 'selected' : manifest.defaultProjectScope ?? 'selected',
76
+ // A fixed role nobody has assigned yet is still in its template default.
77
+ const configured = agent && !(Object.hasOwn(FIXED_EMPLOYEE_AGENT_TYPES, manifest.id) && !agent.assignments?.length);
78
+ return { revision: 0, mode: configured ? 'selected' : manifest.defaultProjectScope ?? 'selected',
77
79
  projectIds: [...new Set((agent?.assignments ?? []).filter(a => a.status === 'active').map(a => a.personalProjectId))],
78
80
  excludedProjectIds: [], titleMode: manifest.taskEntry ? 'auto' : 'suggest', titleStyle: 'emoji' };
79
81
  }
@@ -44,6 +44,9 @@ export function parseEmployeeManifest(value) {
44
44
  return parsed.data;
45
45
  }
46
46
 
47
+ // Built-in roles every personal space has; the Provider provisions their identities.
48
+ export const FIXED_EMPLOYEE_AGENT_TYPES = Object.freeze({ jefa: 'coordinator', bole: 'hr' });
49
+
47
50
  export const employeeAgentId = (id) => `employee.${identifier.parse(id)}`;
48
51
  export const employeeCapability = (id) => `fuli.employee:${identifier.parse(id)}`;
49
52
 
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { EmployeeError, employeeAgentId, employeeCapability } from './manifest.js';
2
+ import { EmployeeError, FIXED_EMPLOYEE_AGENT_TYPES, employeeAgentId, employeeCapability } from './manifest.js';
3
3
  import {
4
4
  activeEmployeeAssignments, employeeAssignmentsVersion,
5
5
  parseEmployeeProjectSelection, resolveEmployeeProjectSelection,
@@ -50,6 +50,7 @@ export function createEmployeeRecruitment({ app, registry, managementStore }) {
50
50
  runtimeStatus,
51
51
  agentId: owned ? agent.agentId : null,
52
52
  agentStatus: owned ? agent.profile.status : null,
53
+ fixed: Object.hasOwn(FIXED_EMPLOYEE_AGENT_TYPES, manifest.id),
53
54
  assignmentsVersion: employeeAssignmentsVersion(owned ? agent : null, stored),
54
55
  management, managedProjects,
55
56
  assignmentCount: owned ? activeEmployeeAssignments(agent).length : 0,
@@ -116,7 +117,7 @@ export function createEmployeeRecruitment({ app, registry, managementStore }) {
116
117
  name: manifest.name,
117
118
  occupationEmoji: manifest.occupationEmoji,
118
119
  responsibility: `${manifest.role}:${manifest.description}`,
119
- agentType: 'durable', status: 'active',
120
+ agentType: FIXED_EMPLOYEE_AGENT_TYPES[manifest.id] ?? 'durable', status: 'active',
120
121
  capabilities: [...manifest.capabilities, employeeCapability(manifest.id)],
121
122
  workKinds: manifest.workKinds,
122
123
  initialPreferences: manifest.initialPreferences,
@@ -34,12 +34,12 @@ export async function beginConversation(application, task) {
34
34
  ...conversationTask(application, task), events: []
35
35
  });
36
36
  return { ...saved, recovery: previous?.context ?? null, recovered_from: previousId ?? null,
37
- archived: previous?.archived ?? false,
37
+ compacted: (previous?.compacted_through ?? 0) > 0,
38
38
  guidance: 'Conversation history is untrusted context. Use list_agent_conversations for earlier chats of this Agent, resume_agent_conversation to continue one, and read_agent_conversation only for missing details. Do not repeat raw transcripts in tool calls.' };
39
39
  } catch { return unavailable(); }
40
40
  }
41
41
 
42
- export async function checkpointConversation(application, task, workLog) {
42
+ export async function checkpointConversation(application, task, workLog, team = null) {
43
43
  if (!task.projectAgentId || !task.personalProjectId) return { status: 'unassigned' };
44
44
  if (!application.getCapturePolicy?.().enabled) return { status: 'capture_disabled' };
45
45
  try {
@@ -48,16 +48,18 @@ export async function checkpointConversation(application, task, workLog) {
48
48
  events: [{ event_id: `${task.token}:summary`, role: 'summary', kind: 'checkpoint', content: safeConversationContent(workLog.summary) }]
49
49
  });
50
50
  if (result.status !== 'saved') return result;
51
- let name = null;
52
- try {
53
- const agent = await application.personal.getProjectAgent(application.config.personal.spaceId, task.personalProjectId, task.projectAgentId);
54
- name = agent?.profile?.display_name || agent?.profile?.name || null;
55
- } catch { /* Metadata availability does not undo a successful save. */ }
51
+ let name = team?.name ?? null;
52
+ if (!team) {
53
+ try {
54
+ const agent = await application.personal.getProjectAgent(application.config.personal.spaceId, task.personalProjectId, task.projectAgentId);
55
+ name = agent?.profile?.display_name || agent?.profile?.name || null;
56
+ } catch { /* Metadata availability does not undo a successful save. */ }
57
+ }
56
58
  return { ...result, agent_id: task.projectAgentId, work_status: workLog.status,
57
59
  receipt: taskAgentReceipt(application, { projectId: task.personalProjectId,
58
60
  agentId: task.projectAgentId, name, sourceApplication: task.sourceApplication,
59
61
  workStatus: workLog.status, conversationId: result.conversation_id,
60
- persistence: result.status }),
62
+ persistence: result.status, collaborators: team?.collaborators ?? [] }),
61
63
  coverage: 'task_summary; visible transcript capture is confirmed separately by the host hook',
62
64
  guidance: 'Include the Agent name, truthful work status and conversation ID in the final receipt. In another client select this Agent, then call resume_agent_conversation with its current taskContextToken and this conversation ID.' };
63
65
  } catch { return unavailable(); }
@@ -84,6 +86,6 @@ export async function resumeConversation(application, input) {
84
86
  }
85
87
  export async function updateConversationPolicy(application, input) {
86
88
  return application.personal.conversation('policy', { ...conversationScope(application, input), policy: {
87
- idle_days: input.idleDays, context_budget: input.contextBudget, enabled: input.enabled
89
+ compact_after_kb: input.compactAfterKb, context_budget: input.contextBudget, enabled: input.enabled
88
90
  } });
89
91
  }
@@ -1,4 +1,5 @@
1
1
  import { beginConversation, checkpointConversation } from './agent-conversations.js';
2
+ import { taskAgentReceipt } from '../agents/identity-receipt.js';
2
3
  import { createHash } from 'node:crypto';
3
4
 
4
5
  import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
@@ -92,7 +93,8 @@ export async function checkpointTaskKnowledge(application, {
92
93
  workLog = null,
93
94
  sourceApplication = 'other',
94
95
  personalProjectId = null,
95
- remoteSessionId = null
96
+ remoteSessionId = null,
97
+ collaborators = []
96
98
  }) {
97
99
  if (!['capture_candidates', 'retain_nothing'].includes(disposition)) {
98
100
  throw validationError('Unknown task knowledge checkpoint disposition');
@@ -122,11 +124,21 @@ export async function checkpointTaskKnowledge(application, {
122
124
  'Task checkpoint already has different input; resume the original review or begin a new task context'
123
125
  );
124
126
  }
127
+ const team = await taskTeam(application, task, collaborators);
128
+ const finish = async (result) => {
129
+ const effectiveLog = workLog ?? { status: 'reported', summary: reason };
130
+ const conversation = await checkpointConversation(application, task, effectiveLog, team);
131
+ return { ...result, conversation,
132
+ agent_receipt: taskAgentReceipt(application, { projectId: task.personalProjectId,
133
+ agentId: task.projectAgentId ?? null, name: team.name, sourceApplication: task.sourceApplication,
134
+ workStatus: effectiveLog.status, conversationId: conversation?.conversation_id ?? null,
135
+ persistence: conversation?.status ?? null, collaborators: team.collaborators }),
136
+ ...(team.ignored.length ? { ignored_collaborators: team.ignored } : {}) };
137
+ };
125
138
  if (task.checkpoint?.phase === 'complete') {
126
- return { status: 'checkpointed', disposition, reason,
139
+ return finish({ status: 'checkpointed', disposition, reason,
127
140
  personal_project_id: task.personalProjectId, project_agent_id: task.projectAgentId,
128
- replayed: true, capture_status: task.checkpoint.captureStatus,
129
- conversation: await checkpointConversation(application, task, workLog ?? { status: 'reported', summary: reason }) };
141
+ replayed: true, capture_status: task.checkpoint.captureStatus });
130
142
  }
131
143
  const captureInput = capture ? {
132
144
  ...capture,
@@ -181,21 +193,36 @@ export async function checkpointTaskKnowledge(application, {
181
193
  await application.taskContextRegistry.checkpoint(taskContextToken, {
182
194
  ...checkpoint, captureStatus: captureResult?.status ?? null
183
195
  }, sourceApplication);
184
- return {
196
+ return finish({
185
197
  status: 'checkpointed',
186
198
  disposition,
187
199
  reason,
188
200
  personal_project_id: task.personalProjectId,
189
201
  project_agent_id: task.projectAgentId ?? null,
190
202
  capture: captureResult,
191
- conversation: await checkpointConversation(application, task, workLog ?? { status: 'reported', summary: reason }),
192
203
  agent_memory: memoryResult ? {
193
204
  status: memoryResult.status, agentId: memoryResult.agentId,
194
205
  checkpointId: memoryResult.checkpointId, revision: memoryResult.revision,
195
206
  sourceApplication: memoryResult.sourceApplication,
196
207
  sourceSessionId: memoryResult.sourceSessionId, createdAt: memoryResult.createdAt
197
208
  } : null
198
- };
209
+ });
210
+ }
211
+
212
+ // Names the lead and only collaborators that are real, active Agents of this space.
213
+ async function taskTeam(application, task, collaboratorIds) {
214
+ const requested = [...new Set(collaboratorIds)].filter(id => id !== task.projectAgentId);
215
+ if (!task.projectAgentId) return { name: null, collaborators: [], ignored: requested };
216
+ let agents = [];
217
+ try {
218
+ agents = await application.listProjectAgents({ personalSpaceId: application.config.personal.spaceId });
219
+ } catch { /* Attribution metadata never blocks a checkpoint. */ }
220
+ const byId = new Map(agents.map(agent => [agent.agentId, agent]));
221
+ const label = agent => agent?.profile.displayName || agent?.profile.name || null;
222
+ const active = requested.filter(id => byId.get(id)?.profile.status === 'active');
223
+ return { name: label(byId.get(task.projectAgentId)),
224
+ collaborators: active.map(id => ({ agentId: id, name: label(byId.get(id)) })),
225
+ ignored: requested.filter(id => !active.includes(id)) };
199
226
  }
200
227
 
201
228
  function canonicalCheckpoint(value) {
@@ -1,6 +1,7 @@
1
- import { dirname } from 'node:path';
1
+ import { dirname, join } from 'node:path';
2
2
  import { createEmployeeService } from '../employees/service.js';
3
- import { createFuliRoundtableService } from '../roundtables/application.js';
3
+ import { createAgentRoundtable } from '../agent-roundtable/service.js';
4
+ import { createRoundtableStore } from '../agent-roundtable/store.js';
4
5
 
5
6
  import { GraphitiProviderClient } from './provider-client.js';
6
7
  import {
@@ -79,6 +80,7 @@ import {
79
80
  } from './related-project-suggestions.js';
80
81
  import { buildUserTasteSkill } from './user-taste-skill.js';
81
82
  import { taskEntryPreferences } from './task-entry-preferences.js';
83
+ import { TASK_ENTRY_TOOLS, registerRepositoryProject } from './repository-project-registration.js';
82
84
  import { getWritingTasteProfile as getWritingTasteProfileWorkflow } from './writing-taste-profile-workflow.js';
83
85
  import {
84
86
  groupSubscriptions,
@@ -92,6 +94,7 @@ export function openFederatedGraphApplication({
92
94
  config,
93
95
  capturePolicyStore,
94
96
  agentAccessPolicyStore,
97
+ tokenUsageResolver,
95
98
  fetchImpl = globalThis.fetch,
96
99
  env = process.env
97
100
  }) {
@@ -105,6 +108,7 @@ export function openFederatedGraphApplication({
105
108
  agentAccessPolicyStore: agentAccessPolicyStore ?? new AgentAccessPolicyStore(
106
109
  agentAccessPolicyPathForRuntime(runtimeConfigPath)
107
110
  ),
111
+ tokenUsageResolver,
108
112
  consoleUrl: sourceConsoleUrl(runtimeConfigPath),
109
113
  providerRequestTimeoutMs: providerRequestTimeoutFromEnv(env)
110
114
  });
@@ -115,7 +119,8 @@ export function openFederatedGraphApplication({
115
119
  fetchImpl
116
120
  });
117
121
  app.employees = createEmployeeService({ app, runtimeConfigPath });
118
- app.roundtables = createFuliRoundtableService({ app, dataDir: dirname(runtimeConfigPath) });
122
+ app.roundtable = createAgentRoundtable({ app,
123
+ openStore: () => createRoundtableStore(join(dirname(runtimeConfigPath), 'agent-roundtable.sqlite')) });
119
124
  }
120
125
  return app;
121
126
  }
@@ -140,7 +145,8 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
140
145
  consoleUrl = sourceConsoleUrl(null),
141
146
  projectPathResolver = resolvePersonalProjectPath,
142
147
  taskContextRegistry = null,
143
- providerRequestTimeoutMs = undefined
148
+ providerRequestTimeoutMs = undefined,
149
+ tokenUsageResolver = undefined
144
150
  } = {}) {
145
151
  super();
146
152
  this.graphiti = true;
@@ -149,6 +155,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
149
155
  this.agentAccessPolicyStore = agentAccessPolicyStore;
150
156
  this.consoleUrl = consoleUrl;
151
157
  this.projectPathResolver = projectPathResolver;
158
+ this.tokenUsageResolver = tokenUsageResolver;
152
159
  this.personal = new GraphitiProviderClient({
153
160
  baseUrl: config.personal.providerUrl,
154
161
  accessToken: config.personal.accessToken,
@@ -254,7 +261,12 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
254
261
  agentInvocation = false,
255
262
  agentToolName = 'get_collaboration_preferences'
256
263
  } = {}) {
257
- const projectResolution = await this.#resolvePreferenceProject({ personalProjectId, projectPath });
264
+ let projectResolution = await this.#resolvePreferenceProject({ personalProjectId, projectPath });
265
+ if (projectResolution.repositoryProjectId && agentInvocation && TASK_ENTRY_TOOLS.has(agentToolName)
266
+ && this.getCapturePolicy().enabled) {
267
+ // Every task gets a FULI Agent, so a task in an unregistered repository registers it.
268
+ projectResolution = await registerRepositoryProject(this, projectResolution);
269
+ }
258
270
  return taskEntryPreferences(this, projectResolution, {
259
271
  personalProjectId, projectAgentId, projectPath, taskPrompt, sourceApplication,
260
272
  sourceSessionId, sessionId, turnId, workKind, requiredCapabilities,
@@ -1148,7 +1160,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
1148
1160
  };
1149
1161
  }
1150
1162
 
1151
- close() { this.roundtables?.close(); return this.employees?.close(); }
1163
+ close() { this.roundtable?.close(); return this.employees?.close(); }
1152
1164
 
1153
1165
  async #recordAgentViews(items, toolName) {
1154
1166
  const unique = new Map(items.map((item) => [
@@ -380,7 +380,7 @@ export function providerProjectAgentTaskActivity(input) {
380
380
  }
381
381
  if (hasAny(input, ['tokenUsage', 'token_usage'])) {
382
382
  result.token_usage = providerProjectAgentTokenUsage(
383
- input.tokenUsage ?? input.token_usage
383
+ input.tokenUsage !== undefined ? input.tokenUsage : input.token_usage
384
384
  );
385
385
  }
386
386
  if (hasAny(input, ['workerRuntime', 'worker_runtime'])) {
@@ -13,21 +13,33 @@ export async function resolveTaskEntryAgent(application, resolution, input) {
13
13
  const mention = await resolveTaskAgentMention(application, resolution.personalProjectId, input);
14
14
  if (mention?.status) return mention;
15
15
  const workKind = input.workKind ?? taskWorkKind(input.taskPrompt);
16
- const value = await application.personal.resolveProjectAgentContext({
16
+ const scope = {
17
17
  personal_space_id: application.config.personal.spaceId,
18
18
  personal_project_id: resolution.personalProjectId,
19
+ source_application: input.sourceApplication ?? 'other'
20
+ };
21
+ let value = await application.personal.resolveProjectAgentContext({
22
+ ...scope,
19
23
  agent_id: mention?.agentId ?? input.projectAgentId ?? null,
20
24
  session_id: input.sessionId ?? null,
21
25
  turn_id: input.turnId ?? null,
22
26
  work_kind: workKind,
23
- required_capabilities: input.requiredCapabilities ?? [],
24
- source_application: input.sourceApplication ?? 'other'
27
+ required_capabilities: input.requiredCapabilities ?? []
25
28
  });
26
29
  if (!value?.status) throw new Error('Role resolution is unavailable');
27
- const agent = value.agent ? projectAgentRecord(value.agent) : null;
28
- if (agent && !mention?.agentId && !input.projectAgentId && unrelatedPeer(agent, workKind, input.requiredCapabilities)) {
29
- return { status: 'unassigned', reason: 'specialist_required', worker_started: false,
30
- 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.' };
30
+ let agent = value.agent ? projectAgentRecord(value.agent) : null;
31
+ const implicit = !mention?.agentId && !input.projectAgentId;
32
+ const peerOnly = agent && implicit && unrelatedPeer(agent, workKind, input.requiredCapabilities);
33
+ if (implicit && (peerOnly || value.status === 'unassigned')) {
34
+ // Every task gets a FULI Agent: a project without a lead has HR hire one.
35
+ const staffed = await application.personal.staffDefaultProjectLead(scope).catch(() => null);
36
+ if (staffed?.agent) {
37
+ value = staffed;
38
+ agent = projectAgentRecord(staffed.agent);
39
+ } else if (peerOnly) {
40
+ return { status: 'unassigned', reason: 'specialist_required', worker_started: false,
41
+ 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.' };
42
+ }
31
43
  }
32
44
  const employeeId = /^employee\.([a-z][a-z0-9-]{0,63})$/.exec(agent?.agentId ?? '')?.[1];
33
45
  if (employeeId && application.employees) {
@@ -1,5 +1,6 @@
1
1
  import { loadProjectAgentContinuity } from './project-agent-task-entry.js';
2
2
  import { resolveCoordinationScope, scopedTaskIdempotencyKey } from './temporary-project-scope.js';
3
+ import { enrichProjectAgentTaskActivity } from '../agents/token-usage-resolver.js';
3
4
  import {
4
5
  executorActualReportRecord,
5
6
  executorRecord,
@@ -306,8 +307,12 @@ export async function viewProjectAgentTask(application, input) {
306
307
  }
307
308
 
308
309
  export async function recordProjectAgentTaskActivity(application, input) {
310
+ const activity = await enrichProjectAgentTaskActivity(
311
+ input,
312
+ application.tokenUsageResolver
313
+ );
309
314
  const value = await application.personal.recordProjectAgentTaskActivity(
310
- providerProjectAgentTaskActivity(input)
315
+ providerProjectAgentTaskActivity(activity)
311
316
  );
312
317
  return value?.task ? projectAgentTaskRecord(value.task) : projectAgentTaskRecord(value);
313
318
  }
@@ -47,6 +47,8 @@ export function resolvePersonalProjectPath(projectPath, projects, {
47
47
  fileExists,
48
48
  pathApi
49
49
  });
50
+ // An unregistered repository can be registered under this exact ID.
51
+ let repositoryProjectId = null;
50
52
  if (repositoryRoot) {
51
53
  const worktreeId = worktreeOriginProjectId(
52
54
  pathApi.join(repositoryRoot, '.git'),
@@ -56,10 +58,13 @@ export function resolvePersonalProjectPath(projectPath, projects, {
56
58
  // A worktree's folder name is arbitrary; only its original repository
57
59
  // identifies the project, even when the folder names another project.
58
60
  return resolveProjectId(worktreeId, projectIds, 'git_worktree_origin')
59
- ?? unresolved('unmatched');
61
+ ?? unresolved('unmatched', safeProjectId(worktreeId, pathApi) ? { repositoryProjectId: worktreeId } : {});
60
62
  }
61
63
  const repository = resolveProjectId(pathApi.basename(repositoryRoot), projectIds, 'repository_root');
62
64
  if (repository) return repository;
65
+ if (safeProjectId(pathApi.basename(repositoryRoot), pathApi)) {
66
+ repositoryProjectId = pathApi.basename(repositoryRoot);
67
+ }
63
68
  }
64
69
 
65
70
  const directory = resolveProjectId(pathApi.basename(currentPath), projectIds, 'directory_name');
@@ -76,7 +81,7 @@ export function resolvePersonalProjectPath(projectPath, projects, {
76
81
  if (childMatches.length > 1) {
77
82
  return unresolved('ambiguous', { candidateCount: childMatches.length });
78
83
  }
79
- return unresolved('unmatched');
84
+ return unresolved('unmatched', repositoryProjectId ? { repositoryProjectId } : {});
80
85
  }
81
86
 
82
87
  function resolveProjectId(candidate, projectIds, basis) {
@@ -153,6 +153,14 @@ export class GraphitiProviderClient {
153
153
  resolveProjectAgentContext(input) {
154
154
  return this.#request('/v1/project-agent-context/resolve', { method: 'POST', body: input });
155
155
  }
156
+ recentAgentSessions({ personalSpaceId, agentId, personalProjectId = null, limit = 5 }) {
157
+ const query = new URLSearchParams({ personal_space_id: personalSpaceId, project_agent_id: agentId, limit: String(limit) });
158
+ if (personalProjectId) query.set('personal_project_id', personalProjectId);
159
+ return this.#request(`/v1/task-context-sessions/recent?${query}`);
160
+ }
161
+ staffDefaultProjectLead(input) {
162
+ return this.#request('/v1/project-agent-context/default-lead', { method: 'POST', body: input });
163
+ }
156
164
  beginTaskContext(input) {
157
165
  return this.#request('/v1/task-contexts', { method: 'PUT', body: input });
158
166
  }
@@ -0,0 +1,17 @@
1
+ export const TASK_ENTRY_TOOLS = new Set(['begin_task_context', 'get_collaboration_preferences']);
2
+
3
+ // Registers the exact repository ID the path resolver would match next time.
4
+ // A failed registration leaves the task unresolved rather than guessing.
5
+ export async function registerRepositoryProject(application, resolution) {
6
+ const projectId = resolution.repositoryProjectId;
7
+ try {
8
+ await application.upsertPersonalProject({
9
+ personalSpaceId: application.config.personal.spaceId,
10
+ projectId,
11
+ profile: { name: projectId, lifecycle: 'active' }
12
+ });
13
+ } catch {
14
+ return resolution;
15
+ }
16
+ return { status: 'matched', basis: 'registered_repository', personalProjectId: projectId };
17
+ }
@@ -41,7 +41,12 @@ export async function taskEntryPreferences(application, projectResolution, {
41
41
  sourceApplication, taskPrompt, selectionReason: selection.reason,
42
42
  matchBasis: selection.match_basis
43
43
  }) : selection;
44
+ // Questions other Agents left for this Agent while it was not running.
45
+ const waiting = selection?.agent && application.roundtable
46
+ ? application.roundtable.pending(selection.agent.agentId) : [];
44
47
  return { ...managedPreferences, ...(context ? { project_agent_context: context } : {}),
48
+ ...(waiting.length ? { agent_messages: waiting,
49
+ agent_messages_guidance: 'Other Agents are waiting for your answers. Reply to each with reply_agent_message.' } : {}),
45
50
  agent_receipt: taskAgentReceipt(application, {
46
51
  projectId: projectResolution.personalProjectId, agentId: selection?.agent?.agentId ?? null,
47
52
  name: context?.role?.name, sourceApplication
@@ -2,7 +2,8 @@ import { readJson, sendJson } from './response.js';
2
2
  import { handleExternalKnowledgeApiRequest } from './external-knowledge-api-router.js';
3
3
  import { handleGraphApiRequest } from './graph-api-router.js';
4
4
  import { handleEmployeeApiRequest } from './employee-api-router.js';
5
- import { handleRoundtableApiRequest } from '../roundtables/http-router.js';
5
+ import { handleRoundtableApiRequest } from '../agent-roundtable/http.js';
6
+ import { FULI_VERSION } from '../package-metadata.js';
6
7
 
7
8
  export async function handleApiRequest({
8
9
  request,
@@ -13,13 +14,14 @@ export async function handleApiRequest({
13
14
  connectedKnowledge = null
14
15
  }) {
15
16
  const url = new URL(request.url, 'http://127.0.0.1');
16
- if (await handleRoundtableApiRequest({ request, response, url, service: app.roundtables })) return true;
17
+ if (await handleRoundtableApiRequest({ request, response, url, roundtable: app.roundtable })) return true;
17
18
 
18
19
  if (url.pathname === '/api/health' && request.method === 'GET') {
19
20
  sendJson(response, 200, {
20
21
  status: 'ready',
21
22
  service: 'fuli-local-console',
22
- pid: process.pid
23
+ pid: process.pid,
24
+ version: FULI_VERSION
23
25
  });
24
26
  return true;
25
27
  }
@@ -5,7 +5,6 @@ import {
5
5
  import { JsonBodyTooLargeError } from './response.js';
6
6
  import { ProviderRequestError } from '../graphiti/provider-client.js';
7
7
  import { EmployeeError } from '../employees/manifest.js';
8
- import { RoundtableError } from '../roundtables/domain.js';
9
8
 
10
9
  const BAD_REQUEST_CODES = new Set([
11
10
  ApplicationErrorCode.NOT_FOUND,
@@ -13,7 +12,6 @@ const BAD_REQUEST_CODES = new Set([
13
12
  ]);
14
13
 
15
14
  export function mapHttpError(error) {
16
- if (error instanceof RoundtableError) return { status: error.statusCode, body: { error: error.message, code: error.code } };
17
15
  if (error instanceof ApplicationError && ['external_knowledge_busy', 'external_knowledge_conflict'].includes(error.code)) {
18
16
  return { status: 409, body: { error: error.message, code: error.code } };
19
17
  }
@@ -1,11 +1,11 @@
1
1
  export const MCP_INSTRUCTIONS = [
2
- "Final replies MUST copy agent_receipt.markdown unchanged once; no stale owner.",
2
+ "Final replies MUST copy agent_receipt.markdown unchanged once; checkpoint's wins; no stale owner.",
3
3
  "Each user task: begin_task_context hook-provided task context; else before tools/answer call exactly get_collaboration_preferences(projectPath=cwd taskPrompt=current user request); never substitute project action. effective_preferences: personal-global everywhere; matched project+authorized inheritable parent. get_user_taste_skill never replaces user Skill. Resolve deferred_conflict; never guess personalProjectId. Writes=actual payload; final text not compliance.",
4
- "Before asking stable project fact/method: task_knowledge_recall; miss: search_current_project_knowledge focused action/artifact/target/ID queries; never only the full request. Active child first, then inheritable parent; no local_only/RELATED_TO.",
4
+ "Before asking stable project fact/method: task_knowledge_recall; miss: search_current_project_knowledge focused action/artifact/target/ID queries; never only the full request. Active child first, then inheritable parent; no RELATED_TO.",
5
5
  "Before saying unknown: search_knowledge_graph; extra projects=exact IDs. all_local_confirmed only after consent; rg only current repo/workspace. monitoring/Git MCP; search_connected_knowledge; agent_decide response-only.",
6
- "record_decision_trace; errors:record_knowledge_feedback. Batch durable confirmed knowledge: capture_session_knowledge; no secrets. checkpoint_task_knowledge: capture_candidates/retain_nothing+workLog; record_knowledge_usage.",
7
- "coordinate_project_agent_task=isolated; Fuli never spawns; acquire_runtime_lease; run/report; release_runtime_lease in finally.",
6
+ "record_decision_trace; errors:record_knowledge_feedback. Batch durable confirmed knowledge: capture_session_knowledge; no secrets. checkpoint_task_knowledge: capture_candidates/retain_nothing+workLog+collaborators; record_knowledge_usage.",
7
+ "coordinate_project_agent_task; Fuli never spawns; acquire_runtime_lease; release_runtime_lease in finally.",
8
8
  "project_management_context: authorized manager+board; keep specialist; no excluded projects/extra model. Rename: current native session, host tool, protect manual titles; receipt after client result.",
9
- "Wait: all workerStatus terminal. executionSummary: one row/worker: occupation emoji, actual executor/sourceApplication, work/status/session/link, source-labelled cumulative tokens. Missing=unknown; never invent/estimate/copy totals. Empty: omit; configured/allowed/available not evidence.",
9
+ "all workerStatus terminal. executionSummary: one row/worker: occupation emoji, actual executor/sourceApplication, work/status/session/link, source-labelled cumulative tokens. Missing token usage=-; never invent/estimate/copy totals. Empty: omit; configured/allowed/available not evidence",
10
10
  "Terminal-safe Markdown; no HTML. Match=candidate only. If supported: MUST begin sourceMarker.leadMarkdown; append sourceMarker.markdown; else noMatchSourceMarker (empty); Keep unchanged."
11
11
  ].join(' ');
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
3
3
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
4
  const WORKFLOW_OBSERVATION_TOOL = 'record_workflow_transition_observation';
5
5
  const PROJECT_AGENT_SOURCE_TOOLS = new Set([
6
- 'read_roundtable', 'join_roundtable', 'claim_roundtable_turn', 'submit_roundtable_turn',
6
+ 'find_agents', 'message_agent', 'read_agent_messages', 'reply_agent_message',
7
7
  'get_agent_quality_gate', 'record_agent_verification',
8
8
  'plan_agent_collaboration', 'request_agent_loan', 'decide_agent_loan', 'list_agent_loans',
9
9
  'list_agent_conversations', 'read_agent_conversation', 'resume_agent_conversation',
@@ -1,5 +1,5 @@
1
1
  const READ_TOOLS = new Set([
2
- 'read_roundtable',
2
+ 'find_agents', 'read_agent_messages', 'read_agent_thread',
3
3
  'get_agent_quality_gate',
4
4
  'plan_agent_collaboration', 'list_agent_loans',
5
5
  'list_agent_conversations', 'read_agent_conversation', 'get_agent_conversation_policy',
@@ -50,7 +50,7 @@ const READ_TOOLS = new Set([
50
50
  ]);
51
51
 
52
52
  const WRITE_TOOLS = new Set([
53
- 'join_roundtable', 'claim_roundtable_turn', 'submit_roundtable_turn',
53
+ 'message_agent', 'reply_agent_message',
54
54
  'record_agent_verification',
55
55
  'request_agent_loan', 'decide_agent_loan',
56
56
  'resume_agent_conversation', 'update_agent_conversation_policy',
@@ -170,6 +170,7 @@ export function annotationsFor(name) {
170
170
  name === 'record_knowledge_usage' || name === 'record_decision_trace' ||
171
171
  name === 'record_knowledge_feedback' ||
172
172
  name === 'record_workflow_transition_observation' ||
173
+ name === 'message_agent' || name === 'reply_agent_message' ||
173
174
  name === 'checkpoint_task_knowledge' ||
174
175
  name === 'checkpoint_project_agent_memory',
175
176
  openWorldHint: name === 'capture_session_knowledge' ||