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
@@ -0,0 +1,12 @@
1
+ import { objectSchema, stringSchema, integerSchema, booleanSchema } from './schema.js';
2
+ const id = { ...stringSchema(), minLength: 1, maxLength: 256 };
3
+ const scope = { personalSpaceId: id, personalProjectId: { ...id, type: ['string', 'null'] } };
4
+ export const JUDGMENT_TOOL_DEFINITIONS = [
5
+ { name: 'get_judgment_policy', title: 'READ · Tonborg autonomy', description: 'Read effective autonomy and quality preferences, including project inheritance. Only the user changes autonomy in Settings. Never infer permission from a recommendation.', inputSchema: objectSchema(scope, ['personalSpaceId']) },
6
+ { name: 'list_judgments', title: 'READ · Tonborg decisions', description: 'Read scoped decisions, execution receipts and human feedback. A recommendation is not evidence of execution. Feedback is scoped evidence, not a new global instruction.', inputSchema: objectSchema({ ...scope, limit: integerSchema({ minimum: 1, maximum: 100 }) }, ['personalSpaceId']) },
7
+ { name: 'review_with_tonborg', title: 'WRITE · Tonborg review', description: 'Run a bounded real-model review under the saved autonomy policy. Manual mode only analyzes. AI-reviewed never asserts human confirmation. Reuse requestId on retries; failures and stale evidence remain visible. May invoke the locally configured judgment client.', inputSchema: objectSchema({ ...scope, requestId: id, limit: integerSchema({ minimum: 1, maximum: 10 }) }, ['personalSpaceId', 'requestId']) },
8
+ { name: 'assess_agent_action', title: 'READ · Tonborg executor judgment', description: 'Before a new subagent, session or client choice, ask Tonborg whether to continue, delegate, reuse a verified conversation or open a new session, and select from authorized, preflighted executors and their available models using scoped preferences and feedback. Respect locked Agent policies and user rules. The host still validates and starts workers. Report actual runtime separately; this does not grant permission or claim execution.', inputSchema: objectSchema({ ...scope, requestId: id, objective: { ...stringSchema(), minLength: 1, maxLength: 8000 }, agentId: id, workKind: id, taskId: id, requiredCapabilities: { type: 'array', items: id, maxItems: 32 }, action: { type: 'string', enum: ['subagent', 'session', 'client'] } }, ['personalSpaceId', 'personalProjectId', 'requestId', 'objective', 'agentId', 'workKind']) },
9
+ { name: 'assess_task_completion', title: 'WRITE · Tonborg task acceptance', description: 'The accountable lead asks Tonborg to assess an awaiting-review FULI task at its exact revision. Saved autonomy may complete it only with a passed real verification by another Agent. Preserves Jefa human acceptance and never approves publication or new permissions. Reuse requestId on retries; inspect the actual execution receipt.', inputSchema: objectSchema({ ...scope, requestId: id, taskContextToken: id, taskId: id, artifactRevision: { ...id, maxLength: 160 }, expectedRevision: integerSchema({ minimum: 0 }) }, ['personalSpaceId', 'personalProjectId', 'requestId', 'taskContextToken', 'taskId', 'artifactRevision', 'expectedRevision']) },
10
+ { name: 'get_agent_pins', title: 'READ · Pinned Agents', description: 'Read the user’s pinned Agent IDs and revision. An explicit empty list remains empty.', inputSchema: objectSchema({ personalSpaceId: id }, ['personalSpaceId']) },
11
+ { name: 'pin_agent', title: 'WRITE · Pin an Agent', description: 'On the user’s request pin or unpin an existing Agent in navigation. Preserve the current revision and reload after conflicts.', inputSchema: objectSchema({ personalSpaceId: id, agentId: id, pinned: booleanSchema(), expectedRevision: integerSchema({ minimum: 0 }) }, ['personalSpaceId', 'agentId', 'pinned', 'expectedRevision']) },
12
+ ];
@@ -61,7 +61,7 @@ export const projectAgentProfile = objectSchema({
61
61
  taste: { type: 'string', maxLength: 2048, default: '' },
62
62
  personality: { type: 'string', maxLength: 2048, default: '' }
63
63
  }),
64
- description: 'HR-configured judgment, taste and personality based on role responsibilities. These are intended working traits, not evidence of observed growth or performance.'
64
+ description: 'AR-configured judgment, taste and personality based on role responsibilities. These are intended working traits, not evidence of observed growth or performance.'
65
65
  },
66
66
  expectations: {
67
67
  type: 'string', maxLength: 4096, default: '',
@@ -8,6 +8,13 @@ import { firstField, listDirectory } from '../session-log.js';
8
8
  export const claudeCodeWake = Object.freeze({
9
9
  id: 'claude_code',
10
10
 
11
+ judgmentArgs({ quality }) {
12
+ return ['-p', '--output-format', 'stream-json', '--verbose', '--safe-mode',
13
+ '--tools', '', '--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}',
14
+ '--setting-sources', '', '--no-session-persistence', '--permission-mode', 'dontAsk',
15
+ '--effort', quality === 'economy' ? 'low' : quality === 'balanced' ? 'medium' : 'high'];
16
+ },
17
+
11
18
  resolveCommand({ env, which, platform }) {
12
19
  return env.FULI_CLAUDE_BIN || which('claude', platform);
13
20
  },
@@ -9,6 +9,13 @@ import { findFile, firstField, listDirectory } from '../session-log.js';
9
9
  export const codexWake = Object.freeze({
10
10
  id: 'codex',
11
11
 
12
+ judgmentArgs({ cwd, quality }) {
13
+ return ['exec', '--json', '--ephemeral', '--ignore-user-config', '--ignore-rules',
14
+ '--skip-git-repo-check', '--sandbox', 'read-only', '-C', cwd,
15
+ '-c', 'project_doc_max_bytes=0', '-c', 'features.shell_tool=false',
16
+ '-c', `model_reasoning_effort="${quality === 'economy' ? 'low' : quality === 'balanced' ? 'medium' : 'high'}"`, '-'];
17
+ },
18
+
12
19
  resolveCommand({ env, which, platform, fileExists = existsSync }) {
13
20
  if (env.FULI_CODEX_BIN) return env.FULI_CODEX_BIN;
14
21
  const found = which('codex', platform);
@@ -53,7 +53,7 @@ function unassignedReceipt(status, reason) {
53
53
  agent_selection_conflict: '分配待确认(FLA 选择冲突)',
54
54
  manual_selection: '分配待确认(需要选择负责人)'
55
55
  };
56
- return `FULI Agent:${labels[status] ?? '分配未完成(HR 尚未返回负责人)'}`;
56
+ return `FULI Agent:${labels[status] ?? '分配未完成(AR 尚未返回负责人)'}`;
57
57
  }
58
58
 
59
59
  export function employeeCollaborationReceipt(application, context, { tool, permission, sourceApplication }) {
@@ -19,8 +19,11 @@ Local service:
19
19
  open [--data-dir DIR]
20
20
 
21
21
  Install and Agent connection:
22
+ connect [--format standard|vscode|server] [--data-dir DIR | --runtime-config FILE] (print generic MCP configuration)
23
+ mcp [--runtime-config FILE] [--source-application codex|claude|claude_code|cursor|kiro|other] (stdio server)
22
24
  setup [--yes] [--codex-only] [--data-dir DIR] [--personal-space NAME] [--port PORT] [--runtime-mode container|native] [--memory-profile low|balanced] [--adaptive-memory|--no-adaptive-memory] [--skip-agents] [--no-start] [--personal-only|--with-dev-public]
23
25
  connect-workspace --url URL --token-file FILE [--data-dir DIR]
26
+ peer status|host|invite|join|share|revoke|off [--data-dir DIR] (LAN roundtable Beta: pair devices and share project leads)
24
27
  remote-mcp --personal-project-id ID --bearer-token-file FILE [--port PORT] [--data-dir DIR] [--source-application claude|cursor|other] [--allowed-origin HTTPS_ORIGIN] [--allowed-host HOST] [--max-sessions N] [--session-idle-ttl-seconds N]
25
28
  employee install BUILT_PACKAGE_DIRECTORY [--replace] [--runtime-config FILE]
26
29
  update [setup options]
@@ -0,0 +1,28 @@
1
+ import { resolveSetupPaths } from '../setup/paths.js';
2
+ import { resolveGraphRuntimeOptions } from '../graphiti/runtime-config.js';
3
+ import { clientConnectionConfig } from '../setup/client-connection.js';
4
+
5
+ export function runConnectCommand(args, { env = process.env, write = console.log } = {}) {
6
+ if (args.length === 1 && ['--help', '-h'].includes(args[0])) {
7
+ write('fuli connect [--format standard|vscode|server] [--data-dir DIR | --runtime-config FILE]\nPrint local MCP configuration; merge the fuli entry into your client settings. No files are changed.');
8
+ return;
9
+ }
10
+ const options = new Map();
11
+ for (let index = 0; index < args.length; index += 2) {
12
+ const flag = args[index];
13
+ if (!['--format', '--data-dir', '--runtime-config'].includes(flag)) throw new TypeError(`Unknown connect option: ${flag}`);
14
+ if (options.has(flag)) throw new TypeError(`Duplicate ${flag}`);
15
+ const value = args[index + 1];
16
+ if (!value?.trim() || value.startsWith('--')) throw new TypeError(`Missing value for ${flag}`);
17
+ options.set(flag, value);
18
+ }
19
+ if (options.has('--data-dir') && options.has('--runtime-config')) {
20
+ throw new TypeError('Choose --data-dir or --runtime-config, not both');
21
+ }
22
+ const paths = resolveSetupPaths({ dataDir: options.get('--data-dir'), env });
23
+ const runtimeConfigPath = options.has('--data-dir') ? paths.graphRuntimeConfigPath
24
+ : resolveGraphRuntimeOptions(options.has('--runtime-config') ? ['--runtime-config', options.get('--runtime-config')] : [], env).runtimeConfigPath;
25
+ const config = clientConnectionConfig({ mcpServerPath: paths.mcpServerPath, runtimeConfigPath }, options.get('--format'));
26
+ write(JSON.stringify(config, null, 2));
27
+ return config;
28
+ }
@@ -0,0 +1,99 @@
1
+ import { callAgentTool, listAgentTools } from '../agent-tools.js';
2
+ import {
3
+ bindRuntimeLeaseAgentTools,
4
+ createRuntimeLeaseClient
5
+ } from '../adaptive-runtime/lease-client.js';
6
+ import { ApplicationError } from '../app/application-error.js';
7
+ import { openFederatedGraphApplication } from '../graphiti/federated-application.js';
8
+ import {
9
+ GraphRuntimeConfigurationError,
10
+ resolveGraphRuntimeOptions
11
+ } from '../graphiti/runtime-config.js';
12
+ import { runStdio } from '../mcp/runtime.js';
13
+ import { prepareDirectAgentToolCall } from '../mcp/direct-call.js';
14
+ import { normalizeMcpSourceApplication } from '../mcp/session-id.js';
15
+ import { applicationErrorMessage } from '../mcp/tool-result.js';
16
+
17
+ export async function runMcpCommand(args, { env = process.env } = {}) {
18
+ if (args.includes('--tools')) {
19
+ process.stdout.write(`${JSON.stringify(listAgentTools(), null, 2)}\n`);
20
+ return;
21
+ }
22
+ if (args.includes('--help') || args.includes('-h')) {
23
+ process.stdout.write(helpText());
24
+ return;
25
+ }
26
+
27
+ const { runtimeConfigPath } = resolveGraphRuntimeOptions(args, env);
28
+ const configuredSourceApplication = args.includes('--source-application')
29
+ ? requiredOption(args, '--source-application')
30
+ : null;
31
+ if (args.includes('--call')) {
32
+ await runCall({
33
+ args,
34
+ runtimeConfigPath,
35
+ sourceApplication: configuredSourceApplication
36
+ ? normalizeMcpSourceApplication(configuredSourceApplication)
37
+ : 'other'
38
+ });
39
+ return;
40
+ }
41
+ await runStdio({
42
+ runtimeConfigPath,
43
+ sourceApplication: configuredSourceApplication
44
+ ? normalizeMcpSourceApplication(configuredSourceApplication)
45
+ : undefined
46
+ });
47
+ }
48
+
49
+ async function runCall({ args, runtimeConfigPath, sourceApplication }) {
50
+ const toolName = requiredOption(args, '--call');
51
+ const prepared = await prepareDirectAgentToolCall({
52
+ toolName,
53
+ input: parseInput(args),
54
+ sourceApplication
55
+ });
56
+ const app = openFederatedGraphApplication({ runtimeConfigPath });
57
+ const leaseClient = createRuntimeLeaseClient({ runtimeConfigPath });
58
+ bindRuntimeLeaseAgentTools(app, leaseClient);
59
+ try {
60
+ const result = await leaseClient.withGraphLease(
61
+ `cli-call:${toolName}`,
62
+ () => callAgentTool(app, toolName, prepared.input)
63
+ );
64
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
65
+ } finally {
66
+ await leaseClient.close();
67
+ await app.close();
68
+ }
69
+ }
70
+
71
+ function option(args, flag) {
72
+ const index = args.indexOf(flag);
73
+ return index === -1 ? null : args[index + 1];
74
+ }
75
+
76
+ function requiredOption(args, flag) {
77
+ const value = option(args, flag);
78
+ if (!value) throw new TypeError(`Missing value for ${flag}`);
79
+ return value;
80
+ }
81
+
82
+ function parseInput(args) {
83
+ const raw = option(args, '--input');
84
+ return raw ? JSON.parse(raw) : {};
85
+ }
86
+
87
+ export function startupMessage(error) {
88
+ if (error instanceof ApplicationError) return applicationErrorMessage(error);
89
+ if (error instanceof GraphRuntimeConfigurationError) return error.message;
90
+ return 'MCP server failed to start';
91
+ }
92
+
93
+ function helpText() {
94
+ return 'Usage: fuli mcp [--runtime-config <graph-runtime.json>] ' +
95
+ '[--source-application <codex|claude|claude_code|cursor|kiro|other>]\n' +
96
+ ' fuli mcp --tools\n' +
97
+ ' fuli mcp --runtime-config <graph-runtime.json> ' +
98
+ '--call <tool> [--input <json>]\n';
99
+ }
@@ -0,0 +1,100 @@
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ import { resolveSetupPaths } from '../setup/paths.js';
4
+ import { DEFAULT_RUNTIME_SETTINGS, readRuntimeSettings } from '../system/runtime-settings.js';
5
+ import { shortFingerprint } from '../agent-peer/protocol.js';
6
+
7
+ const USAGE = `fuli peer <action> (LAN roundtable Beta; the local console must be running)
8
+ status
9
+ host --address IP [--port PORT] [--name NAME] turn this computer into the coordinator
10
+ invite print a one-use pairing invitation (10 minutes)
11
+ join (--invitation TEXT | --invitation-file FILE) [--name NAME]
12
+ share --project ID --clients codex[,claude_code] [--folder DIR] (repeat --project for more; replaces the list)
13
+ revoke --device FINGERPRINT coordinator only
14
+ off leave: stop listening, sharing and trusting devices`;
15
+
16
+ // The owner's LAN roundtable controls through the local console, so the CLI
17
+ // and Settings share one runtime and one set of local-only checks.
18
+ export async function runPeerCommand(args, { env = process.env, fetchImpl = fetch, write = console.log } = {}) {
19
+ const [action, ...rest] = args;
20
+ if (!action || action === '--help' || action === '-h') { write(USAGE); return; }
21
+ const paths = resolveSetupPaths({ dataDir: option(rest, '--data-dir'), env });
22
+ const settings = paths.runtimeSettingsPath ? readRuntimeSettings(paths.runtimeSettingsPath) : DEFAULT_RUNTIME_SETTINGS;
23
+ const base = `http://127.0.0.1:${settings.ports.console}`;
24
+ const call = async (method, path, body) => {
25
+ let response;
26
+ try {
27
+ response = await fetchImpl(`${base}${path}`, { method, headers: { 'content-type': 'application/json' },
28
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
29
+ } catch {
30
+ throw new Error('The local FULI console is not running; start it with `fuli start`.');
31
+ }
32
+ const value = await response.json().catch(() => ({}));
33
+ if (!response.ok) throw new Error(value.message ?? `Request failed (${response.status})`);
34
+ return value;
35
+ };
36
+ switch (action) {
37
+ case 'status': return printStatus(await call('GET', '/api/peer'), write);
38
+ case 'host': {
39
+ const address = option(rest, '--address');
40
+ if (!address) throw new Error('Pass --address with one of this computer\'s LAN addresses (see `fuli peer status`).');
41
+ return printStatus(await call('POST', '/api/peer/coordinator', { host: address,
42
+ port: Number(option(rest, '--port') ?? 0), name: option(rest, '--name') }), write);
43
+ }
44
+ case 'invite': {
45
+ const result = await call('POST', '/api/peer/invitations', {});
46
+ write(`Coordinator fingerprint: ${shortFingerprint(result.fingerprint)}`);
47
+ write(`Invitation (one use, until ${result.expiresAt}):\n${result.invitation}`);
48
+ write('Compare this fingerprint with the one shown on the joining device before you share it.');
49
+ return;
50
+ }
51
+ case 'join': {
52
+ const file = option(rest, '--invitation-file');
53
+ const invitation = option(rest, '--invitation') ?? (file ? readFileSync(file, 'utf8').trim() : null);
54
+ if (!invitation) throw new Error('Pass --invitation or --invitation-file.');
55
+ return printStatus(await call('POST', '/api/peer/join', { invitation, name: option(rest, '--name') }), write);
56
+ }
57
+ case 'share': {
58
+ const projects = options(rest, '--project');
59
+ const clients = (option(rest, '--clients') ?? '').split(',').map((client) => client.trim()).filter(Boolean);
60
+ const folder = option(rest, '--folder');
61
+ if (projects.length > 1 && folder) throw new Error('--folder applies to a single --project.');
62
+ return printStatus(await call('PUT', '/api/peer/shares', { shares: projects.map((projectId) => ({ projectId, clients,
63
+ ...(folder ? { workingDirectory: folder } : {}) })) }), write);
64
+ }
65
+ case 'revoke': {
66
+ const device = option(rest, '--device');
67
+ if (!/^[0-9a-f]{64}$/.test(device ?? '')) throw new Error('Pass --device with the full fingerprint from `fuli peer status`.');
68
+ return printStatus(await call('POST', `/api/peer/devices/${device}/revoke`, {}), write);
69
+ }
70
+ case 'off': return printStatus(await call('POST', '/api/peer/disable', {}), write);
71
+ default: throw new Error(`Unknown peer action: ${action}\n${USAGE}`);
72
+ }
73
+ }
74
+
75
+ function printStatus(status, write) {
76
+ if (!status.role) {
77
+ write('LAN roundtable (Beta): off.');
78
+ if (status.remoteRemovalPending) write('This device has stopped sharing. Pairing removal will retry when the coordinator is available.');
79
+ if (status.addresses.length) write(`LAN addresses: ${status.addresses.join(', ')}`);
80
+ return;
81
+ }
82
+ write(`LAN roundtable (Beta): ${status.role === 'coordinator' ? 'coordinator' : 'paired device'}`);
83
+ write(`This device: ${status.device.shortFingerprint}`);
84
+ write(`Coordinator: ${status.coordinator.name} ${status.coordinator.url} (${status.coordinator.shortFingerprint})`);
85
+ for (const device of status.devices.filter((entry) => !entry.self)) {
86
+ write(` device ${device.name} ${device.shortFingerprint} ${device.status}${device.online ? ' online' : ''} ${device.nodeId}`);
87
+ }
88
+ for (const share of status.shares) {
89
+ write(` shared project lead: ${share.projectName} · ${share.clients.join(', ')} · ${share.state}${share.workingDirectory ? '' : ' · folder from recent sessions'}`);
90
+ }
91
+ }
92
+
93
+ function option(args, name) {
94
+ const index = args.indexOf(name);
95
+ return index >= 0 && index + 1 < args.length ? args[index + 1] : undefined;
96
+ }
97
+
98
+ function options(args, name) {
99
+ return args.flatMap((value, index) => (value === name && index + 1 < args.length ? [args[index + 1]] : []));
100
+ }
@@ -86,7 +86,7 @@ export function selectedAgentIds(agents, state) {
86
86
 
87
87
  export function formatAgentCheckboxSelection(agents, state) {
88
88
  return [
89
- 'Multiple supported Agents were detected:',
89
+ 'Clients available for automatic setup:',
90
90
  ...agents.map((agent, index) => {
91
91
  const pointer = index === state.activeIndex ? '❯' : ' ';
92
92
  const checkbox = state.selected[index] ? '[x]' : '[ ]';
@@ -161,7 +161,7 @@ async function promptCheckboxAgentSelection(agents, { input, output }) {
161
161
 
162
162
  async function promptTextAgentSelection(agents, { input, output }) {
163
163
  const terminal = createInterface({ input, output });
164
- output.write('Multiple supported Agents were detected:\n');
164
+ output.write('Clients available for automatic setup:\n');
165
165
  for (const [index, agent] of agents.entries()) {
166
166
  output.write(` ${index + 1}. ${agent.label}\n`);
167
167
  }
@@ -67,7 +67,8 @@ export function formatSetupPreview(plan, options) {
67
67
  `Management UI: ${options.noStart
68
68
  ? 'will not start'
69
69
  : `http://127.0.0.1:${runtimeSettings.ports.console}`}`,
70
- `Agents: ${agentSummary}`
70
+ `Automatic client setup: ${agentSummary}`,
71
+ 'Other MCP clients: connect after setup with fuli connect (no model required)'
71
72
  ];
72
73
  if (
73
74
  runtimeMode !== 'native' &&
@@ -80,7 +81,7 @@ export function formatSetupPreview(plan, options) {
80
81
  return lines.join('\n');
81
82
  }
82
83
 
83
- export function formatSetupResult(result) {
84
+ export function formatSetupResult(result, plan) {
84
85
  const lines = [result.status === 'ready'
85
86
  ? 'Fuli is ready.'
86
87
  : 'Fuli started, but one or more Agents are not connected.'];
@@ -106,6 +107,10 @@ export function formatSetupResult(result) {
106
107
  : `${agent.label}: connected`);
107
108
  for (const step of agent.nextSteps ?? []) lines.push(`${agent.label}: ${step}`);
108
109
  }
110
+ const dataDir = plan?.paths?.dataDir;
111
+ lines.push('Other MCP clients: run fuli connect and merge the fuli entry into your MCP settings.');
112
+ if (dataDir) lines.push(`For this installation, use --data-dir ${JSON.stringify(dataDir)}.`);
113
+ lines.push('MCP provides tools and memory. Automatic conversation capture and wake/resume require a client adapter.');
109
114
  return lines.join('\n');
110
115
  }
111
116
 
package/src/cli.js CHANGED
@@ -11,6 +11,9 @@ 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
13
  import { runSetupCommand } from './cli/setup-command.js';
14
+ import { runConnectCommand } from './cli/connect-command.js';
15
+ import { runPeerCommand } from './cli/peer-command.js';
16
+ import { runMcpCommand, startupMessage } from './cli/mcp-command.js';
14
17
  import { runUninstallCommand } from './cli/uninstall-command.js';
15
18
  import { runUpdateCommand } from './cli/update-command.js';
16
19
  import { runWorkspaceConnectionCommand } from './cli/workspace-connection-command.js';
@@ -32,6 +35,19 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
32
35
  await runSetupCommand(commandArgs, { env });
33
36
  return;
34
37
  }
38
+ if (command === 'connect') {
39
+ assertSupportedNodeVersion();
40
+ return runConnectCommand(commandArgs, { env });
41
+ }
42
+ if (command === 'peer') {
43
+ assertSupportedNodeVersion();
44
+ return runPeerCommand(commandArgs, { env });
45
+ }
46
+ if (command === 'mcp') {
47
+ assertSupportedNodeVersion();
48
+ try { return await runMcpCommand(commandArgs, { env }); }
49
+ catch (error) { throw new Error(startupMessage(error)); }
50
+ }
35
51
  if (command === 'graph') {
36
52
  assertSupportedNodeVersion();
37
53
  const [action, ...graphArgs] = commandArgs;
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "bole",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "name": "Bole",
6
- "role": "HR",
6
+ "role": "AR",
7
7
  "description": "查看 Agent 人员分布、当前工作和带时间与原因的招募记录。",
8
8
  "occupationEmoji": "🔎",
9
9
  "defaultProjectScope": "all",
@@ -0,0 +1,20 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "tonborg",
4
+ "version": "1.0.0",
5
+ "name": "Tonborg",
6
+ "role": "判断官",
7
+ "description": "根据你的偏好与判断记录处理日常审核,为任务建议客户端和模型。",
8
+ "occupationEmoji": "⚖️",
9
+ "defaultProjectScope": "all",
10
+ "workbench": { "kind": "native", "view": "judgment" },
11
+ "capabilities": ["判断审核", "执行器选择", "判断反馈", "fuli.employee:tonborg"],
12
+ "workKinds": ["judgment", "knowledge-review", "executor-selection"],
13
+ "initialPreferences": [
14
+ "优先考虑用户明确偏好和当前任务证据;反馈只用于适用的范围。",
15
+ "记录判断依据与执行结果,权限不足或证据不足时交给用户。",
16
+ "保持项目负责人的汇报关系,不越过项目管理。"
17
+ ],
18
+ "permissions": ["judgment.read", "judgment.review"],
19
+ "runtime": null
20
+ }
@@ -29,7 +29,7 @@ export const employeeManifestSchema = z.object({
29
29
  }).strict().optional(),
30
30
  workbench: z.object({
31
31
  kind: z.literal('native'),
32
- view: z.literal('people')
32
+ view: z.enum(['people', 'judgment'])
33
33
  }).strict().optional(),
34
34
  runtime: z.object({
35
35
  apiVersion: z.literal(1),
@@ -45,7 +45,7 @@ export function parseEmployeeManifest(value) {
45
45
  }
46
46
 
47
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' });
48
+ export const FIXED_EMPLOYEE_AGENT_TYPES = Object.freeze({ jefa: 'coordinator', bole: 'hr', tonborg: 'durable' });
49
49
 
50
50
  export const employeeAgentId = (id) => `employee.${identifier.parse(id)}`;
51
51
  export const employeeCapability = (id) => `fuli.employee:${identifier.parse(id)}`;
@@ -186,7 +186,8 @@ export function createEmployeeRecruitment({ app, registry, managementStore }) {
186
186
  // All-project policy also covers projects without a materialized assignment.
187
187
  const agents = await app.listProjectAgents({ personalSpaceId: spaceId });
188
188
  const agent = agents.find((entry) => entry.agentId === employeeAgentId(manifest.id));
189
- const management = managementFor(manifest, agent, managementStore.read(spaceId, manifest.id));
189
+ const stored = managementStore.read(spaceId, manifest.id);
190
+ const management = managementFor(manifest, agent, stored);
190
191
  if (input.sourceApplication && Array.isArray(agent?.profile.allowedClients) && !agent.profile.allowedClients.includes(input.sourceApplication)) {
191
192
  throw new EmployeeError('This employee is not enabled for the current Agent client', 403, 'assignment_required');
192
193
  }
@@ -196,6 +197,24 @@ export function createEmployeeRecruitment({ app, registry, managementStore }) {
196
197
  (management.mode !== 'all' && !(agent.assignments ?? []).some((entry) => entry.personalProjectId === selectedProject.id && entry.status === 'active'))) {
197
198
  throw new EmployeeError('Recruit this employee into the selected project first', 403, 'assignment_required');
198
199
  }
200
+ if (manifest.id === 'tonborg' && input.ensureAssignment === true &&
201
+ !activeEmployeeAssignments(agent).some(entry => entry.personalProjectId === selectedProject.id)) {
202
+ // Provider memory is project-scoped. Preserve the already authorized all
203
+ // rule before its first concrete assignment, without launching a worker.
204
+ if (!stored) managementStore.write(spaceId, manifest.id, managementWithoutRevision(management), 0);
205
+ const history = (agent.assignments ?? []).filter(entry => entry.personalProjectId === selectedProject.id);
206
+ const generation = history.map(entry => `${entry.assignmentId}:${entry.revision ?? 0}`).sort();
207
+ const key = createHash('sha256').update(JSON.stringify([manifest.id, selectedProject.id, generation])).digest('hex');
208
+ await app.createProjectAgentAssignment({
209
+ personalSpaceId: spaceId, personalProjectId: selectedProject.id, agentId: agent.agentId,
210
+ idempotencyKey: `employee-assignment:${key}`, responsibility: agent.profile.responsibility,
211
+ workKinds: agent.profile.workKinds ?? manifest.workKinds, capabilities: agent.profile.capabilities,
212
+ reason: 'Initialize authorized Tonborg project memory',
213
+ });
214
+ // Assignment storage is asynchronous; a concurrent scope/client change
215
+ // must be observed before any judgment evidence is read.
216
+ return authorize({ ...input, ensureAssignment: false });
217
+ }
199
218
  return { personalSpaceId: spaceId, project: selectedProject, agentId: agent.agentId, management };
200
219
  }
201
220
 
@@ -30,11 +30,17 @@ export async function planAgentCollaboration(application, input) {
30
30
  }
31
31
  candidates.sort((a, b) => Number(a.requiresLoan) - Number(b.requiresLoan) || Number(b.workMatch) - Number(a.workMatch)
32
32
  || a.activeTasks - b.activeTasks || a.agentId.localeCompare(b.agentId));
33
+ const preferencePrompt = verificationPreference({ executors, rules, projectId: task.personalProjectId,
34
+ lockedExecutorIds: currentAgent.profile?.executorPolicy?.mode === 'locked' ? currentAgent.profile.executorPolicy.lockedExecutorIds : null });
35
+ const judgmentPolicy = await application.judgment?.policy({ personalSpaceId: spaceId, personalProjectId: task.personalProjectId });
36
+ if (judgmentPolicy && judgmentPolicy.mode !== 'manual' && preferencePrompt.choiceSource !== 'user_rule') {
37
+ Object.assign(preferencePrompt, { shouldAsk: false, recommendedExecutorId: null, choiceSource: 'tonborg',
38
+ guidance: 'The user delegated routine executor choices to Tonborg. Call assess_agent_action for the selected specialist before dispatch; ask only when its outcome requires user input. Recheck runtime availability and preserve the real execution receipt.' });
39
+ }
33
40
  return { teamLeadId: policy.team_lead_agent_id, currentAgentId: task.projectAgentId,
34
41
  candidates: candidates.slice(0, 8), pendingLoans: loans.loans?.filter(loan => loan.status === 'requested') ?? [],
35
42
  executionStarted: false,
36
- preferencePrompt: verificationPreference({ executors, rules, projectId: task.personalProjectId,
37
- lockedExecutorIds: currentAgent.profile?.executorPolicy?.mode === 'locked' ? currentAgent.profile.executorPolicy.lockedExecutorIds : null }),
43
+ preferencePrompt,
38
44
  guidance: 'Keep the project lead as owner. Choose a qualified specialist. For another project, the destination lead calls request_agent_loan; the source lead approves with decide_agent_loan in its own scoped task. Then coordinate_project_agent_task with the borrowed Agent. Share only the bounded task brief and authorized artifacts. Record actual workers. Verify each artifact; use record_agent_verification. Two failed attempts at one capability tier require escalation, not another identical retry. A selected model is not proof of execution or success.' };
39
45
  }
40
46
 
@@ -2,6 +2,10 @@ import { dirname, join } from 'node:path';
2
2
  import { createEmployeeService } from '../employees/service.js';
3
3
  import { createAgentRoundtable } from '../agent-roundtable/service.js';
4
4
  import { createRoundtableStore } from '../agent-roundtable/store.js';
5
+ import { createPeerPort } from '../agent-peer/peer-port.js';
6
+ import { createJudgmentService } from '../judgment/service.js';
7
+ import { JudgmentStore } from '../judgment/store.js';
8
+ import { createJudgmentEngine } from '../judgment/engine.js';
5
9
 
6
10
  import { GraphitiProviderClient } from './provider-client.js';
7
11
  import {
@@ -120,6 +124,10 @@ export function openFederatedGraphApplication({
120
124
  fetchImpl
121
125
  });
122
126
  app.employees = createEmployeeService({ app, runtimeConfigPath });
127
+ app.judgment = createJudgmentService({ app, engine: createJudgmentEngine({ app }),
128
+ openStore: () => new JudgmentStore(join(dirname(runtimeConfigPath), 'judgment.sqlite')) });
129
+ app.peer = createPeerPort({ dataDir: dirname(runtimeConfigPath), provider: app.personal,
130
+ spaceId: () => app.config.personal.spaceId });
123
131
  app.roundtable = createAgentRoundtable({ app, env,
124
132
  openStore: () => createRoundtableStore(join(dirname(runtimeConfigPath), 'agent-roundtable.sqlite')) });
125
133
  }
@@ -1164,7 +1172,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
1164
1172
  };
1165
1173
  }
1166
1174
 
1167
- close() { this.roundtable?.close(); return this.employees?.close(); }
1175
+ close() { this.judgment?.close(); this.roundtable?.close(); this.peer?.close(); return this.employees?.close(); }
1168
1176
 
1169
1177
  async #recordAgentViews(items, toolName) {
1170
1178
  const unique = new Map(items.map((item) => [
@@ -3,7 +3,8 @@ export function startKnowledgeReview(app, input) {
3
3
  return app.personal.startKnowledgeReview({
4
4
  personal_space_id: input.personalSpaceId,
5
5
  scope: input.scope,
6
- personal_project_id: input.personalProjectId ?? null
6
+ personal_project_id: input.personalProjectId ?? null,
7
+ ...(input.reviewer ? { reviewer: input.reviewer } : {})
7
8
  });
8
9
  }
9
10
 
@@ -12,7 +13,8 @@ export function listKnowledgeReviewCandidates(app, input) {
12
13
  return app.personal.listKnowledgeReviewCandidates({
13
14
  personal_space_id: input.personalSpaceId,
14
15
  review_id: input.reviewId,
15
- limit: input.limit
16
+ limit: input.limit,
17
+ ...(input.offset !== undefined ? { offset: input.offset } : {})
16
18
  });
17
19
  }
18
20
 
@@ -23,7 +25,9 @@ export function recordKnowledgeReviewProgress(app, input) {
23
25
  review_id: input.reviewId,
24
26
  candidate_key: input.candidateKey,
25
27
  outcome: input.outcome,
26
- note: input.note ?? null
28
+ note: input.note ?? null,
29
+ ...(input.aiReviewEvidenceToken ? { ai_review_evidence_token: input.aiReviewEvidenceToken } : {}),
30
+ ...(input.aiAssessment ? { ai_assessment: input.aiAssessment } : {})
27
31
  });
28
32
  }
29
33
 
@@ -2,6 +2,7 @@ import { planAgentCollaboration, agentLoan, agentVerification } from './agent-co
2
2
  import { queryConversations, resumeConversation, updateConversationPolicy } from './agent-conversations.js';
3
3
  import { agentProjectResolution } from './agent-knowledge-workflows.js';
4
4
  import { listAgentAttention, changeAgentAttention } from './agent-attention.js';
5
+ import { loadProjectTeam } from './project-team-view.js';
5
6
  import {
6
7
  getProjectAgentMemory,
7
8
  agentMemoryView,
@@ -205,6 +206,12 @@ export class ProjectAgentControlPlaneApplication {
205
206
  return getProjectAgentCoordinationPolicyWorkflow(this, input);
206
207
  }
207
208
 
209
+ async getProjectTeam(input) {
210
+ this.#assertSpace(input.personalSpaceId);
211
+ if (!input.personalProjectId) throw new TypeError('personalProjectId is required');
212
+ return loadProjectTeam(this, input.personalSpaceId, input.personalProjectId);
213
+ }
214
+
208
215
  async updateProjectAgentCoordinationPolicy(input) {
209
216
  this.#assertSpace(input.personalSpaceId);
210
217
  return updateProjectAgentCoordinationPolicyWorkflow(this, input);
@@ -343,6 +343,8 @@ export function projectAgentTokenUsage(value) {
343
343
  export function providerProjectAgentTaskActivity(input) {
344
344
  const result = withoutUndefined({
345
345
  artifact_revision: input.artifactRevision ?? input.artifact_revision,
346
+ expected_verification_attempt_id: input.expectedVerificationAttemptId,
347
+ judgment_task_context_token: input.judgmentTaskContextToken,
346
348
  personal_space_id: input.personalSpaceId ?? input.personal_space_id,
347
349
  personal_project_id: input.personalProjectId ?? input.personal_project_id,
348
350
  task_id: input.taskId ?? input.task_id,
@@ -2,6 +2,7 @@ import { projectAgentRecord } from './project-agent-mapping.js';
2
2
  import { agentMemoryView } from './project-agent-memory.js';
3
3
  import { planTaskKnowledgeRecall } from './task-knowledge-recall.js';
4
4
  import { loadProjectTeamContext } from './project-agent-team-context.js';
5
+ import { isPeerRoleAgent } from './project-team-view.js';
5
6
  import { resolveTaskAgentMention } from './project-agent-mention.js';
6
7
 
7
8
  const ENTRY_TOOLS = new Set(['begin_task_context', 'get_collaboration_preferences']);
@@ -49,14 +50,14 @@ export async function resolveTaskEntryAgent(application, resolution, input) {
49
50
  const implicit = !mention?.agentId && !input.projectAgentId;
50
51
  const peerOnly = agent && implicit && unrelatedPeer(agent, workKind, input.requiredCapabilities);
51
52
  if (implicit && value.reason !== 'project_lead_required' && (peerOnly || value.status === 'unassigned')) {
52
- // Every task gets a FULI Agent: a project without a lead has HR hire one.
53
+ // Every task gets a FULI Agent: a project without a lead has AR hire one.
53
54
  const staffed = await application.personal.staffDefaultProjectLead(scope).catch(() => null);
54
55
  if (staffed?.agent) {
55
56
  value = staffed;
56
57
  agent = projectAgentRecord(staffed.agent);
57
58
  } else if (peerOnly) {
58
59
  return { status: 'unassigned', reason: 'specialist_required', worker_started: false,
59
- 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.' };
60
+ required_action: 'Use coordinate_project_agent_task to select or recruit a qualified durable owner before implementation. AR and project managers collaborate in their own roles; do not attribute development to them by fallback.' };
60
61
  }
61
62
  }
62
63
  const employeeId = /^employee\.([a-z][a-z0-9-]{0,63})$/.exec(agent?.agentId ?? '')?.[1];
@@ -159,8 +160,7 @@ function boundedRoleText(value, limit) {
159
160
  function unrelatedPeer(agent, workKind, required = []) {
160
161
  const profile = agent.profile;
161
162
  const capabilities = (profile.capabilities ?? []).map(value => value.toLowerCase());
162
- const peer = ['employee.jefa', 'employee.bole', 'fuli-project-hr'].includes(agent.agentId)
163
- || profile.agentType === 'hr' || capabilities.some(value => ['fuli.employee:jefa', 'fuli.employee:bole'].includes(value));
163
+ const peer = isPeerRoleAgent({ agentId: agent.agentId, agentType: profile.agentType, capabilities });
164
164
  return peer && !(profile.workKinds ?? []).some(kind => kind.toLowerCase() === workKind.toLowerCase())
165
165
  && !(required.length && required.every(value => capabilities.includes(value.toLowerCase())));
166
166
  }