fuli-context 0.10.0 → 0.11.1

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 (212) hide show
  1. package/README.md +27 -30
  2. package/README.zh-CN.md +22 -26
  3. package/dist/web/assets/AboutPage-cW9hDghN.js +1 -0
  4. package/dist/web/assets/AgentConversations-CGznTWuV.js +2 -0
  5. package/dist/web/assets/AgentConversations-XqNEBY_2.css +1 -0
  6. package/dist/web/assets/AgentHand-DkyF4vgk.js +1 -0
  7. package/dist/web/assets/AgentName-C1mAVr-v.js +1 -0
  8. package/dist/web/assets/AgentProfilePage-pKUR0rfj.js +1 -0
  9. package/dist/web/assets/AgentsDirectoryPage-CUlbT2hD.js +1 -0
  10. package/dist/web/assets/AgentsDirectoryPage-yRWktiin.css +1 -0
  11. package/dist/web/assets/ConnectionsPage-CM9C-zEI.css +1 -0
  12. package/dist/web/assets/ConnectionsPage-CuDHIJXu.js +1 -0
  13. package/dist/web/assets/EmployeeRecruitDialog-CVbqCzHb.css +1 -0
  14. package/dist/web/assets/EmployeeRecruitDialog-D9QfGipd.js +1 -0
  15. package/dist/web/assets/EmployeeWorkbenchPage-D-c_Jm2z.css +1 -0
  16. package/dist/web/assets/EmployeeWorkbenchPage-DAtvGney.js +3 -0
  17. package/dist/web/assets/KnowledgeEditDialog-a-5rasG0.js +1 -0
  18. package/dist/web/assets/KnowledgeEditDialog-hT9znkoe.css +1 -0
  19. package/dist/web/assets/KnowledgeInspector-BGsqjNY2.js +1 -0
  20. package/dist/web/assets/KnowledgeOrganizerPage-BndgUsHS.js +1 -0
  21. package/dist/web/assets/KnowledgeOrganizerPage-DeL7Jp34.css +1 -0
  22. package/dist/web/assets/KnowledgePage-CQweO0YJ.js +1 -0
  23. package/dist/web/assets/KnowledgeWorkspace-BtUjKTLb.js +4 -0
  24. package/dist/web/assets/KnowledgeWorkspace-D4a7FcAb.css +1 -0
  25. package/dist/web/assets/NotFoundPage-BmQTTqQn.js +1 -0
  26. package/dist/web/assets/OverviewPage-By-fUzSG.css +1 -0
  27. package/dist/web/assets/OverviewPage-C73HyTsM.js +1 -0
  28. package/dist/web/assets/PersonalProfilePage-BMuOrppx.js +1 -0
  29. package/dist/web/assets/PersonalProfilePage-DrMDAAdy.css +1 -0
  30. package/dist/web/assets/PersonalProjectsPage-Cq9_kFvs.js +1 -0
  31. package/dist/web/assets/ProjectAgentFirstTask-Bi1ZsTC1.css +1 -0
  32. package/dist/web/assets/ProjectAgentFirstTask-CaHvOjkp.js +1 -0
  33. package/dist/web/assets/ProjectAgentsPage-DWGkgIsr.css +1 -0
  34. package/dist/web/assets/ProjectAgentsPage-WVcNdzVi.js +12 -0
  35. package/dist/web/assets/ProjectScopePicker-zAgiaZKL.js +1 -0
  36. package/dist/web/assets/PublicProjectsPage-DKamXDV9.css +1 -0
  37. package/dist/web/assets/PublicProjectsPage-DL093f65.js +1 -0
  38. package/dist/web/assets/ReviewPage-D_NIhPx8.js +1 -0
  39. package/dist/web/assets/RoundtablesPage-Cb0R4GaM.css +1 -0
  40. package/dist/web/assets/RoundtablesPage-CdVVyZE-.js +1 -0
  41. package/dist/web/assets/SettingsPage-BmYkLul4.css +1 -0
  42. package/dist/web/assets/SettingsPage-CbLaau8Q.js +1 -0
  43. package/dist/web/assets/SharedJefaReportPage-aygd6wvb.js +1 -0
  44. package/dist/web/assets/UiDialog-Q7fsJUoO.js +1 -0
  45. package/dist/web/assets/UiDisclosure-BS-muK5j.js +1 -0
  46. package/dist/web/assets/UiSegmented-CT28bxqw.js +1 -0
  47. package/dist/web/assets/UiSelect-Bv6R0yr1.css +1 -0
  48. package/dist/web/assets/UiSelect-DcGpPQQo.js +1 -0
  49. package/dist/web/assets/WritingTastePage-B_zYD1df.css +1 -0
  50. package/dist/web/assets/WritingTastePage-CYlwAFqV.js +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-D-qIFRIb.js} +1 -1
  53. package/dist/web/assets/{catalog-DN72452f.js → catalog-UDv854JN.js} +1 -1
  54. package/dist/web/assets/client-DLS5FZBI.js +1 -0
  55. package/dist/web/assets/{console-BtPqo79j.js → console-CqzwSo7a.js} +1 -1
  56. package/dist/web/assets/{conversation-launcher-settings-jkHRmWai.js → conversation-launcher-settings-DRoIz42V.js} +1 -1
  57. package/dist/web/assets/{i18n-D2CJkEYK.js → i18n-b14l2pQA.js} +3 -3
  58. package/dist/web/assets/{identity-B3zEw-1u.js → identity-BWbPmZZf.js} +1 -1
  59. package/dist/web/assets/index-D1OHIz4c.js +5 -0
  60. package/dist/web/assets/index-kPB0Cf8Q.css +1 -0
  61. package/dist/web/assets/{model-S7ehAjky.js → model-DupZy8Qb.js} +1 -1
  62. package/dist/web/assets/{personal-profile-graph--GG3Mizo.js → personal-profile-graph-BPTtMJTZ.js} +1 -1
  63. package/dist/web/assets/shared-report-BYnLhmCZ.js +1 -0
  64. package/dist/web/assets/{useAgentRoster-BAPKWZ9y.js → useAgentRoster-ep7SpKv1.js} +1 -1
  65. package/dist/web/assets/{useApi-CROJJdhE-BtFMXFjw.js → useApi-CROJJdhE-CEQAEuug.js} +1 -1
  66. package/dist/web/assets/{vue-router-DkaTtPn0.js → vue-router-B6ZjK-Br.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 +77 -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_context_models.py +5 -1
  76. package/graph-provider/fuli_graph/project_agent_delegations.py +118 -0
  77. package/graph-provider/fuli_graph/project_agent_employee_numbers.py +41 -0
  78. package/graph-provider/fuli_graph/project_agent_memory_routes.py +20 -0
  79. package/graph-provider/fuli_graph/project_agent_models.py +1 -0
  80. package/graph-provider/fuli_graph/project_agent_task_models.py +3 -0
  81. package/graph-provider/fuli_graph/project_agent_team_growth.py +2 -2
  82. package/graph-provider/fuli_graph/runtime.py +10 -76
  83. package/graph-provider/fuli_graph/store.py +2 -0
  84. package/graph-provider/fuli_graph/store_agent_conversations.py +60 -8
  85. package/graph-provider/fuli_graph/store_project_agent_context.py +52 -4
  86. package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +16 -1
  87. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +3 -1
  88. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +3 -2
  89. package/graph-provider/fuli_graph/store_project_agent_tasks.py +12 -8
  90. package/graph-provider/fuli_graph/store_project_agents.py +56 -129
  91. package/graph-provider/fuli_graph/store_task_contexts.py +26 -0
  92. package/graph-provider/fuli_graph/system_agents.py +130 -0
  93. package/graph-provider/fuli_graph/system_hr_identity.py +1 -1
  94. package/graph-provider/fuli_graph/task_context_routes.py +10 -0
  95. package/graph-provider/pyproject.toml +1 -1
  96. package/npm-shrinkwrap.json +2 -2
  97. package/package.json +2 -2
  98. package/src/agent-roundtable/http.js +14 -0
  99. package/src/agent-roundtable/service.js +247 -0
  100. package/src/agent-roundtable/store.js +86 -0
  101. package/src/agent-roundtable/tools.js +61 -0
  102. package/src/agent-roundtable/wake.js +100 -0
  103. package/src/agent-tools/agent-conversation-definitions.js +3 -3
  104. package/src/agent-tools/graph-definitions.js +3 -2
  105. package/src/agent-tools/interface-catalog.js +0 -5
  106. package/src/agent-tools.js +2 -2
  107. package/src/agents/claude-code/wake.js +37 -0
  108. package/src/agents/codex/wake.js +47 -0
  109. package/src/agents/identity-receipt.js +35 -7
  110. package/src/agents/session-log.js +34 -0
  111. package/src/agents/wake-registry.js +14 -0
  112. package/src/cli/command-registry.js +0 -2
  113. package/src/cli/setup-command.js +2 -5
  114. package/src/cli/update-command.js +13 -2
  115. package/src/cli.js +0 -5
  116. package/src/employees/management-policy.js +4 -2
  117. package/src/employees/manifest.js +3 -0
  118. package/src/employees/recruitment.js +3 -2
  119. package/src/graphiti/agent-conversations.js +11 -9
  120. package/src/graphiti/agent-knowledge-workflows.js +34 -7
  121. package/src/graphiti/federated-application.js +17 -5
  122. package/src/graphiti/project-agent-mapping.js +2 -0
  123. package/src/graphiti/project-agent-task-entry.js +38 -8
  124. package/src/graphiti/project-agent-team-context.js +4 -1
  125. package/src/graphiti/project-path-context.js +7 -2
  126. package/src/graphiti/provider-client.js +17 -0
  127. package/src/graphiti/repository-project-registration.js +17 -0
  128. package/src/graphiti/task-entry-preferences.js +17 -4
  129. package/src/graphiti/task-entry-project-scope.js +67 -0
  130. package/src/http/api-router.js +5 -3
  131. package/src/http/error-mapping.js +0 -2
  132. package/src/mcp/instructions.js +5 -5
  133. package/src/mcp/session-id.js +1 -1
  134. package/src/mcp/tool-annotations.js +3 -4
  135. package/src/setup/agents.js +1 -41
  136. package/src/setup/graph-runtime.js +15 -2
  137. package/src/setup/session-skill.js +22 -2
  138. package/dist/web/assets/AboutPage-D-GyJNv1.js +0 -1
  139. package/dist/web/assets/AgentConversations-DOuZgUeA.js +0 -2
  140. package/dist/web/assets/AgentConversations-DS8h3yKn.css +0 -1
  141. package/dist/web/assets/AgentHand-eGsXoGSq.js +0 -1
  142. package/dist/web/assets/AgentName-Dtmu9Ff5.js +0 -1
  143. package/dist/web/assets/AgentProfilePage-D8Tvayn7.js +0 -1
  144. package/dist/web/assets/AgentsDirectoryPage-BTEfKZjF.js +0 -1
  145. package/dist/web/assets/ConnectionsPage-C1w99yN3.css +0 -1
  146. package/dist/web/assets/ConnectionsPage-DwOIcvl9.js +0 -1
  147. package/dist/web/assets/EmployeeRecruitDialog-BrIMRblq.css +0 -1
  148. package/dist/web/assets/EmployeeRecruitDialog-D11blfwU.js +0 -1
  149. package/dist/web/assets/EmployeeWorkbenchPage-BcEzYt65.css +0 -1
  150. package/dist/web/assets/EmployeeWorkbenchPage-CK2BAoGt.js +0 -3
  151. package/dist/web/assets/GrowthLoading-KTjE2e50.js +0 -1
  152. package/dist/web/assets/KnowledgeEditDialog-B3x8vs3c.css +0 -1
  153. package/dist/web/assets/KnowledgeEditDialog-n9trHQ5d.js +0 -1
  154. package/dist/web/assets/KnowledgeInspector-BcNjgw9H.js +0 -1
  155. package/dist/web/assets/KnowledgeOrganizerPage-BzgxP-nG.css +0 -1
  156. package/dist/web/assets/KnowledgeOrganizerPage-DMBpGxLr.js +0 -1
  157. package/dist/web/assets/KnowledgePage-POkCMW8f.js +0 -1
  158. package/dist/web/assets/KnowledgeWorkspace-2FvQadhB.js +0 -4
  159. package/dist/web/assets/KnowledgeWorkspace-sb8OJ97I.css +0 -1
  160. package/dist/web/assets/NotFoundPage-BCMZbeB8.js +0 -1
  161. package/dist/web/assets/OverviewPage-0CFWCG9-.css +0 -1
  162. package/dist/web/assets/OverviewPage-0mYifpjp.js +0 -1
  163. package/dist/web/assets/PersonalProfilePage-Chv2a2pi.css +0 -1
  164. package/dist/web/assets/PersonalProfilePage-JW6Muix6.js +0 -1
  165. package/dist/web/assets/PersonalProjectsPage-CRbi64JP.js +0 -1
  166. package/dist/web/assets/ProjectAgentFirstTask-6afJ8HUe.css +0 -1
  167. package/dist/web/assets/ProjectAgentFirstTask-DJ0wTbh8.js +0 -1
  168. package/dist/web/assets/ProjectAgentsPage-CPgKVLgg.js +0 -12
  169. package/dist/web/assets/ProjectAgentsPage-DeuRkMp1.css +0 -1
  170. package/dist/web/assets/ProjectScopePicker-BwB7gK-7.js +0 -1
  171. package/dist/web/assets/PublicProjectsPage-CB272Sq-.js +0 -1
  172. package/dist/web/assets/ReviewPage-CelTZU4C.js +0 -1
  173. package/dist/web/assets/RoundtablesPage-C9dXfWCl.js +0 -2
  174. package/dist/web/assets/RoundtablesPage-DCbIBnBq.css +0 -1
  175. package/dist/web/assets/SearchableSelect-f7EHbyDL.js +0 -1
  176. package/dist/web/assets/SettingsPage-5g7mRN2F.css +0 -1
  177. package/dist/web/assets/SettingsPage-nAijmm5f.js +0 -1
  178. package/dist/web/assets/SharedJefaReportPage-CjpJ9bkk.js +0 -1
  179. package/dist/web/assets/UiDisclosure-CWlC91Zt.js +0 -1
  180. package/dist/web/assets/WritingTastePage-DaHWY61P.js +0 -1
  181. package/dist/web/assets/WritingTastePage-SMGF39mf.css +0 -1
  182. package/dist/web/assets/agent-profile-CBvJHGjt.css +0 -1
  183. package/dist/web/assets/client-B2cRl-7c.js +0 -1
  184. package/dist/web/assets/copy-D2LqwtPZ.js +0 -1
  185. package/dist/web/assets/index-CNp0tRNp.css +0 -1
  186. package/dist/web/assets/index-ulISYH02.js +0 -5
  187. package/dist/web/assets/shared-report-DEK9wvhl.js +0 -1
  188. package/dist/web/assets/useModalDialog-D8tiJfbc.js +0 -1
  189. package/docs/roundtable.md +0 -46
  190. package/src/agents/a2a/roundtable-participant.js +0 -374
  191. package/src/agents/claude-code/roundtable-participant.js +0 -24
  192. package/src/agents/codex/roundtable-config.js +0 -32
  193. package/src/agents/codex/roundtable-participant.js +0 -34
  194. package/src/agents/grok/roundtable-participant.js +0 -185
  195. package/src/agents/pi/roundtable-participant.js +0 -193
  196. package/src/agents/pi/roundtable-result-format.js +0 -183
  197. package/src/cli/roundtable-command.js +0 -79
  198. package/src/roundtables/application.js +0 -69
  199. package/src/roundtables/domain.js +0 -181
  200. package/src/roundtables/http-router.js +0 -58
  201. package/src/roundtables/mcp-server.js +0 -55
  202. package/src/roundtables/participant-registry.js +0 -12
  203. package/src/roundtables/process.js +0 -70
  204. package/src/roundtables/profiles.js +0 -84
  205. package/src/roundtables/result-contract.js +0 -47
  206. package/src/roundtables/server.js +0 -52
  207. package/src/roundtables/service.js +0 -480
  208. package/src/roundtables/store.js +0 -63
  209. package/src/roundtables/tool-contract.js +0 -58
  210. package/src/roundtables/turn-prompt.js +0 -24
  211. package/src/roundtables/worker.js +0 -85
  212. /package/dist/web/assets/{GrowthLoading-CIuQ1icy.css → client-CIuQ1icy.css} +0 -0
@@ -5,6 +5,9 @@ from fastapi import HTTPException
5
5
 
6
6
  from .personal_project_access import authorize_personal_project
7
7
  from .project_agent_access import authorize_project_agent
8
+ from .project_agent_employee_numbers import (
9
+ ASSIGN_EMPLOYEE_NUMBERS, EMPLOYEE_NUMBER_LOCK, employee_number_label,
10
+ )
8
11
  from .project_agent_models import (
9
12
  ProjectAgentAssignmentCreate,
10
13
  ProjectAgentAssignmentEnd,
@@ -20,146 +23,42 @@ from .project_agent_models import (
20
23
  from .provider_values import native_datetime, now_utc, stable_uuid
21
24
  from .project_agent_names import agent_display_name
22
25
  from .store_transactions import query_store_transaction
26
+ from .system_agents import (
27
+ SYSTEM_COORDINATOR_AGENT_ID,
28
+ SYSTEM_HR_AGENT_ID,
29
+ ensure_system_agents,
30
+ )
23
31
  from .system_hr_identity import LEGACY_HR_AGENT_ID, merge_legacy_hr, resolve_hr_alias
24
32
 
25
33
 
26
- SYSTEM_COORDINATOR_AGENT_ID = 'fuli-project-coordinator'
27
- SYSTEM_HR_AGENT_ID = 'employee.bole'
28
-
29
-
30
34
  class StoreProjectAgents:
31
35
  async def ensure_system_project_hr(
32
36
  self,
33
37
  actor: dict,
34
38
  personal_space_id: str,
35
39
  ) -> ProjectAgentRecord:
36
- self._require_personal()
37
- await self.authorize(actor, personal_space_id, 'maintainer')
38
- async with query_store_transaction(self) as scoped:
39
- await scoped._ensure_system_project_hr_identity(personal_space_id)
40
- await merge_legacy_hr(scoped, personal_space_id)
40
+ await self._ensure_system_agents(actor, personal_space_id)
41
41
  return await self.get_project_agent(actor, personal_space_id, None, SYSTEM_HR_AGENT_ID)
42
42
 
43
- async def _ensure_system_project_hr_identity(self, personal_space_id):
44
- profile = ProjectAgentProfile(
45
- name='Bole',
46
- occupation_emoji='🔎',
47
- responsibility=(
48
- '维护 Agent 人员分布、当前工作与可审计招募记录,'
49
- '并在需要新角色时执行受策略约束的招募。'
50
- ),
51
- agent_type='hr',
52
- work_kinds=['agent-recruitment', 'staffing-review'],
53
- capabilities=[
54
- 'Agent 招募', '人员分布', '工作状态', '招募审计',
55
- 'fuli.employee:bole',
56
- ],
57
- initial_preferences=[
58
- '每次招募保留任务、原因、触发来源与时间线。',
59
- '只展示 Provider 已记录的人员状态,不推测未上报的工作。',
60
- ],
61
- status='active',
62
- )
63
- updated_at = now_utc()
64
- node_id = stable_uuid(
65
- self.settings.provider_id,
66
- personal_space_id,
67
- 'project-agent',
68
- SYSTEM_HR_AGENT_ID,
69
- )
70
- await self.runtime.driver.execute_query(
71
- '''
72
- MATCH (space:FuliSpace {id: $personal_space_id, kind: 'personal'})
73
- MERGE (agent:FuliProjectAgent {id: $id})
74
- ON CREATE SET agent.agent_id = $agent_id,
75
- agent.created_at = $updated_at,
76
- agent.system_managed = true
77
- ON CREATE SET agent.profile_json = $profile_json,
78
- agent.name = $name,
79
- agent.occupation_emoji = $occupation_emoji,
80
- agent.responsibility = $responsibility,
81
- agent.capabilities = $capabilities,
82
- agent.work_kinds = $work_kinds,
83
- agent.agent_type = 'hr',
84
- agent.memory_scope = 'reviewed_agent',
85
- agent.status = 'active',
86
- agent.updated_at = $updated_at
87
- MERGE (space)-[:HAS_PROJECT_AGENT_IDENTITY]->(agent)
88
- RETURN agent
89
- ''',
90
- personal_space_id=personal_space_id,
91
- id=node_id,
92
- agent_id=SYSTEM_HR_AGENT_ID,
93
- profile_json=profile.model_dump_json(),
94
- name=profile.name,
95
- occupation_emoji=profile.occupation_emoji,
96
- responsibility=profile.responsibility,
97
- capabilities=profile.capabilities,
98
- work_kinds=profile.work_kinds,
99
- updated_at=updated_at,
100
- )
101
-
102
43
  async def ensure_system_project_coordinator(
103
44
  self,
104
45
  actor: dict,
105
46
  personal_space_id: str,
106
47
  ) -> ProjectAgentRecord:
107
- self._require_personal()
108
- await self.authorize(actor, personal_space_id, 'maintainer')
109
- profile = ProjectAgentProfile(
110
- name='项目协调人',
111
- responsibility='评估任务、选择模型策略、路由已有 Agent,并审计职责缺口。',
112
- agent_type='coordinator',
113
- work_kinds=['task-coordination'],
114
- capabilities=['任务评估', '模型策略', 'Agent 路由'],
115
- initial_preferences=['质量与可验收完成优先于成本和时间'],
116
- status='active',
117
- )
118
- updated_at = now_utc()
119
- node_id = stable_uuid(
120
- self.settings.provider_id,
121
- personal_space_id,
122
- 'project-agent',
123
- SYSTEM_COORDINATOR_AGENT_ID,
124
- )
125
- await self.runtime.driver.execute_query(
126
- '''
127
- MATCH (space:FuliSpace {id: $personal_space_id, kind: 'personal'})
128
- MERGE (agent:FuliProjectAgent {id: $id})
129
- ON CREATE SET agent.agent_id = $agent_id,
130
- agent.created_at = $updated_at,
131
- agent.system_managed = true
132
- ON CREATE SET agent.profile_json = $profile_json,
133
- agent.name = $name,
134
- agent.occupation_emoji = $occupation_emoji,
135
- agent.responsibility = $responsibility,
136
- agent.capabilities = $capabilities,
137
- agent.work_kinds = $work_kinds,
138
- agent.agent_type = 'coordinator',
139
- agent.memory_scope = 'reviewed_agent',
140
- agent.status = 'active',
141
- agent.updated_at = $updated_at
142
- MERGE (space)-[:HAS_PROJECT_AGENT_IDENTITY]->(agent)
143
- RETURN agent
144
- ''',
145
- personal_space_id=personal_space_id,
146
- id=node_id,
147
- agent_id=SYSTEM_COORDINATOR_AGENT_ID,
148
- profile_json=profile.model_dump_json(),
149
- name=profile.name,
150
- occupation_emoji=profile.occupation_emoji,
151
- responsibility=profile.responsibility,
152
- capabilities=profile.capabilities,
153
- work_kinds=profile.work_kinds,
154
- updated_at=updated_at,
155
- )
48
+ await self._ensure_system_agents(actor, personal_space_id)
156
49
  return await self.get_project_agent(
157
- actor,
158
- personal_space_id,
159
- None,
160
- SYSTEM_COORDINATOR_AGENT_ID,
50
+ actor, personal_space_id, None, SYSTEM_COORDINATOR_AGENT_ID,
161
51
  )
162
52
 
53
+ async def _ensure_system_agents(self, actor, personal_space_id):
54
+ self._require_personal()
55
+ await self.authorize(actor, personal_space_id, 'maintainer')
56
+ async with query_store_transaction(self) as scoped:
57
+ await ensure_system_agents(
58
+ scoped.runtime.driver, scoped.settings.provider_id, personal_space_id,
59
+ )
60
+ await merge_legacy_hr(scoped, personal_space_id)
61
+
163
62
  async def upsert_project_agent(
164
63
  self,
165
64
  actor: dict,
@@ -207,10 +106,13 @@ class StoreProjectAgents:
207
106
  status_code=422,
208
107
  detail='the system HR identity cannot change Agent type',
209
108
  )
210
- if request.agent_id == SYSTEM_HR_AGENT_ID and request.profile.status != 'active':
109
+ if (
110
+ request.agent_id in (SYSTEM_HR_AGENT_ID, SYSTEM_COORDINATOR_AGENT_ID)
111
+ and request.profile.status != 'active'
112
+ ):
211
113
  raise HTTPException(
212
114
  status_code=422,
213
- detail='the system HR must remain active',
115
+ detail='the project manager and HR must remain active',
214
116
  )
215
117
  space = await self.authorize(actor, request.personal_space_id, 'maintainer')
216
118
  if request.personal_project_id:
@@ -235,6 +137,7 @@ class StoreProjectAgents:
235
137
  records, _, _ = await self.runtime.driver.execute_query(
236
138
  '''
237
139
  MATCH (space:FuliSpace {id: $personal_space_id, kind: 'personal'})
140
+ ''' + EMPLOYEE_NUMBER_LOCK + '''
238
141
  MERGE (agent:FuliProjectAgent {id: $id})
239
142
  ON CREATE SET agent.agent_id = $agent_id,
240
143
  agent.created_at = $updated_at,
@@ -244,7 +147,12 @@ class StoreProjectAgents:
244
147
  SET agent._profile_upsert_lock = true
245
148
  REMOVE agent._profile_upsert_lock
246
149
  WITH space, agent
247
- WHERE agent.status IS NULL OR agent.status <> 'archived'
150
+ WHERE (agent.status IS NULL OR agent.status <> 'archived')
151
+ AND ($status = 'active' OR NOT EXISTS {
152
+ MATCH (space)-[:CONTAINS_PROJECT]->(:FuliPersonalProject)-
153
+ [:HAS_PROJECT_AGENT_COORDINATION_POLICY]->(policy:FuliProjectAgentCoordinationPolicy)
154
+ WHERE policy.team_lead_agent_id = agent.agent_id
155
+ })
248
156
  SET agent.profile_json = $profile_json,
249
157
  agent.name = $name,
250
158
  agent.occupation_emoji = $occupation_emoji,
@@ -260,6 +168,7 @@ class StoreProjectAgents:
260
168
  agent.recruitment_id, $recruitment_id),
261
169
  agent.updated_at = $updated_at
262
170
  MERGE (space)-[:HAS_PROJECT_AGENT_IDENTITY]->(agent)
171
+ ''' + ASSIGN_EMPLOYEE_NUMBERS + '''
263
172
  RETURN agent
264
173
  ''',
265
174
  personal_space_id=request.personal_space_id,
@@ -283,7 +192,7 @@ class StoreProjectAgents:
283
192
  raise HTTPException(
284
193
  status_code=409,
285
194
  detail=(
286
- 'archived Project Agents require a dedicated restore operation'
195
+ 'archived Agents require a dedicated restore operation; project leads must hand off before deactivation'
287
196
  ),
288
197
  )
289
198
  if request.personal_project_id:
@@ -488,7 +397,7 @@ class StoreProjectAgents:
488
397
  if agent_id == SYSTEM_COORDINATOR_AGENT_ID:
489
398
  raise HTTPException(
490
399
  status_code=422,
491
- detail='the system coordinator cannot be archived',
400
+ detail='the project manager cannot be archived',
492
401
  )
493
402
  if agent_id == SYSTEM_HR_AGENT_ID:
494
403
  raise HTTPException(
@@ -521,7 +430,12 @@ class StoreProjectAgents:
521
430
  'awaiting_recruitment', 'queued', 'running',
522
431
  'paused', 'blocked', 'awaiting_review'
523
432
  ]] AS open_tasks
524
- WHERE agent.status = 'archived' OR size(open_tasks) = 0
433
+ WHERE (agent.status = 'archived' OR size(open_tasks) = 0)
434
+ AND NOT EXISTS {
435
+ MATCH (space)-[:CONTAINS_PROJECT]->(:FuliPersonalProject)-
436
+ [:HAS_PROJECT_AGENT_COORDINATION_POLICY]->(policy:FuliProjectAgentCoordinationPolicy)
437
+ WHERE policy.team_lead_agent_id = agent.agent_id
438
+ }
525
439
  SET agent.status = 'archived',
526
440
  agent.archive_reason = coalesce(agent.archive_reason, $reason),
527
441
  agent.archived_at = coalesce(agent.archived_at, $updated_at),
@@ -547,7 +461,7 @@ class StoreProjectAgents:
547
461
  if not records:
548
462
  raise HTTPException(
549
463
  status_code=409,
550
- detail='Agent is missing or still has non-terminal task work',
464
+ detail='Agent is missing, has non-terminal task work, or is a project lead; hand off before archiving',
551
465
  )
552
466
  return await self.get_project_agent(
553
467
  actor,
@@ -571,6 +485,8 @@ class StoreProjectAgents:
571
485
  [:HAS_PROJECT_AGENT_IDENTITY]->(agent:FuliProjectAgent)
572
486
  WHERE ($agent_id IS NULL OR agent.agent_id = $agent_id)
573
487
  AND ($status IS NULL OR agent.status = $status)
488
+ // A retired system identity stays readable by ID for history only.
489
+ AND ($agent_id IS NOT NULL OR agent.superseded_by IS NULL)
574
490
  AND (
575
491
  $personal_project_id IS NULL
576
492
  OR EXISTS {
@@ -867,6 +783,11 @@ class StoreProjectAgents:
867
783
  WITH project, assignment, agent
868
784
  WHERE assignment.status = 'active'
869
785
  AND coalesce(assignment.revision, 0) = $expected_revision
786
+ AND NOT EXISTS {
787
+ MATCH (project)-[:HAS_PROJECT_AGENT_COORDINATION_POLICY]->
788
+ (policy:FuliProjectAgentCoordinationPolicy)
789
+ WHERE policy.team_lead_agent_id = agent.agent_id
790
+ }
870
791
  SET assignment.status = 'ended',
871
792
  assignment.end_reason = $reason,
872
793
  assignment.ended_at = $updated_at,
@@ -885,7 +806,7 @@ class StoreProjectAgents:
885
806
  if not records:
886
807
  raise HTTPException(
887
808
  status_code=409,
888
- detail='assignment is not active or revision is stale',
809
+ detail='assignment is not active, revision is stale, or project leadership requires a handoff',
889
810
  )
890
811
  row = records[0]
891
812
  return self._assignment(
@@ -950,6 +871,11 @@ class StoreProjectAgents:
950
871
  REMOVE lifecycle_agent._task_lifecycle_lock
951
872
  WITH project, ended, ended_agent, replacement_agent,
952
873
  collect(lifecycle_agent) AS locked_agents
874
+ WHERE ended.status <> 'active' OR NOT EXISTS {
875
+ MATCH (project)-[:HAS_PROJECT_AGENT_COORDINATION_POLICY]->
876
+ (policy:FuliProjectAgentCoordinationPolicy)
877
+ WHERE policy.team_lead_agent_id = ended_agent.agent_id
878
+ }
953
879
  OPTIONAL MATCH (existing_replacement:FuliProjectAgentAssignment {
954
880
  id: $replacement_id
955
881
  })
@@ -1174,6 +1100,7 @@ class StoreProjectAgents:
1174
1100
  })
1175
1101
  return ProjectAgentRecord(
1176
1102
  agent_id=raw['agent_id'],
1103
+ employee_number=employee_number_label(raw.get('employee_number')),
1177
1104
  legacy_agent_ids=list(raw.get('legacy_agent_ids') or []),
1178
1105
  personal_space_id=personal_space_id,
1179
1106
  personal_project_id=projection_project_id,
@@ -99,6 +99,32 @@ class StoreTaskContexts:
99
99
  id=self._task_session_id(space_id, source_application, session_id))
100
100
  return json.loads(rows[0]['record_json']) if rows else None
101
101
 
102
+ async def recent_agent_sessions(self, actor, space_id, agent_id, project_id, limit):
103
+ """The host conversations an Agent most recently worked in, newest first."""
104
+ self._require_personal()
105
+ await self.authorize(actor, space_id, 'reader')
106
+ rows, _, _ = await self.runtime.driver.execute_query(
107
+ '''
108
+ MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
109
+ [:HAS_TASK_CONTEXT_SESSION]->(:FuliTaskContextSession)-
110
+ [:HAS_CONTEXT]->(task:FuliTaskContext {project_agent_id: $agent_id})
111
+ WHERE $project_id IS NULL OR task.personal_project_id = $project_id
112
+ RETURN task.record_json AS record_json, task.created_at AS created_at
113
+ ORDER BY task.created_at DESC LIMIT 200
114
+ ''', space_id=space_id, agent_id=agent_id, project_id=project_id, routing_='r',
115
+ )
116
+ sessions = {}
117
+ for row in rows:
118
+ record = json.loads(row['record_json'])
119
+ key = (record['source_application'], record['session_id'])
120
+ if key not in sessions:
121
+ sessions[key] = {
122
+ 'source_application': key[0], 'session_id': key[1],
123
+ 'personal_project_id': record.get('personal_project_id'),
124
+ 'last_active': str(row['created_at']),
125
+ }
126
+ return list(sessions.values())[:limit]
127
+
102
128
  async def get_task_context(self, actor, space_id, token, source_application):
103
129
  self._require_personal()
104
130
  await self.authorize(actor, space_id, 'reader')
@@ -0,0 +1,130 @@
1
+ """The two fixed roles every personal space has: Jefa (project manager) and Bole (HR).
2
+
3
+ Jefa also coordinates task routing, replacing the earlier separate coordinator
4
+ identity. That legacy node is retired, not deleted, so old tasks keep their
5
+ original coordinator reference.
6
+ """
7
+
8
+ import json
9
+
10
+ from .project_agent_models import ProjectAgentProfile
11
+ from .project_agent_employee_numbers import ASSIGN_EMPLOYEE_NUMBERS, EMPLOYEE_NUMBER_LOCK
12
+ from .provider_values import now_utc, stable_uuid
13
+
14
+ SYSTEM_COORDINATOR_AGENT_ID = 'employee.jefa'
15
+ LEGACY_COORDINATOR_AGENT_ID = 'fuli-project-coordinator'
16
+ SYSTEM_HR_AGENT_ID = 'employee.bole'
17
+
18
+
19
+ def coordinator_profile() -> ProjectAgentProfile:
20
+ return ProjectAgentProfile(
21
+ name='Jefa',
22
+ occupation_emoji='🧭',
23
+ responsibility='项目经理:拆解目标、维护看板与进展,并为任务选择合适的 Agent。',
24
+ agent_type='coordinator',
25
+ work_kinds=['project_management', 'planning', 'review', 'task-coordination'],
26
+ capabilities=['项目规划', '任务拆解', '看板读写', 'Agent 路由', 'fuli.employee:jefa'],
27
+ initial_preferences=[
28
+ '质量与可验收完成优先于成本和时间。',
29
+ 'Agent 认为完成时只移入待确认,最终完成由用户决定。',
30
+ ],
31
+ status='active',
32
+ )
33
+
34
+
35
+ def hr_profile() -> ProjectAgentProfile:
36
+ return ProjectAgentProfile(
37
+ name='Bole',
38
+ occupation_emoji='🔎',
39
+ responsibility='HR:维护 Agent 人员分布与工作状态,按需招募并保留招募记录。',
40
+ agent_type='hr',
41
+ work_kinds=['agent-recruitment', 'staffing-review'],
42
+ capabilities=['Agent 招募', '人员分布', '工作状态', '招募审计', 'fuli.employee:bole'],
43
+ initial_preferences=[
44
+ '每次招募保留任务、原因、触发来源与时间线。',
45
+ '只展示 Provider 已记录的人员状态,不推测未上报的工作。',
46
+ ],
47
+ status='active',
48
+ )
49
+
50
+
51
+ SYSTEM_PROFILES = {
52
+ SYSTEM_COORDINATOR_AGENT_ID: coordinator_profile,
53
+ SYSTEM_HR_AGENT_ID: hr_profile,
54
+ }
55
+
56
+
57
+ async def ensure_system_identity(driver, provider_id, space_id, agent_id):
58
+ """Create the fixed role once; an existing identity keeps the user's edits.
59
+
60
+ A Jefa recruited before it became a fixed role is adopted in place, keeping
61
+ its profile, assignments, memory and history.
62
+ """
63
+ default = SYSTEM_PROFILES[agent_id]()
64
+ node_id = stable_uuid(provider_id, space_id, 'project-agent', agent_id)
65
+ rows, _, _ = await driver.execute_query(
66
+ 'MATCH (agent:FuliProjectAgent {id: $id}) RETURN agent.profile_json AS profile_json',
67
+ id=node_id,
68
+ )
69
+ profile = default
70
+ if rows and rows[0]['profile_json']:
71
+ stored = json.loads(rows[0]['profile_json'])
72
+ profile = ProjectAgentProfile.model_validate(
73
+ {**stored, 'agent_type': default.agent_type, 'status': 'active'}
74
+ )
75
+ now = now_utc()
76
+ await driver.execute_query(
77
+ '''
78
+ MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
79
+ ''' + EMPLOYEE_NUMBER_LOCK + '''
80
+ MERGE (agent:FuliProjectAgent {id: $id})
81
+ ON CREATE SET agent.agent_id = $agent_id,
82
+ agent.name = $name,
83
+ agent.occupation_emoji = $occupation_emoji,
84
+ agent.responsibility = $responsibility,
85
+ agent.capabilities = $capabilities,
86
+ agent.work_kinds = $work_kinds,
87
+ agent.memory_scope = 'reviewed_agent',
88
+ agent.created_at = $now,
89
+ agent.updated_at = $now
90
+ SET agent.profile_json = $profile_json,
91
+ agent.agent_type = $agent_type,
92
+ agent.status = 'active',
93
+ agent.system_managed = true
94
+ MERGE (space)-[:HAS_PROJECT_AGENT_IDENTITY]->(agent)
95
+ ''' + ASSIGN_EMPLOYEE_NUMBERS + ' RETURN agent',
96
+ space_id=space_id,
97
+ id=node_id,
98
+ agent_id=agent_id,
99
+ profile_json=profile.model_dump_json(),
100
+ name=profile.name,
101
+ occupation_emoji=profile.occupation_emoji,
102
+ responsibility=profile.responsibility,
103
+ capabilities=profile.capabilities,
104
+ work_kinds=profile.work_kinds,
105
+ agent_type=profile.agent_type,
106
+ now=now,
107
+ )
108
+
109
+
110
+ async def retire_legacy_coordinator(driver, space_id):
111
+ await driver.execute_query(
112
+ '''
113
+ MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-[:HAS_PROJECT_AGENT_IDENTITY]->
114
+ (agent:FuliProjectAgent {agent_id: $legacy_id})
115
+ WHERE agent.status <> 'archived'
116
+ SET agent.status = 'archived',
117
+ agent.superseded_by = $canonical_id,
118
+ agent.updated_at = $now
119
+ ''',
120
+ space_id=space_id,
121
+ legacy_id=LEGACY_COORDINATOR_AGENT_ID,
122
+ canonical_id=SYSTEM_COORDINATOR_AGENT_ID,
123
+ now=now_utc(),
124
+ )
125
+
126
+
127
+ async def ensure_system_agents(driver, provider_id, space_id):
128
+ for agent_id in SYSTEM_PROFILES:
129
+ await ensure_system_identity(driver, provider_id, space_id, agent_id)
130
+ await retire_legacy_coordinator(driver, space_id)
@@ -12,8 +12,8 @@ from fastapi import HTTPException
12
12
 
13
13
  from .project_agent_models import ProjectAgentProfile
14
14
  from .provider_values import now_utc
15
+ from .system_agents import SYSTEM_HR_AGENT_ID
15
16
 
16
- SYSTEM_HR_AGENT_ID = 'employee.bole'
17
17
  LEGACY_HR_AGENT_ID = 'fuli-project-hr'
18
18
  REFERENCE_FIELDS = (
19
19
  'agent_id', 'hr_agent_id', 'lead_agent_id', 'coordinator_agent_id',
@@ -7,6 +7,16 @@ from .task_context_models import TaskContextBegin, TaskContextCheckpoint, TaskCo
7
7
 
8
8
 
9
9
  def register_task_context_routes(application, store, Actor):
10
+ @application.get('/v1/task-context-sessions/recent')
11
+ async def recent_sessions(actor: Actor,
12
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
13
+ project_agent_id: Annotated[str, Query(min_length=1, max_length=128)],
14
+ personal_project_id: Annotated[str | None, Query(min_length=1, max_length=128)] = None,
15
+ limit: Annotated[int, Query(ge=1, le=20)] = 5):
16
+ return await store.recent_agent_sessions(
17
+ actor, personal_space_id, project_agent_id, personal_project_id, limit,
18
+ )
19
+
10
20
  @application.get('/v1/task-context-sessions/current')
11
21
  async def current_context(actor: Actor,
12
22
  personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fuli-graph-provider"
3
- version = "0.10.0"
3
+ version = "0.11.1"
4
4
  description = "Graphiti-backed personal and shared project provider for Fuli."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "fuli-context",
9
- "version": "0.10.0",
9
+ "version": "0.11.1",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@a2a-js/sdk": "1.3.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Local-first collaboration relationship graph for AI agents.",
5
5
  "type": "module",
6
6
  "files": [
@@ -14,7 +14,7 @@
14
14
  "docs/agent-conversations-and-collaboration.md",
15
15
  "docs/employee-agents.md",
16
16
  "docs/agent-interface-architecture.md",
17
- "docs/roundtable.md",
17
+ "docs/agent-roundtable.md",
18
18
  "examples/external-knowledge/markdown-folder.mjs",
19
19
  "graph-provider/Dockerfile",
20
20
  "graph-provider/pyproject.toml",
@@ -0,0 +1,14 @@
1
+ import { sendJson } from '../http/response.js';
2
+
3
+ // Read-only console view of Agent-to-Agent threads.
4
+ export async function handleRoundtableApiRequest({ request, response, url, roundtable }) {
5
+ if (!roundtable || request.method !== 'GET') return false;
6
+ if (url.pathname === '/api/roundtable/threads') {
7
+ sendJson(response, 200, roundtable.threads({ limit: Math.min(Number(url.searchParams.get('limit')) || 50, 200) }));
8
+ return true;
9
+ }
10
+ const match = /^\/api\/roundtable\/threads\/([^/]+)$/.exec(url.pathname);
11
+ if (!match) return false;
12
+ sendJson(response, 200, roundtable.thread({ threadId: decodeURIComponent(match[1]) }));
13
+ return true;
14
+ }