fuli-context 0.11.1 → 0.12.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 (213) hide show
  1. package/README.md +6 -5
  2. package/README.zh-CN.md +5 -4
  3. package/dist/web/assets/AboutPage-Dized8Uz.js +1 -0
  4. package/dist/web/assets/AgentConversations-Ccy41mEA.js +2 -0
  5. package/dist/web/assets/AgentHand-UzVhbKUC.js +1 -0
  6. package/dist/web/assets/AgentName-DJpvkub2.js +1 -0
  7. package/dist/web/assets/AgentProfilePage-CFViQ8kj.js +1 -0
  8. package/dist/web/assets/AgentsDirectoryPage-CBFTg_IE.js +1 -0
  9. package/dist/web/assets/AgentsDirectoryPage-Cv-vxw2C.css +1 -0
  10. package/dist/web/assets/ConnectionsPage-uXcabab_.js +1 -0
  11. package/dist/web/assets/EmployeeRecruitDialog-D8Hvhc-M.js +1 -0
  12. package/dist/web/assets/EmployeeWorkbenchPage-BQes4L1f.css +1 -0
  13. package/dist/web/assets/EmployeeWorkbenchPage-TBl_HNj6.js +3 -0
  14. package/dist/web/assets/KnowledgeEditDialog-HmiqLC-Y.js +1 -0
  15. package/dist/web/assets/KnowledgeInspector-Cj2yK4Qh.js +1 -0
  16. package/dist/web/assets/KnowledgeOrganizerPage-DT3BROZm.js +1 -0
  17. package/dist/web/assets/KnowledgeOrganizerPage-ccgscrPd.css +1 -0
  18. package/dist/web/assets/KnowledgePage-C4J4VueL.js +1 -0
  19. package/dist/web/assets/KnowledgeWorkspace-DA_dqVRG.js +4 -0
  20. package/dist/web/assets/NotFoundPage-x_Yd59mm.js +1 -0
  21. package/dist/web/assets/OverviewPage-3eZjQ26v.js +1 -0
  22. package/dist/web/assets/OverviewPage-8LBN8Shi.css +1 -0
  23. package/dist/web/assets/PersonalProfilePage-CDQk096L.js +1 -0
  24. package/dist/web/assets/PersonalProjectsPage-CJxp4Wyz.js +1 -0
  25. package/dist/web/assets/ProjectAgentFirstTask-BkRJ0zwO.js +1 -0
  26. package/dist/web/assets/ProjectAgentsPage-BuNNqSEy.js +12 -0
  27. package/dist/web/assets/ProjectScopePicker-CiDxM2Yx.js +1 -0
  28. package/dist/web/assets/PublicProjectsPage-ByOT559r.js +1 -0
  29. package/dist/web/assets/ReviewPage-C0Vdr9uq.js +1 -0
  30. package/dist/web/assets/RoundtablesPage-Drou-B81.css +1 -0
  31. package/dist/web/assets/RoundtablesPage-L3ShEWS3.js +1 -0
  32. package/dist/web/assets/SettingsPage-BE8pWbI6.css +1 -0
  33. package/dist/web/assets/SettingsPage-McYuinGz.js +1 -0
  34. package/dist/web/assets/SharedJefaReportPage-Bg6lIk3E.js +1 -0
  35. package/dist/web/assets/UiButton-x8hiHNRM.js +1 -0
  36. package/dist/web/assets/UiDialog-mjletK0y.js +1 -0
  37. package/dist/web/assets/UiDisclosure-_IxeiexW.js +1 -0
  38. package/dist/web/assets/UiSegmented-WFfz9Evl.js +1 -0
  39. package/dist/web/assets/UiSelect-K29jnV-1.js +1 -0
  40. package/dist/web/assets/WritingTastePage-DCULB-d2.js +1 -0
  41. package/dist/web/assets/agent-pins-DS4mNsNt.js +1 -0
  42. package/dist/web/assets/agent-profile-Bh3MJGvb.css +1 -0
  43. package/dist/web/assets/agent-work-summary-C-yW7x7A.js +1 -0
  44. package/dist/web/assets/{attention-store-D-qIFRIb.js → attention-store-CA5y8j1R.js} +1 -1
  45. package/dist/web/assets/catalog-D0VEfOAV.js +1 -0
  46. package/dist/web/assets/client-DnwnUBQV.js +1 -0
  47. package/dist/web/assets/console-Cw7TzOSC.js +1 -0
  48. package/dist/web/assets/conversation-launcher-settings-Yc13viwr.js +1 -0
  49. package/dist/web/assets/{i18n-b14l2pQA.js → i18n-oJFlt8hW.js} +3 -3
  50. package/dist/web/assets/{identity-BWbPmZZf.js → identity-DSGS4CZc.js} +1 -1
  51. package/dist/web/assets/index-BfHCcUL2.css +1 -0
  52. package/dist/web/assets/index-DO10_fLc.js +5 -0
  53. package/dist/web/assets/judgment-types-DR931cZ7.js +1 -0
  54. package/dist/web/assets/model-BJ-UF4cs.js +1 -0
  55. package/dist/web/assets/{personal-profile-graph-BPTtMJTZ.js → personal-profile-graph-SOK0BR0w.js} +1 -1
  56. package/dist/web/assets/project-team-api-C_0qJk_B.js +1 -0
  57. package/dist/web/assets/shared-report-CiX64ZiD.js +1 -0
  58. package/dist/web/assets/{agent-work-summary-LeW-Al_S.js → task-evidence-DTiPCs7r.js} +1 -1
  59. package/dist/web/assets/useAgentRoster-B84mzZIg.js +1 -0
  60. package/dist/web/assets/{useApi-CROJJdhE-CEQAEuug.js → useApi-CROJJdhE-7gt0QyQ5.js} +1 -1
  61. package/dist/web/assets/vue-router-CCvPGaLD.js +1 -0
  62. package/dist/web/index.html +17 -12
  63. package/dist/web/licenses/ant-design-icons.txt +22 -0
  64. package/docs/agent-conversations-and-collaboration.md +3 -3
  65. package/docs/agent-roundtable.md +33 -0
  66. package/docs/client-connections.md +86 -0
  67. package/docs/employee-agents.md +3 -3
  68. package/docs/judgment-and-autonomy.md +33 -0
  69. package/docs/project-agent-memory.md +6 -6
  70. package/graph-provider/fuli_graph/ai_review.py +154 -0
  71. package/graph-provider/fuli_graph/common_knowledge.py +12 -0
  72. package/graph-provider/fuli_graph/default_project_lead.py +1 -1
  73. package/graph-provider/fuli_graph/graph_models.py +14 -0
  74. package/graph-provider/fuli_graph/graph_query.py +217 -0
  75. package/graph-provider/fuli_graph/knowledge_audit.py +6 -0
  76. package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +6 -0
  77. package/graph-provider/fuli_graph/knowledge_management.py +23 -0
  78. package/graph-provider/fuli_graph/knowledge_review.py +383 -4
  79. package/graph-provider/fuli_graph/knowledge_review_models.py +61 -2
  80. package/graph-provider/fuli_graph/personal_global_preferences.py +5 -1
  81. package/graph-provider/fuli_graph/preference_conflicts.py +91 -1
  82. package/graph-provider/fuli_graph/project_agent_delegations.py +70 -2
  83. package/graph-provider/fuli_graph/project_agent_memory_routes.py +18 -1
  84. package/graph-provider/fuli_graph/project_agent_remote_origins.py +154 -0
  85. package/graph-provider/fuli_graph/project_agent_task_models.py +3 -1
  86. package/graph-provider/fuli_graph/project_knowledge.py +41 -0
  87. package/graph-provider/fuli_graph/runtime.py +15 -0
  88. package/graph-provider/fuli_graph/store_agent_verification.py +12 -5
  89. package/graph-provider/fuli_graph/store_knowledge.py +98 -58
  90. package/graph-provider/fuli_graph/store_project_agent_attention.py +1 -1
  91. package/graph-provider/fuli_graph/store_project_agent_context.py +3 -2
  92. package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +1 -1
  93. package/graph-provider/fuli_graph/store_project_agent_task_activity.py +26 -0
  94. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +1 -1
  95. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +5 -5
  96. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +4 -3
  97. package/graph-provider/fuli_graph/store_project_agent_tasks.py +2 -2
  98. package/graph-provider/fuli_graph/store_project_agents.py +6 -6
  99. package/graph-provider/fuli_graph/system_agents.py +31 -2
  100. package/graph-provider/fuli_graph/system_hr_identity.py +21 -7
  101. package/graph-provider/pyproject.toml +2 -1
  102. package/npm-shrinkwrap.json +280 -5
  103. package/package.json +7 -2
  104. package/skills/operating-fuli/SKILL.md +3 -3
  105. package/src/agent-peer/coordinator-http.js +117 -0
  106. package/src/agent-peer/coordinator-service.js +258 -0
  107. package/src/agent-peer/coordinator-store.js +95 -0
  108. package/src/agent-peer/device-identity.js +70 -0
  109. package/src/agent-peer/http.js +47 -0
  110. package/src/agent-peer/node-store.js +127 -0
  111. package/src/agent-peer/origin-proof.js +56 -0
  112. package/src/agent-peer/peer-port.js +140 -0
  113. package/src/agent-peer/peer-runtime.js +298 -0
  114. package/src/agent-peer/peer-transport.js +78 -0
  115. package/src/agent-peer/protocol.js +161 -0
  116. package/src/agent-peer/runner.js +251 -0
  117. package/src/agent-roundtable/http.js +17 -3
  118. package/src/agent-roundtable/service.js +272 -43
  119. package/src/agent-roundtable/tools.js +15 -1
  120. package/src/agent-roundtable/wake.js +12 -3
  121. package/src/agent-tools/graph-definitions.js +16 -5
  122. package/src/agent-tools/graph-handlers.js +8 -0
  123. package/src/agent-tools/interface-catalog.js +16 -1
  124. package/src/agent-tools/judgment-definitions.js +12 -0
  125. package/src/agent-tools/project-agent-definitions.js +1 -1
  126. package/src/agents/claude-code/wake.js +7 -0
  127. package/src/agents/codex/wake.js +7 -0
  128. package/src/agents/identity-receipt.js +1 -1
  129. package/src/cli/command-registry.js +3 -0
  130. package/src/cli/connect-command.js +28 -0
  131. package/src/cli/mcp-command.js +99 -0
  132. package/src/cli/peer-command.js +100 -0
  133. package/src/cli/setup-agent-selection.js +2 -2
  134. package/src/cli/setup-command.js +7 -2
  135. package/src/cli.js +16 -0
  136. package/src/employees/catalog/bole.json +2 -2
  137. package/src/employees/catalog/tonborg.json +20 -0
  138. package/src/employees/manifest.js +2 -2
  139. package/src/employees/recruitment.js +20 -1
  140. package/src/graphiti/agent-collaboration.js +8 -2
  141. package/src/graphiti/federated-application.js +9 -1
  142. package/src/graphiti/knowledge-review.js +7 -3
  143. package/src/graphiti/project-agent-control-plane.js +7 -0
  144. package/src/graphiti/project-agent-mapping.js +2 -0
  145. package/src/graphiti/project-agent-task-entry.js +4 -4
  146. package/src/graphiti/project-agent-team-context.js +16 -18
  147. package/src/graphiti/project-team-view.js +82 -0
  148. package/src/graphiti/provider-client.js +9 -0
  149. package/src/graphiti/task-entry-preferences.js +6 -1
  150. package/src/http/api-router.js +12 -1
  151. package/src/http/graph-api-router.js +7 -0
  152. package/src/judgment/acceptance.js +91 -0
  153. package/src/judgment/engine.js +168 -0
  154. package/src/judgment/http.js +24 -0
  155. package/src/judgment/model.js +24 -0
  156. package/src/judgment/routing.js +96 -0
  157. package/src/judgment/scheduler.js +25 -0
  158. package/src/judgment/service.js +66 -0
  159. package/src/judgment/store.js +123 -0
  160. package/src/mcp/session-id.js +1 -0
  161. package/src/mcp/tool-annotations.js +8 -5
  162. package/src/mcp-server.js +2 -100
  163. package/src/server.js +22 -3
  164. package/src/setup/client-connection.js +16 -0
  165. package/src/system/system-service.js +5 -0
  166. package/dist/web/assets/AboutPage-cW9hDghN.js +0 -1
  167. package/dist/web/assets/AgentConversations-CGznTWuV.js +0 -2
  168. package/dist/web/assets/AgentHand-DkyF4vgk.js +0 -1
  169. package/dist/web/assets/AgentName-C1mAVr-v.js +0 -1
  170. package/dist/web/assets/AgentProfilePage-pKUR0rfj.js +0 -1
  171. package/dist/web/assets/AgentsDirectoryPage-CUlbT2hD.js +0 -1
  172. package/dist/web/assets/AgentsDirectoryPage-yRWktiin.css +0 -1
  173. package/dist/web/assets/ConnectionsPage-CuDHIJXu.js +0 -1
  174. package/dist/web/assets/EmployeeRecruitDialog-D9QfGipd.js +0 -1
  175. package/dist/web/assets/EmployeeWorkbenchPage-D-c_Jm2z.css +0 -1
  176. package/dist/web/assets/EmployeeWorkbenchPage-DAtvGney.js +0 -3
  177. package/dist/web/assets/KnowledgeEditDialog-a-5rasG0.js +0 -1
  178. package/dist/web/assets/KnowledgeInspector-BGsqjNY2.js +0 -1
  179. package/dist/web/assets/KnowledgeOrganizerPage-BndgUsHS.js +0 -1
  180. package/dist/web/assets/KnowledgeOrganizerPage-DeL7Jp34.css +0 -1
  181. package/dist/web/assets/KnowledgePage-CQweO0YJ.js +0 -1
  182. package/dist/web/assets/KnowledgeWorkspace-BtUjKTLb.js +0 -4
  183. package/dist/web/assets/NotFoundPage-BmQTTqQn.js +0 -1
  184. package/dist/web/assets/OverviewPage-By-fUzSG.css +0 -1
  185. package/dist/web/assets/OverviewPage-C73HyTsM.js +0 -1
  186. package/dist/web/assets/PersonalProfilePage-BMuOrppx.js +0 -1
  187. package/dist/web/assets/PersonalProjectsPage-Cq9_kFvs.js +0 -1
  188. package/dist/web/assets/ProjectAgentFirstTask-CaHvOjkp.js +0 -1
  189. package/dist/web/assets/ProjectAgentsPage-WVcNdzVi.js +0 -12
  190. package/dist/web/assets/ProjectScopePicker-zAgiaZKL.js +0 -1
  191. package/dist/web/assets/PublicProjectsPage-DL093f65.js +0 -1
  192. package/dist/web/assets/ReviewPage-D_NIhPx8.js +0 -1
  193. package/dist/web/assets/RoundtablesPage-Cb0R4GaM.css +0 -1
  194. package/dist/web/assets/RoundtablesPage-CdVVyZE-.js +0 -1
  195. package/dist/web/assets/SettingsPage-BmYkLul4.css +0 -1
  196. package/dist/web/assets/SettingsPage-CbLaau8Q.js +0 -1
  197. package/dist/web/assets/SharedJefaReportPage-aygd6wvb.js +0 -1
  198. package/dist/web/assets/UiDialog-Q7fsJUoO.js +0 -1
  199. package/dist/web/assets/UiDisclosure-BS-muK5j.js +0 -1
  200. package/dist/web/assets/UiSegmented-CT28bxqw.js +0 -1
  201. package/dist/web/assets/UiSelect-DcGpPQQo.js +0 -1
  202. package/dist/web/assets/WritingTastePage-CYlwAFqV.js +0 -1
  203. package/dist/web/assets/agent-profile-BZj3ezWe.css +0 -1
  204. package/dist/web/assets/catalog-UDv854JN.js +0 -1
  205. package/dist/web/assets/client-DLS5FZBI.js +0 -1
  206. package/dist/web/assets/console-CqzwSo7a.js +0 -1
  207. package/dist/web/assets/conversation-launcher-settings-DRoIz42V.js +0 -1
  208. package/dist/web/assets/index-D1OHIz4c.js +0 -5
  209. package/dist/web/assets/index-kPB0Cf8Q.css +0 -1
  210. package/dist/web/assets/model-DupZy8Qb.js +0 -1
  211. package/dist/web/assets/shared-report-BYnLhmCZ.js +0 -1
  212. package/dist/web/assets/useAgentRoster-ep7SpKv1.js +0 -1
  213. package/dist/web/assets/vue-router-B6ZjK-Br.js +0 -1
@@ -1,4 +1,4 @@
1
- """The two fixed roles every personal space has: Jefa (project manager) and Bole (HR).
1
+ """Fixed roles: Jefa (project manager), Bole (AR), and Tonborg (judgment).
2
2
 
3
3
  Jefa also coordinates task routing, replacing the earlier separate coordinator
4
4
  identity. That legacy node is retired, not deleted, so old tasks keep their
@@ -14,6 +14,9 @@ from .provider_values import now_utc, stable_uuid
14
14
  SYSTEM_COORDINATOR_AGENT_ID = 'employee.jefa'
15
15
  LEGACY_COORDINATOR_AGENT_ID = 'fuli-project-coordinator'
16
16
  SYSTEM_HR_AGENT_ID = 'employee.bole'
17
+ SYSTEM_JUDGMENT_AGENT_ID = 'employee.tonborg'
18
+ # Upgrade only the former built-in copy; custom responsibilities are user data.
19
+ LEGACY_HR_RESPONSIBILITY = 'HR:维护 Agent 人员分布与工作状态,按需招募并保留招募记录。'
17
20
 
18
21
 
19
22
  def coordinator_profile() -> ProjectAgentProfile:
@@ -36,7 +39,7 @@ def hr_profile() -> ProjectAgentProfile:
36
39
  return ProjectAgentProfile(
37
40
  name='Bole',
38
41
  occupation_emoji='🔎',
39
- responsibility='HR:维护 Agent 人员分布与工作状态,按需招募并保留招募记录。',
42
+ responsibility='AR:维护 Agent 人员分布与工作状态,按需招募并保留招募记录。',
40
43
  agent_type='hr',
41
44
  work_kinds=['agent-recruitment', 'staffing-review'],
42
45
  capabilities=['Agent 招募', '人员分布', '工作状态', '招募审计', 'fuli.employee:bole'],
@@ -48,9 +51,27 @@ def hr_profile() -> ProjectAgentProfile:
48
51
  )
49
52
 
50
53
 
54
+ def judgment_profile() -> ProjectAgentProfile:
55
+ return ProjectAgentProfile(
56
+ name='Tonborg',
57
+ occupation_emoji='⚖️',
58
+ responsibility='判断官:依照用户偏好审核日常事项、建议客户端和模型,保留判断依据与反馈。',
59
+ agent_type='durable',
60
+ work_kinds=['judgment', 'knowledge-review', 'executor-selection'],
61
+ capabilities=['判断审核', '执行器选择', '判断反馈', 'fuli.employee:tonborg'],
62
+ initial_preferences=[
63
+ '用户明确偏好优先;只把相关范围的历史反馈用于当前判断。',
64
+ '不把模型建议当作已执行,不把 AI 审核冒充用户确认。',
65
+ '保持项目总负责人与汇报关系。',
66
+ ],
67
+ status='active',
68
+ )
69
+
70
+
51
71
  SYSTEM_PROFILES = {
52
72
  SYSTEM_COORDINATOR_AGENT_ID: coordinator_profile,
53
73
  SYSTEM_HR_AGENT_ID: hr_profile,
74
+ SYSTEM_JUDGMENT_AGENT_ID: judgment_profile,
54
75
  }
55
76
 
56
77
 
@@ -67,8 +88,13 @@ async def ensure_system_identity(driver, provider_id, space_id, agent_id):
67
88
  id=node_id,
68
89
  )
69
90
  profile = default
91
+ upgrade_responsibility = False
70
92
  if rows and rows[0]['profile_json']:
71
93
  stored = json.loads(rows[0]['profile_json'])
94
+ upgrade_responsibility = (agent_id == SYSTEM_HR_AGENT_ID
95
+ and stored.get('responsibility') == LEGACY_HR_RESPONSIBILITY)
96
+ if upgrade_responsibility:
97
+ stored['responsibility'] = default.responsibility
72
98
  profile = ProjectAgentProfile.model_validate(
73
99
  {**stored, 'agent_type': default.agent_type, 'status': 'active'}
74
100
  )
@@ -88,6 +114,8 @@ async def ensure_system_identity(driver, provider_id, space_id, agent_id):
88
114
  agent.created_at = $now,
89
115
  agent.updated_at = $now
90
116
  SET agent.profile_json = $profile_json,
117
+ agent.responsibility = CASE WHEN $upgrade_responsibility
118
+ THEN $responsibility ELSE agent.responsibility END,
91
119
  agent.agent_type = $agent_type,
92
120
  agent.status = 'active',
93
121
  agent.system_managed = true
@@ -100,6 +128,7 @@ async def ensure_system_identity(driver, provider_id, space_id, agent_id):
100
128
  name=profile.name,
101
129
  occupation_emoji=profile.occupation_emoji,
102
130
  responsibility=profile.responsibility,
131
+ upgrade_responsibility=upgrade_responsibility,
103
132
  capabilities=profile.capabilities,
104
133
  work_kinds=profile.work_kinds,
105
134
  agent_type=profile.agent_type,
@@ -1,4 +1,4 @@
1
- """Lossless, exact-ID upgrade of the original HR into the built-in Bole.
1
+ """Lossless, exact-ID upgrade of the original AR into the built-in Bole.
2
2
 
3
3
  The old node and its original edges remain an audit source, not another active
4
4
  Agent. Only this reserved legacy ID is eligible; names never establish identity.
@@ -103,7 +103,7 @@ async def merge_legacy_hr(store, space_id):
103
103
  for edge in edges:
104
104
  kind = edge['kind']
105
105
  if not re.fullmatch(r'[A-Z][A-Z0-9_]*', kind):
106
- raise HTTPException(409, 'Unsupported HR relationship; no data was changed')
106
+ raise HTTPException(409, 'Unsupported AR relationship; no data was changed')
107
107
  start_id = row['canonical_element'] if edge['start_id'] == row['legacy_element'] else edge['start_id']
108
108
  end_id = row['canonical_element'] if edge['end_id'] == row['legacy_element'] else edge['end_id']
109
109
  copied, _, _ = await driver.execute_query(
@@ -149,17 +149,31 @@ async def merge_legacy_hr(store, space_id):
149
149
  '''MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
150
150
  MATCH (n) WHERE n.group_id = space.group_id
151
151
  AND (n:Entity OR n:Episodic)
152
- FOREACH (_ IN CASE WHEN n.fuli_project_agent_id = $legacy_id THEN [1] ELSE [] END |
153
- SET n.fuli_project_agent_id = $canonical_id, n.hr_identity_original_agent_id = $legacy_id)
154
- FOREACH (_ IN CASE WHEN n.fuli_preference_agent_id = $legacy_id THEN [1] ELSE [] END |
155
- SET n.fuli_preference_agent_id = $canonical_id, n.hr_identity_original_agent_id = $legacy_id)''',
152
+ AND (n.fuli_project_agent_id = $legacy_id
153
+ OR n.fuli_preference_agent_id = $legacy_id)
154
+ WITH n
155
+ ORDER BY CASE WHEN n:Entity THEN 'entity:' ELSE 'episodic:' END +
156
+ coalesce(n.uuid, '')
157
+ SET n.fuli_ai_review_lock_version =
158
+ coalesce(n.fuli_ai_review_lock_version, 0) + 1,
159
+ n.fuli_project_agent_id = CASE
160
+ WHEN n.fuli_project_agent_id = $legacy_id
161
+ THEN $canonical_id ELSE n.fuli_project_agent_id END,
162
+ n.fuli_preference_agent_id = CASE
163
+ WHEN n.fuli_preference_agent_id = $legacy_id
164
+ THEN $canonical_id ELSE n.fuli_preference_agent_id END,
165
+ n.hr_identity_original_agent_id = $legacy_id''',
156
166
  **scope,
157
167
  )
158
168
  await driver.execute_query(
159
169
  '''MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
160
170
  MATCH ()-[edge:RELATES_TO]->() WHERE edge.group_id = space.group_id
161
171
  AND edge.fuli_preference_agent_id = $legacy_id
162
- SET edge.fuli_preference_agent_id = $canonical_id,
172
+ WITH edge
173
+ ORDER BY 'relationship:' + coalesce(edge.uuid, '')
174
+ SET edge.fuli_ai_review_lock_version =
175
+ coalesce(edge.fuli_ai_review_lock_version, 0) + 1,
176
+ edge.fuli_preference_agent_id = $canonical_id,
163
177
  edge.hr_identity_original_agent_id = $legacy_id''', **scope,
164
178
  )
165
179
  await driver.execute_query(
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fuli-graph-provider"
3
- version = "0.11.1"
3
+ version = "0.12.1"
4
4
  description = "Graphiti-backed personal and shared project provider for Fuli."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -8,6 +8,7 @@ dependencies = [
8
8
  "graphiti-core==0.29.2",
9
9
  "openai>=1.91,<2",
10
10
  "pydantic-settings==2.10.1",
11
+ "pynacl==1.6.2",
11
12
  "uvicorn[standard]==0.35.0",
12
13
  ]
13
14
 
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "fuli-context",
9
- "version": "0.11.1",
9
+ "version": "0.12.1",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@a2a-js/sdk": "1.3.0",
13
13
  "@hono/node-server": "2.0.12",
14
14
  "@modelcontextprotocol/sdk": "^1.31.0",
15
+ "@peculiar/x509": "2.1.0",
16
+ "reflect-metadata": "0.2.2",
15
17
  "zod": "^4.4.3"
16
18
  },
17
19
  "bin": {
@@ -683,6 +685,225 @@
683
685
  "url": "https://github.com/sponsors/Boshen"
684
686
  }
685
687
  },
688
+ "node_modules/@peculiar/asn1-asym-key": {
689
+ "version": "2.10.0",
690
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-asym-key/-/asn1-asym-key-2.10.0.tgz",
691
+ "integrity": "sha512-1R8xUTeqcUTSMLFTTCzJyFuKiq3y25EQKs9DezUetJ8qMqbJyxo3XkzykiwwT/Xzlt5/vRzS/sW4M1r+pq1FTg==",
692
+ "license": "MIT",
693
+ "dependencies": {
694
+ "@peculiar/asn1-pkcs8": "^2.10.0",
695
+ "@peculiar/asn1-schema": "^2.10.0",
696
+ "@peculiar/asn1-x509": "^2.10.0",
697
+ "asn1js": "^3.0.10",
698
+ "tslib": "^2.8.1"
699
+ },
700
+ "engines": {
701
+ "node": ">=14"
702
+ }
703
+ },
704
+ "node_modules/@peculiar/asn1-cms": {
705
+ "version": "2.10.0",
706
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.10.0.tgz",
707
+ "integrity": "sha512-CkX0H4NCIOMHOU3rh2xXZywinYZ/EnJlaOlJiGI0e5L4XjFqHf4iH30LMbXWChVppdA9ljbanjtzQFOhDNfTWg==",
708
+ "license": "MIT",
709
+ "dependencies": {
710
+ "@peculiar/asn1-schema": "^2.10.0",
711
+ "@peculiar/asn1-x509": "^2.10.0",
712
+ "@peculiar/asn1-x509-attr": "^2.10.0",
713
+ "asn1js": "^3.0.10",
714
+ "tslib": "^2.8.1"
715
+ },
716
+ "engines": {
717
+ "node": ">=14"
718
+ }
719
+ },
720
+ "node_modules/@peculiar/asn1-csr": {
721
+ "version": "2.10.0",
722
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.10.0.tgz",
723
+ "integrity": "sha512-jTPTr/9rxKM+niQLMF3jiAMb0lWHUhUHIuSOhdGCIfpB4FAQYf9SoiXOgxP8bS/68IB+bj+1OHvVnDVCQfVUZw==",
724
+ "license": "MIT",
725
+ "dependencies": {
726
+ "@peculiar/asn1-schema": "^2.10.0",
727
+ "@peculiar/asn1-x509": "^2.10.0",
728
+ "asn1js": "^3.0.10",
729
+ "tslib": "^2.8.1"
730
+ },
731
+ "engines": {
732
+ "node": ">=14"
733
+ }
734
+ },
735
+ "node_modules/@peculiar/asn1-ecc": {
736
+ "version": "2.10.0",
737
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.10.0.tgz",
738
+ "integrity": "sha512-GFd3iOjFrWX+QWH2R2dO5QSJyyRGv9CIBKtRlGlPNCvb2RvmCbBDexe91MJgV76c69d998Xsa+CvuQ98seoiPg==",
739
+ "license": "MIT",
740
+ "dependencies": {
741
+ "@peculiar/asn1-schema": "^2.10.0",
742
+ "@peculiar/asn1-x509": "^2.10.0",
743
+ "asn1js": "^3.0.10",
744
+ "tslib": "^2.8.1"
745
+ },
746
+ "engines": {
747
+ "node": ">=14"
748
+ }
749
+ },
750
+ "node_modules/@peculiar/asn1-pfx": {
751
+ "version": "2.10.0",
752
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.10.0.tgz",
753
+ "integrity": "sha512-1y3QK9ZH1IPleAMmRoJlPS10eGkAWnULETW8zDFNUK1ffqpG7zmDY+kYBMYQgwlcQZ2iitLm2z2EBP0xzRXpaA==",
754
+ "license": "MIT",
755
+ "dependencies": {
756
+ "@peculiar/asn1-cms": "^2.10.0",
757
+ "@peculiar/asn1-pkcs8": "^2.10.0",
758
+ "@peculiar/asn1-rsa": "^2.10.0",
759
+ "@peculiar/asn1-schema": "^2.10.0",
760
+ "asn1js": "^3.0.10",
761
+ "tslib": "^2.8.1"
762
+ },
763
+ "engines": {
764
+ "node": ">=14"
765
+ }
766
+ },
767
+ "node_modules/@peculiar/asn1-pkcs8": {
768
+ "version": "2.10.0",
769
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.10.0.tgz",
770
+ "integrity": "sha512-Ri+BZT9bnwqlHWmhs7lvWjvJRxKev2hA2+4EbZajgeYWCbR8hyv0znF4DhsFouOhMj2nSDV/iGJ6DMQtwITnCw==",
771
+ "license": "MIT",
772
+ "dependencies": {
773
+ "@peculiar/asn1-schema": "^2.10.0",
774
+ "@peculiar/asn1-x509": "^2.10.0",
775
+ "asn1js": "^3.0.10",
776
+ "tslib": "^2.8.1"
777
+ },
778
+ "engines": {
779
+ "node": ">=14"
780
+ }
781
+ },
782
+ "node_modules/@peculiar/asn1-pkcs9": {
783
+ "version": "2.10.0",
784
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.10.0.tgz",
785
+ "integrity": "sha512-XIXsbDQFezYk6fudczkuvawkRD4GNpISGCqfYhdkJlvcGF/RFknU+0DDJagOaJqBf+PdPzk4J9oMqDGcvfR9HQ==",
786
+ "license": "MIT",
787
+ "dependencies": {
788
+ "@peculiar/asn1-cms": "^2.10.0",
789
+ "@peculiar/asn1-pfx": "^2.10.0",
790
+ "@peculiar/asn1-pkcs8": "^2.10.0",
791
+ "@peculiar/asn1-schema": "^2.10.0",
792
+ "@peculiar/asn1-x509": "^2.10.0",
793
+ "@peculiar/asn1-x509-attr": "^2.10.0",
794
+ "asn1js": "^3.0.10",
795
+ "tslib": "^2.8.1"
796
+ },
797
+ "engines": {
798
+ "node": ">=14"
799
+ }
800
+ },
801
+ "node_modules/@peculiar/asn1-rsa": {
802
+ "version": "2.10.0",
803
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.10.0.tgz",
804
+ "integrity": "sha512-4Jvmwlh3gZAhNZ4/u7JSyLuce9hofOFZ4o3PYKAccCImGnyaT5CMym/ATgvAvqpOYFNW531oPZvJlEBhJy9VfA==",
805
+ "license": "MIT",
806
+ "dependencies": {
807
+ "@peculiar/asn1-schema": "^2.10.0",
808
+ "@peculiar/asn1-x509": "^2.10.0",
809
+ "asn1js": "^3.0.10",
810
+ "tslib": "^2.8.1"
811
+ },
812
+ "engines": {
813
+ "node": ">=14"
814
+ }
815
+ },
816
+ "node_modules/@peculiar/asn1-schema": {
817
+ "version": "2.10.0",
818
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.10.0.tgz",
819
+ "integrity": "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==",
820
+ "license": "MIT",
821
+ "dependencies": {
822
+ "@peculiar/utils": "^2.0.2",
823
+ "asn1js": "^3.0.10",
824
+ "tslib": "^2.8.1"
825
+ },
826
+ "engines": {
827
+ "node": ">=14"
828
+ }
829
+ },
830
+ "node_modules/@peculiar/asn1-x509": {
831
+ "version": "2.10.0",
832
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.10.0.tgz",
833
+ "integrity": "sha512-ucNVg8+ANveTpMN3fy9lA2alryONdXc2A4cEG2hMniWbvQt+YOZoe8BI80YYNO8FBcuDY1qbDQ4uQGQVraHxGA==",
834
+ "license": "MIT",
835
+ "dependencies": {
836
+ "@peculiar/asn1-schema": "^2.10.0",
837
+ "@peculiar/utils": "^2.0.2",
838
+ "asn1js": "^3.0.10",
839
+ "tslib": "^2.8.1"
840
+ },
841
+ "engines": {
842
+ "node": ">=14"
843
+ }
844
+ },
845
+ "node_modules/@peculiar/asn1-x509-attr": {
846
+ "version": "2.10.0",
847
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.10.0.tgz",
848
+ "integrity": "sha512-/85GtKOKmgvuSJNlaFfwGWNdRSZZ+hpF02NyM01XiCdzpaZONiBltDyfluFPvFx966CR+ZHNSG1jniwpy07oGg==",
849
+ "license": "MIT",
850
+ "dependencies": {
851
+ "@peculiar/asn1-schema": "^2.10.0",
852
+ "@peculiar/asn1-x509": "^2.10.0",
853
+ "asn1js": "^3.0.10",
854
+ "tslib": "^2.8.1"
855
+ },
856
+ "engines": {
857
+ "node": ">=14"
858
+ }
859
+ },
860
+ "node_modules/@peculiar/asn1-x509-post-quantum": {
861
+ "version": "2.10.0",
862
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-post-quantum/-/asn1-x509-post-quantum-2.10.0.tgz",
863
+ "integrity": "sha512-cXKcZ30wKbUkRK5RK4Z3W1imewXvHAB9apEAuIqJ0OOkOQCpJrwLOuN38QlsEXqOI+3gtUv8Kf3UZohUmCbf3w==",
864
+ "license": "MIT",
865
+ "dependencies": {
866
+ "@peculiar/asn1-asym-key": "^2.10.0",
867
+ "@peculiar/asn1-schema": "^2.10.0",
868
+ "@peculiar/asn1-x509": "^2.10.0",
869
+ "asn1js": "^3.0.10",
870
+ "tslib": "^2.8.1"
871
+ },
872
+ "engines": {
873
+ "node": ">=14"
874
+ }
875
+ },
876
+ "node_modules/@peculiar/utils": {
877
+ "version": "2.0.3",
878
+ "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz",
879
+ "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==",
880
+ "license": "MIT",
881
+ "dependencies": {
882
+ "tslib": "^2.8.1"
883
+ }
884
+ },
885
+ "node_modules/@peculiar/x509": {
886
+ "version": "2.1.0",
887
+ "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-2.1.0.tgz",
888
+ "integrity": "sha512-IYbg1R03CSQGWwl24kGyqrdVtixNSbRaDvBg1r5wyYjTP+VwPQkka1BzTgU5+vxiuwqg04OxdvdJ1xYYFIdUSA==",
889
+ "license": "MIT",
890
+ "dependencies": {
891
+ "@peculiar/asn1-cms": "^2.9.4",
892
+ "@peculiar/asn1-csr": "^2.9.4",
893
+ "@peculiar/asn1-ecc": "^2.9.4",
894
+ "@peculiar/asn1-pkcs9": "^2.9.4",
895
+ "@peculiar/asn1-rsa": "^2.9.4",
896
+ "@peculiar/asn1-schema": "^2.9.4",
897
+ "@peculiar/asn1-x509": "^2.9.4",
898
+ "@peculiar/asn1-x509-post-quantum": "^2.9.4",
899
+ "pvtsutils": "^1.3.6",
900
+ "tslib": "^2.8.1",
901
+ "tsyringe": "^4.10.0"
902
+ },
903
+ "engines": {
904
+ "node": ">=20.0.0"
905
+ }
906
+ },
686
907
  "node_modules/@pkgjs/parseargs": {
687
908
  "version": "0.11.0",
688
909
  "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1795,6 +2016,20 @@
1795
2016
  "url": "https://github.com/chalk/ansi-styles?sponsor=1"
1796
2017
  }
1797
2018
  },
2019
+ "node_modules/asn1js": {
2020
+ "version": "3.0.10",
2021
+ "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz",
2022
+ "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==",
2023
+ "license": "BSD-3-Clause",
2024
+ "dependencies": {
2025
+ "pvtsutils": "^1.3.6",
2026
+ "pvutils": "^1.1.5",
2027
+ "tslib": "^2.8.1"
2028
+ },
2029
+ "engines": {
2030
+ "node": ">=12.0.0"
2031
+ }
2032
+ },
1798
2033
  "node_modules/assertion-error": {
1799
2034
  "version": "2.0.1",
1800
2035
  "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -4440,6 +4675,24 @@
4440
4675
  "node": ">=6"
4441
4676
  }
4442
4677
  },
4678
+ "node_modules/pvtsutils": {
4679
+ "version": "1.3.6",
4680
+ "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
4681
+ "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
4682
+ "license": "MIT",
4683
+ "dependencies": {
4684
+ "tslib": "^2.8.1"
4685
+ }
4686
+ },
4687
+ "node_modules/pvutils": {
4688
+ "version": "1.2.0",
4689
+ "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz",
4690
+ "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==",
4691
+ "license": "MIT",
4692
+ "engines": {
4693
+ "node": ">=16.0.0"
4694
+ }
4695
+ },
4443
4696
  "node_modules/qs": {
4444
4697
  "version": "6.16.0",
4445
4698
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
@@ -4529,6 +4782,12 @@
4529
4782
  "url": "https://paulmillr.com/funding/"
4530
4783
  }
4531
4784
  },
4785
+ "node_modules/reflect-metadata": {
4786
+ "version": "0.2.2",
4787
+ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
4788
+ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
4789
+ "license": "Apache-2.0"
4790
+ },
4532
4791
  "node_modules/require-from-string": {
4533
4792
  "version": "2.0.2",
4534
4793
  "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -5065,9 +5324,25 @@
5065
5324
  "version": "2.8.1",
5066
5325
  "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
5067
5326
  "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
5068
- "dev": true,
5069
- "license": "0BSD",
5070
- "optional": true
5327
+ "license": "0BSD"
5328
+ },
5329
+ "node_modules/tsyringe": {
5330
+ "version": "4.10.0",
5331
+ "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz",
5332
+ "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==",
5333
+ "license": "MIT",
5334
+ "dependencies": {
5335
+ "tslib": "^1.9.3"
5336
+ },
5337
+ "engines": {
5338
+ "node": ">= 6.0.0"
5339
+ }
5340
+ },
5341
+ "node_modules/tsyringe/node_modules/tslib": {
5342
+ "version": "1.14.1",
5343
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
5344
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
5345
+ "license": "0BSD"
5071
5346
  },
5072
5347
  "node_modules/type-is": {
5073
5348
  "version": "2.1.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
4
4
  "description": "Local-first collaboration relationship graph for AI agents.",
5
5
  "type": "module",
6
6
  "files": [
@@ -21,7 +21,9 @@
21
21
  "graph-provider/fuli_graph/*.py",
22
22
  "graph-provider/fuli_graph/**/*.py",
23
23
  "skills/",
24
- "compose.graphiti.yml"
24
+ "compose.graphiti.yml",
25
+ "docs/judgment-and-autonomy.md",
26
+ "docs/client-connections.md"
25
27
  ],
26
28
  "bin": {
27
29
  "fl": "src/cli.js",
@@ -43,6 +45,7 @@
43
45
  "dev:web": "vite",
44
46
  "build": "run-s typecheck:web build:web",
45
47
  "build:web": "vite build",
48
+ "build:site": "node scripts/build-site.js",
46
49
  "typecheck:web": "vue-tsc --noEmit -p tsconfig.app.json",
47
50
  "test:node": "node --test",
48
51
  "test:web": "vitest run --config vitest.config.ts",
@@ -78,6 +81,8 @@
78
81
  "@a2a-js/sdk": "1.3.0",
79
82
  "@hono/node-server": "2.0.12",
80
83
  "@modelcontextprotocol/sdk": "^1.31.0",
84
+ "@peculiar/x509": "2.1.0",
85
+ "reflect-metadata": "0.2.2",
81
86
  "zod": "^4.4.3"
82
87
  },
83
88
  "devDependencies": {
@@ -35,9 +35,9 @@ Start from `list_agent_interfaces`; names and schemas can evolve. Common workflo
35
35
 
36
36
  For read-only questions, use the narrowest list/get/search tool and return source and scope information. For writes, keep the user-requested scope explicit and verify after mutation. For multi-step operations, stop on the first failed invariant rather than continuing with a partially stale plan.
37
37
 
38
- ## HR questions and finding people
38
+ ## AR questions and finding people
39
39
 
40
- When the user addresses HR or Bole to ask about organization or find someone, resolve the existing dedicated HR identity and read `list_project_agents` plus `get_project_agent_coordination_policy` for the exact personal space and relevant project. Use recorded roles, assignments and team membership; do not invent an organization or recruit a replacement HR. Match names, responsibilities and capabilities, but never guess between ambiguous or duplicate names. Link each resolved name to its personal page at `/agents/{encodedPersonalSpaceId}/{encodedAgentId}`. Answering in the HR role does not mean a worker has started; report actual execution separately.
40
+ When the user addresses AR or Bole to ask about organization or find someone, resolve the existing dedicated AR identity and read `list_project_agents` plus `get_project_agent_coordination_policy` for the exact personal space and relevant project. Use recorded roles, assignments and team membership; do not invent an organization or recruit a replacement AR. Match names, responsibilities and capabilities, but never guess between ambiguous or duplicate names. Link each resolved name to its personal page at `/agents/{encodedPersonalSpaceId}/{encodedAgentId}`. Answering in the AR role does not mean a worker has started; report actual execution separately.
41
41
 
42
42
  ## Before starting collaborators
43
43
 
@@ -60,4 +60,4 @@ State what changed, the exact project or item scope, the verification result, an
60
60
 
61
61
  Include the task's named durable owner and profile link from `agent_receipt` or the successful checkpoint's `conversation.receipt`. Include Jefa or other peers only with an actual `collaboration_receipt`, naming the tool work they performed; board tools do not imply a separate model worker ran. Keep this receipt brief and distinguish work completion, memory checkpoint and visible-transcript coverage. Do not report saved memory when capture failed or is disabled.
62
62
 
63
- For cross-client continuation, copy the supplied continuation prompt into the same project's connected client. A leading `@Name` matches exact directory names; `@{agentId}` is the stable form (the generated prompt percent-encodes unusual IDs). Duplicate names require an exact ID in the current request; an ambiguous explicit mention must not fall back to the prior session owner or memory. This is FULI task-entry matching, not registration in a client's native @ menu. Obtain a fresh task token before `resume_agent_conversation`; never copy an old token between clients. If no qualified owner exists, route staffing through HR before implementation; Jefa remains a management peer. Preserve confirmed taste/personality/preferences and Agent character/expectations in delegated context.
63
+ For cross-client continuation, copy the supplied continuation prompt into the same project's connected client. A leading `@Name` matches exact directory names; `@{agentId}` is the stable form (the generated prompt percent-encodes unusual IDs). Duplicate names require an exact ID in the current request; an ambiguous explicit mention must not fall back to the prior session owner or memory. This is FULI task-entry matching, not registration in a client's native @ menu. Obtain a fresh task token before `resume_agent_conversation`; never copy an old token between clients. If no qualified owner exists, route staffing through AR before implementation; Jefa remains a management peer. Preserve confirmed taste/personality/preferences and Agent character/expectations in delegated context.
@@ -0,0 +1,117 @@
1
+ import { createServer } from 'node:https';
2
+ import { setTimeout as delay } from 'node:timers/promises';
3
+
4
+ import { derToPem, PEER_LIMITS, PeerError, sha256Hex } from './protocol.js';
5
+
6
+ const CLAIM_POLL_MS = 400;
7
+ const SWEEP_MS = 5_000;
8
+
9
+ // The only network listener of the LAN roundtable. Every route requires a
10
+ // TLS-verified, currently paired device, except one-use pairing, which accepts
11
+ // a not-yet-trusted client certificate and authorizes it by invitation code.
12
+ export async function startCoordinatorServer({ service, identity, host, port = 0, claimWaitMs = PEER_LIMITS.claimWaitMs }) {
13
+ const secureContext = () => ({ ...identity.tlsOptions, ca: service.trustedCertificates() });
14
+ const server = createServer({
15
+ ...secureContext(), requestCert: true, rejectUnauthorized: false, minVersion: 'TLSv1.3',
16
+ requestTimeout: claimWaitMs + 15_000, headersTimeout: 10_000, maxHeaderSize: 8192,
17
+ }, (request, response) => {
18
+ handle(request, response).catch((error) => send(response, error.status ?? 500,
19
+ { error: error instanceof PeerError ? error.code : 'internal_error', message: error instanceof PeerError ? error.message : 'Request failed' }));
20
+ });
21
+ server.keepAliveTimeout = 5_000;
22
+
23
+ async function handle(request, response) {
24
+ const { pathname } = new URL(request.url, 'https://peer.invalid');
25
+ if (request.method !== 'POST' || !pathname.startsWith('/peer/1/')) throw new PeerError('not_found', 'Not found', 404);
26
+ if (request.headers['content-type']?.split(';')[0].trim() !== 'application/json') throw new PeerError('unsupported_media_type', 'JSON required', 415);
27
+ const certificate = request.socket.getPeerX509Certificate?.();
28
+ if (!certificate) throw new PeerError('certificate_required', 'A device certificate is required', 401);
29
+ const body = await readBody(request);
30
+ if (pathname === '/peer/1/pair') {
31
+ sendJson(response, service.pair({ code: body.code, name: body.name, authority: body.authority, certPem: derToPem(certificate.raw) }));
32
+ return;
33
+ }
34
+ if (!request.socket.authorized) throw new PeerError('device_not_trusted', 'This device is not paired', 403);
35
+ const nodeId = service.authenticate(sha256Hex(certificate.raw)).id;
36
+ switch (pathname) {
37
+ case '/peer/1/directory/publish': return sendJson(response, service.publishDirectory(nodeId, body));
38
+ case '/peer/1/directory': return sendJson(response, service.directory(nodeId));
39
+ case '/peer/1/messages': return sendJson(response, service.submit(nodeId, body));
40
+ case '/peer/1/claim': return sendJson(response, { claim: await longPollClaim(nodeId, request, response) });
41
+ case '/peer/1/renew': return sendJson(response, service.renew(nodeId, body));
42
+ case '/peer/1/result': return sendJson(response, service.result(nodeId, body));
43
+ case '/peer/1/status': return sendJson(response, service.status(nodeId, body));
44
+ case '/peer/1/cancel': return sendJson(response, service.cancel(nodeId, body));
45
+ case '/peer/1/leave': return sendJson(response, service.revoke(nodeId));
46
+ default: throw new PeerError('not_found', 'Not found', 404);
47
+ }
48
+ }
49
+
50
+ // Stops before claiming once the waiting runner is gone, so nothing is
51
+ // handed to a closed connection.
52
+ async function longPollClaim(nodeId, request, response) {
53
+ const deadline = Date.now() + claimWaitMs;
54
+ const gone = new AbortController();
55
+ const markClosed = () => gone.abort();
56
+ request.once('close', () => { if (!response.writableEnded) markClosed(); });
57
+ response.once('close', markClosed);
58
+ for (;;) {
59
+ if (gone.signal.aborted || request.socket.destroyed) return null;
60
+ service.authenticate(nodeId);
61
+ const claimed = service.claim(nodeId);
62
+ if (claimed || Date.now() >= deadline) return claimed;
63
+ try { await delay(CLAIM_POLL_MS, undefined, { signal: gone.signal }); } catch { return null; }
64
+ }
65
+ }
66
+
67
+ function sendJson(response, value) {
68
+ send(response, 200, value);
69
+ }
70
+
71
+ const sweeper = setInterval(() => { try { service.sweep(); } catch { /* next sweep retries */ } }, SWEEP_MS);
72
+ sweeper.unref();
73
+ await new Promise((resolve, reject) => {
74
+ server.once('error', reject);
75
+ server.listen(port, host, () => { server.off('error', reject); resolve(); });
76
+ });
77
+ return {
78
+ address: server.address(),
79
+ // Trust changes apply to new connections; existing ones are re-checked per request.
80
+ refreshTrust: () => server.setSecureContext(secureContext()),
81
+ close: () => new Promise((resolve) => {
82
+ clearInterval(sweeper);
83
+ server.close(() => resolve());
84
+ server.closeAllConnections?.();
85
+ }),
86
+ };
87
+ }
88
+
89
+ function readBody(request) {
90
+ return new Promise((resolve, reject) => {
91
+ let size = 0;
92
+ const chunks = [];
93
+ request.on('data', (chunk) => {
94
+ size += chunk.length;
95
+ if (size > PEER_LIMITS.requestBytes) {
96
+ reject(new PeerError('payload_too_large', 'Request body is too large', 413));
97
+ request.destroy();
98
+ } else chunks.push(chunk);
99
+ });
100
+ request.on('end', () => {
101
+ if (!size) return resolve({});
102
+ try {
103
+ const value = JSON.parse(Buffer.concat(chunks).toString('utf8'));
104
+ resolve(value && typeof value === 'object' && !Array.isArray(value) ? value : {});
105
+ } catch { reject(new PeerError('invalid_json', 'Invalid JSON body')); }
106
+ });
107
+ request.on('error', reject);
108
+ });
109
+ }
110
+
111
+ function send(response, status, value) {
112
+ if (response.headersSent) return;
113
+ const body = JSON.stringify(value);
114
+ response.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store',
115
+ 'content-length': Buffer.byteLength(body) });
116
+ response.end(body);
117
+ }