fuli-context 0.7.9 → 0.7.10-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +5 -2
  2. package/README.zh-CN.md +5 -3
  3. package/dist/web/assets/{AboutPage-Bp-Xz6yo.js → AboutPage-EtWPOSpz.js} +1 -1
  4. package/dist/web/assets/AboutPage-vYIBifKw.css +1 -0
  5. package/dist/web/assets/AgentHand-D5pOgVjJ.js +1 -0
  6. package/dist/web/assets/AgentHand-DvdNtJ3_.css +1 -0
  7. package/dist/web/assets/ConnectionsPage-DXkvMuyK.js +1 -0
  8. package/dist/web/assets/EmployeeRecruitDialog-B59X3rOi.css +1 -0
  9. package/dist/web/assets/EmployeeRecruitDialog-DONxXnd2.js +1 -0
  10. package/dist/web/assets/EmployeeWorkbenchPage-Cd4hd4bf.js +3 -0
  11. package/dist/web/assets/EmployeeWorkbenchPage-Cz7y-IFD.css +1 -0
  12. package/dist/web/assets/GrowthLoading-B_Hh0CRS.css +1 -0
  13. package/dist/web/assets/GrowthLoading-CCYPgE2f.js +1 -0
  14. package/dist/web/assets/KnowledgeEditDialog-BUqMfa0a.js +1 -0
  15. package/dist/web/assets/KnowledgeEditDialog-D9NuRHvp.css +1 -0
  16. package/dist/web/assets/KnowledgeInspector-DlVPDGP3.js +1 -0
  17. package/dist/web/assets/KnowledgeOrganizerPage-DhthZGKZ.js +1 -0
  18. package/dist/web/assets/KnowledgeOrganizerPage-h2iKvQpO.css +1 -0
  19. package/dist/web/assets/KnowledgePage-B6RDVxq-.js +1 -0
  20. package/dist/web/assets/KnowledgeWorkspace-mVFHQcxr.js +4 -0
  21. package/dist/web/assets/KnowledgeWorkspace-sb8OJ97I.css +1 -0
  22. package/dist/web/assets/NotFoundPage-CCeEdBy9.js +1 -0
  23. package/dist/web/assets/{OverviewPage-HwoSW-5z.js → OverviewPage-CnYWoZ3r.js} +1 -1
  24. package/dist/web/assets/PersonalProfilePage-Bz4zpTPC.js +1 -0
  25. package/dist/web/assets/PersonalProfilePage-yQRCBude.css +1 -0
  26. package/dist/web/assets/PersonalProjectsPage-2u1o6dvA.js +1 -0
  27. package/dist/web/assets/ProjectAgentsPage-8tci631q.js +11 -0
  28. package/dist/web/assets/ProjectAgentsPage-BN4k3ubu.css +1 -0
  29. package/dist/web/assets/ProjectScopePicker-BcPtIcv8.js +1 -0
  30. package/dist/web/assets/ProjectScopePicker-BvaGTMFx.css +1 -0
  31. package/dist/web/assets/PublicProjectsPage-BmBHfnud.js +1 -0
  32. package/dist/web/assets/ReviewPage-tvt2eqhw.js +1 -0
  33. package/dist/web/assets/{SearchableSelect-CFZNdtlY.js → SearchableSelect-BjBDBVuZ.js} +1 -1
  34. package/dist/web/assets/SettingsPage-Brl-SL33.css +1 -0
  35. package/dist/web/assets/SettingsPage-D9mNzBg-.js +1 -0
  36. package/dist/web/assets/SharedJefaReportPage-DXbj_JhO.css +1 -0
  37. package/dist/web/assets/SharedJefaReportPage-Dn_HBgaa.js +1 -0
  38. package/dist/web/assets/WritingTastePage-BCwimXC-.js +1 -0
  39. package/dist/web/assets/WritingTastePage-tb_pgude.css +1 -0
  40. package/dist/web/assets/bole-avatar-CVIr-bU_.webp +0 -0
  41. package/dist/web/assets/client-v8XKgQji.js +1 -0
  42. package/dist/web/assets/console-C9HS-zbd.js +1 -0
  43. package/dist/web/assets/conversation-launcher-settings-DMDCpf_N.js +1 -0
  44. package/dist/web/assets/employee-dialog-Bh5ccIbK.css +1 -0
  45. package/dist/web/assets/{i18n-DQH9D35M.js → i18n-BkJjUsz_.js} +3 -3
  46. package/dist/web/assets/{identity-JjZG6aS5.js → identity-74yGiMzO.js} +1 -1
  47. package/dist/web/assets/index-CwjxeHGk.css +1 -0
  48. package/dist/web/assets/index-Dioqmmvf.js +2 -0
  49. package/dist/web/assets/{model-zL97NJ-U.js → model-86tZgY0r.js} +1 -1
  50. package/dist/web/assets/shared-report-CLg82y4D.js +1 -0
  51. package/dist/web/assets/shared-report-ezeb8dW4.css +1 -0
  52. package/dist/web/assets/{useApi-CROJJdhE-CElDxEZI.js → useApi-CROJJdhE-BBUO_YEu.js} +1 -1
  53. package/dist/web/assets/useModalDialog-DrLhqtjR.js +1 -0
  54. package/dist/web/assets/{vue-router-F7MnkVlW.js → vue-router-Cw4QZxM1.js} +1 -1
  55. package/dist/web/index.html +14 -8
  56. package/docs/agent-interface-architecture.md +97 -0
  57. package/docs/employee-agents.md +58 -2
  58. package/graph-provider/fuli_graph/collaboration_context.py +2 -1
  59. package/graph-provider/fuli_graph/graph_models.py +5 -3
  60. package/graph-provider/fuli_graph/graph_query.py +4 -0
  61. package/graph-provider/fuli_graph/knowledge_search.py +2 -1
  62. package/graph-provider/fuli_graph/project_agent_access.py +2 -0
  63. package/graph-provider/fuli_graph/project_agent_attention_models.py +64 -0
  64. package/graph-provider/fuli_graph/project_agent_attention_routes.py +38 -0
  65. package/graph-provider/fuli_graph/project_agent_models.py +2 -0
  66. package/graph-provider/fuli_graph/project_agent_names.py +33 -0
  67. package/graph-provider/fuli_graph/project_agent_routes.py +12 -0
  68. package/graph-provider/fuli_graph/runtime.py +49 -20
  69. package/graph-provider/fuli_graph/store.py +2 -0
  70. package/graph-provider/fuli_graph/store_knowledge.py +4 -2
  71. package/graph-provider/fuli_graph/store_project_agent_attention.py +138 -0
  72. package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +8 -0
  73. package/graph-provider/fuli_graph/store_project_agent_memory.py +40 -13
  74. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +4 -1
  75. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +4 -1
  76. package/graph-provider/fuli_graph/store_project_agent_tasks.py +3 -0
  77. package/graph-provider/fuli_graph/store_project_agents.py +109 -0
  78. package/graph-provider/fuli_graph/store_task_contexts.py +1 -1
  79. package/graph-provider/fuli_graph/system_hr_identity.py +218 -0
  80. package/graph-provider/pyproject.toml +1 -1
  81. package/npm-shrinkwrap.json +52 -60
  82. package/package.json +2 -2
  83. package/skills/operating-fuli/SKILL.md +47 -0
  84. package/skills/operating-fuli/agents/openai.yaml +4 -0
  85. package/skills/operating-fuli/references/workflow-recipes.md +33 -0
  86. package/src/agent-tools/agent-attention-definitions.js +33 -0
  87. package/src/agent-tools/graph-definitions.js +8 -2
  88. package/src/agent-tools/graph-handlers.js +41 -0
  89. package/src/agent-tools/interface-catalog.js +101 -0
  90. package/src/agent-tools/interface-definitions.js +184 -0
  91. package/src/agent-tools/project-agent-definitions.js +1 -0
  92. package/src/cli/local-runtime-command.js +2 -1
  93. package/src/employees/catalog/bole.json +19 -0
  94. package/src/employees/install-package.js +3 -0
  95. package/src/employees/manifest.js +4 -0
  96. package/src/employees/package-registry.js +2 -0
  97. package/src/employees/recruitment.js +3 -0
  98. package/src/employees/service.js +10 -0
  99. package/src/external-knowledge/registry.js +24 -0
  100. package/src/external-knowledge/service.js +34 -1
  101. package/src/graphiti/agent-attention.js +26 -0
  102. package/src/graphiti/agent-knowledge-workflows.js +1 -0
  103. package/src/graphiti/project-agent-control-plane.js +21 -0
  104. package/src/graphiti/project-agent-mapping.js +6 -0
  105. package/src/graphiti/provider-client.js +13 -0
  106. package/src/graphiti/workspace-provider-client.js +9 -1
  107. package/src/http/agent-attention-api-router.js +28 -0
  108. package/src/http/error-mapping.js +3 -0
  109. package/src/http/graph-api-router.js +2 -0
  110. package/src/local-runtime/lifecycle.js +8 -2
  111. package/src/mcp/create-mcp-server.js +10 -1
  112. package/src/mcp/tool-annotations.js +31 -0
  113. package/src/mcp/tool-result.js +4 -4
  114. package/src/setup/agent-installation-status.js +2 -1
  115. package/src/setup/agents.js +4 -0
  116. package/src/setup/graph-runtime.js +4 -0
  117. package/src/setup/paths.js +2 -1
  118. package/src/setup/setup.js +4 -2
  119. package/src/setup/uninstall.js +4 -2
  120. package/src/system/resource-monitor.js +11 -1
  121. package/src/system/runtime-settings.js +7 -0
  122. package/dist/web/assets/AboutPage-DXh7Fwxt.css +0 -1
  123. package/dist/web/assets/ConnectionsPage-BMpkYeSE.js +0 -1
  124. package/dist/web/assets/EmployeeRecruitDialog-CnA7rOnB.css +0 -1
  125. package/dist/web/assets/EmployeeRecruitDialog-DbMVuLAI.js +0 -1
  126. package/dist/web/assets/EmployeeWorkbenchPage-BpkqpEs7.css +0 -1
  127. package/dist/web/assets/EmployeeWorkbenchPage-DtXSJOpq.js +0 -1
  128. package/dist/web/assets/KnowledgeEditDialog-DADXj_BT.js +0 -1
  129. package/dist/web/assets/KnowledgeEditDialog-iv-dgUeD.css +0 -1
  130. package/dist/web/assets/KnowledgeInspector-Xs4VCUg8.js +0 -1
  131. package/dist/web/assets/KnowledgeOrganizerPage-B_Ct4bB2.js +0 -1
  132. package/dist/web/assets/KnowledgeOrganizerPage-Cx1w1jb2.css +0 -1
  133. package/dist/web/assets/KnowledgePage-RG9Ttnrp.js +0 -1
  134. package/dist/web/assets/KnowledgeWorkspace-DI8YqTVI.js +0 -4
  135. package/dist/web/assets/PersonalProfilePage-B7T09bow.css +0 -1
  136. package/dist/web/assets/PersonalProfilePage-DCoG52SW.js +0 -1
  137. package/dist/web/assets/PersonalProjectsPage-CNb9mlbp.js +0 -1
  138. package/dist/web/assets/ProjectAgentsPage-C3vW6wmk.css +0 -1
  139. package/dist/web/assets/ProjectAgentsPage-zQ9BZW9v.js +0 -11
  140. package/dist/web/assets/PublicProjectsPage-BuLqkZ8g.js +0 -1
  141. package/dist/web/assets/ReviewPage-8A7N5pJs.js +0 -1
  142. package/dist/web/assets/SettingsPage-4ah1u906.js +0 -1
  143. package/dist/web/assets/SettingsPage-CwZp2ka4.css +0 -1
  144. package/dist/web/assets/WritingTastePage-DSgvTH6x.css +0 -1
  145. package/dist/web/assets/WritingTastePage-DztRj6MK.js +0 -1
  146. package/dist/web/assets/avatars-LvVKbDF7.js +0 -1
  147. package/dist/web/assets/console-B7dWYAoU.js +0 -1
  148. package/dist/web/assets/conversation-launcher-settings-D1xhW1S5.js +0 -1
  149. package/dist/web/assets/index-Ch1jmkyO.css +0 -1
  150. package/dist/web/assets/index-DRd-2Tii.js +0 -2
@@ -9,16 +9,22 @@
9
9
  />
10
10
  <link rel="icon" type="image/png" href="/assets/fuli-logo-ER1wQ8Kk.png" />
11
11
  <title>复利 · Context Graph</title>
12
- <script type="module" crossorigin src="/assets/index-DRd-2Tii.js"></script>
13
- <link rel="modulepreload" crossorigin href="/assets/i18n-DQH9D35M.js">
14
- <link rel="modulepreload" crossorigin href="/assets/console-B7dWYAoU.js">
15
- <link rel="modulepreload" crossorigin href="/assets/useApi-CROJJdhE-CElDxEZI.js">
16
- <link rel="modulepreload" crossorigin href="/assets/vue-router-F7MnkVlW.js">
17
- <link rel="modulepreload" crossorigin href="/assets/fuli-logo-Bu9WBuQe.js">
12
+ <script type="module" crossorigin src="/assets/index-Dioqmmvf.js"></script>
13
+ <link rel="modulepreload" crossorigin href="/assets/i18n-BkJjUsz_.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/client-v8XKgQji.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/console-C9HS-zbd.js">
18
16
  <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BDNMzG2s.js">
19
- <link rel="modulepreload" crossorigin href="/assets/avatars-LvVKbDF7.js">
17
+ <link rel="modulepreload" crossorigin href="/assets/GrowthLoading-CCYPgE2f.js">
18
+ <link rel="modulepreload" crossorigin href="/assets/useApi-CROJJdhE-BBUO_YEu.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/vue-router-Cw4QZxM1.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/fuli-logo-Bu9WBuQe.js">
21
+ <link rel="modulepreload" crossorigin href="/assets/AgentHand-D5pOgVjJ.js">
22
+ <link rel="modulepreload" crossorigin href="/assets/useModalDialog-DrLhqtjR.js">
20
23
  <link rel="modulepreload" crossorigin href="/assets/paths-CDREouhi.js">
21
- <link rel="stylesheet" crossorigin href="/assets/index-Ch1jmkyO.css">
24
+ <link rel="stylesheet" crossorigin href="/assets/GrowthLoading-B_Hh0CRS.css">
25
+ <link rel="stylesheet" crossorigin href="/assets/AgentHand-DvdNtJ3_.css">
26
+ <link rel="stylesheet" crossorigin href="/assets/index-CwjxeHGk.css">
27
+ <link rel="stylesheet" crossorigin href="/assets/employee-dialog-Bh5ccIbK.css">
22
28
  </head>
23
29
  <body>
24
30
  <div id="app"></div>
@@ -0,0 +1,97 @@
1
+ # Agent interface architecture
2
+
3
+ FULI exposes product data operations through structured Agent tools so an AI Agent does not need browser automation to act on the user's behalf. The console and the Agent interface are peer clients of the same application services; neither client owns a second copy of the business rules.
4
+
5
+ ## Contract
6
+
7
+ `list_agent_interfaces` returns the versioned `agent-interface-parity-v1` catalog. Every console data mutation is classified as one of:
8
+
9
+ - `agent`: the catalog names the structured Agent tool or tools that perform the same business operation.
10
+ - `local_user_only`: the catalog records why the operation requires local human presence.
11
+
12
+ The parity catalog is executable product metadata, not just documentation. Tests verify that every `agent` entry names a registered tool and that sensitive local-only routes remain explicit. A route with multiple actions, such as ignoring or resetting routing-learning evidence, lists every matching tool.
13
+
14
+ ## Request path
15
+
16
+ ```text
17
+ Console UI ───────┐
18
+ ├─> application service ─> scoped Provider / local store
19
+ Agent tool / MCP ─┘
20
+ ```
21
+
22
+ Agent handlers delegate to existing application-service methods. They do not call the console over HTTP, render pages, write browser state, or bypass scope checks. Existing Host/Origin protection remains relevant to the console; Agent access is independently guarded by the Agent-access policy before any tool handler runs.
23
+
24
+ ## Required operating sequence
25
+
26
+ ### Agent names and human attention
27
+
28
+ Agent identity (`agentId`) and role (`profile.name`, `responsibility`) remain stable.
29
+ `profile.displayName` is the editable person-like nickname. Legacy role-style names
30
+ receive a deterministic nickname derived from their Agent ID; existing custom names
31
+ and explicit display names are preserved. These are product nicknames, not real people.
32
+ Reading a legacy profile does not rewrite its stored history.
33
+
34
+ Use `request_agent_attention` only for an explicit human action, with the exact space,
35
+ project and Agent, optional task ID, request kind, context and `requestedAction`.
36
+ The same idempotency key and payload returns the same request, including after closure;
37
+ reusing a key with another payload is a conflict. Running, waiting in a queue and
38
+ recoverable retries do not create attention requests automatically.
39
+
40
+ `list_agent_attention` supports project/Agent/status filters and pagination. Counts
41
+ cover the whole filtered set. Agents can withdraw obsolete requests with
42
+ `cancel_agent_attention`, using `expectedRevision` and a reason. Only the local-user
43
+ `POST /api/agent-attention/respond` workflow records a human reply. A reply closes the
44
+ attention item but does not itself grant permission, accept a task or run a command.
45
+ Agents read resolved replies and continue within the existing authority boundaries.
46
+ Original context, response, actor and timestamps remain in the private graph.
47
+
48
+ The console uses one shared attention store and dialog for the sidebar and personnel
49
+ views. Failed reads remain visible as errors, not a false empty state. Replies use
50
+ revision checks and preserve the user's draft on failure. Public reports never query
51
+ or expose this private attention queue.
52
+
53
+ An Agent should:
54
+
55
+ 1. call `list_agent_interfaces`;
56
+ 2. resolve exact space, project, item and version identifiers with read tools;
57
+ 3. inspect the selected tool's current schema;
58
+ 4. read current state before a write;
59
+ 5. execute the narrowest scoped mutation with available compare-and-swap or idempotency fields;
60
+ 6. verify through a structured read.
61
+
62
+ The bundled `operating-fuli` Skill teaches this sequence and is installed by `fuli setup` for supported Agents. Browser automation is permitted only as optional visual verification after a structured operation, never as a data-mutation fallback.
63
+
64
+ ## Human-only boundaries
65
+
66
+ The following operations intentionally remain local-user-only:
67
+
68
+ - enabling or disabling Agent access;
69
+ - device-level runtime settings;
70
+ - completing a preference conflict as a human decision;
71
+ - confirming preference visibility scope;
72
+ - batch confirmation of knowledge.
73
+ - Jefa task completion that records explicit human acceptance (other task updates remain available through the employee tools).
74
+
75
+ These are trust boundaries, not missing interfaces. An Agent may explain where the user can perform the action, but must not substitute direct HTTP, storage, shell, or browser scripting.
76
+
77
+ ## External knowledge
78
+
79
+ Connector discovery, binding creation, readiness checks, target updates, scoped synchronization, live retrieval, conflict policy and deletion have dedicated tools. Connector secrets are referenced by environment-variable name; tool results and catalog listings must not expose secret values.
80
+
81
+ Moving a connection between projects updates the binding targets. Deleting and recreating a binding is not the default because it breaks audit continuity and can discard connector state.
82
+
83
+ Target updates require the Agent's last-read `targetsVersion`. Registry mutations take a cross-process SQLite reservation, so sync/check/delete cannot overwrite a concurrent scope update. Busy or stale writes fail with a controlled conflict and must be re-read, not blindly retried. The legacy HTTP contract still accepts callers without a version for compatibility; current console and Agent clients supply one.
84
+
85
+ ## Adding a feature
86
+
87
+ When a console feature adds a data mutation:
88
+
89
+ 1. implement the business operation in an application service;
90
+ 2. have the console route call that service;
91
+ 3. add or reuse a typed Agent definition and a handler that calls the same service;
92
+ 4. annotate read/write, destructive and open-world behavior for MCP clients;
93
+ 5. add the route-to-tool mapping to `src/agent-tools/interface-catalog.js`, or document a narrowly justified `local_user_only` boundary;
94
+ 6. add contract tests covering dispatch, scope, error behavior and the parity mapping;
95
+ 7. update the `operating-fuli` workflow reference if the operation needs a non-obvious sequence.
96
+
97
+ A missing Agent interface must remain visible as a parity defect. Do not silently ship a browser-only write path.
@@ -1,6 +1,6 @@
1
1
  # Specialist Agents
2
2
 
3
- An employee template is a reusable role with an optional FULI-hosted workbench. Recruitment reuses the existing durable Project Agent identity and assignment model. The first built-in template is **Jefa, project manager**; adding another employee does not require a host-side special case.
3
+ An employee template is a reusable role with an optional FULI-hosted or native workbench. Recruitment reuses the existing durable Project Agent identity and assignment model. The built-in specialists are **Jefa**, the project manager, and **Bole**, the fixed system HR Agent. Bole is created with every personal space, cannot be archived or replaced by another `hr` identity, and does not require a separate runtime package.
4
4
 
5
5
  ## User flow
6
6
 
@@ -16,7 +16,7 @@ The directory's **Filter projects** control supports multi-selection and inversi
16
16
 
17
17
  Repeat recruitment is idempotent. Customized profile preferences and executor/model policies are preserved. One employee identity can have multiple project assignments. Reactivating an inactive identity is an explicit action; a conflicting unrelated identity is never overwritten. Recruitment does not start an executor or constitute model usage.
18
18
 
19
- The catalog shows whether a workbench package is actually installed. Identity-only templates need no runtime and remain valid employees without a sidebar workbench.
19
+ The catalog shows whether a workbench package is actually installed. Identity-only templates need no runtime and remain valid employees without a sidebar workbench. A trusted built-in template may declare `workbench: { "kind": "native", "view": "people" }`; Bole uses this native panel to summarize Agent distribution, current tasks, and timestamped recruitment reasons from the same Project Agent APIs used elsewhere in the console.
20
20
 
21
21
  ## Local installation
22
22
 
@@ -71,8 +71,12 @@ Context includes `personalSpaceId`, `project`, `agentId`, and `basePath`; HTTP a
71
71
 
72
72
  Optional manifest fields `defaultProjectScope: "all" | "selected"` and `taskEntry: { "boardTool": "read_board", "titleTool": "prepare_session_title" }` declare defaults and task-entry participation without a template-specific host branch. Context also carries the trusted `management` policy and native `session` identity when available. Task-entry board tools must declare `board.read`; the host returns bounded data and guidance, not an automatically started worker. A failed employee runtime degrades manager context without breaking collaboration preferences.
73
73
 
74
+ The optional built-in-only native workbench declaration is `workbench: { "kind": "native", "view": "people" }`. It renders host-owned read models and does not load third-party code. Installable employee packages continue to use the runtime contract above.
75
+
74
76
  The reserved identity is `employee.<template-id>`, recognized by the generic `fuli.employee:<template-id>` capability. Preserve this marker when customizing employee profiles. The actual role, history, memory and project assignments remain in FULI's existing Agent model.
75
77
 
78
+ `employee.bole` is additionally reserved as the only system HR identity. Its Provider type is `hr`; other identities cannot claim that type. Recruitment history records the coordinating and HR Agent IDs, time, reason, proposed identity and final recruited identity, which powers Bole's people panel without inventing activity.
79
+
76
80
  ## HTTP and MCP
77
81
 
78
82
  The host serves `/api/employee-templates`, recruitment/workspace/tool endpoints below it, and `/employee-workspaces/<template-id>/<project-id>/…`. Existing Host/Origin, JSON-body and LAN authorization checks apply. All workbench requests require an active matching identity and authorized responsibility policy, including assets and Agent discovery. Selected mode additionally requires an active assignment; all mode includes future active projects but enforces explicit exclusions and ended assignments before reaching the runtime.
@@ -87,6 +91,42 @@ Jefa supplies eight workbench tools for board/task reading, task creation, optim
87
91
 
88
92
  ## Verification and release boundary
89
93
 
94
+ Jefa uses one native `EmployeeTaskBoard` for every project scope. `/employees/jefa` and
95
+ `?project=all` show the current managed projects; legacy `__all__` is normalized. A single
96
+ project query or repeated `project` query parameters filter the same board, without changing
97
+ assignments. Task details and creation use the existing `get_task`, `create_tasks`, and
98
+ version-checked `update_tasks` interfaces. Explicit human confirmation of `done` continues
99
+ through the existing human HTTP endpoint. Other employee packages retain their generic iframe
100
+ workspaces; Jefa no longer switches to the package board when a card is opened.
101
+
102
+ The external-report action beside Jefa's identity has an independent project selection,
103
+ including all, one, several, and inverted selection. Generating a link explicitly enables
104
+ sharing for selected projects through `PATCH /employee-workspaces/jefa/<project>/api/projects/<project>/sharing`
105
+ with `{ publicShareEnabled: true, acceptsPublicRequests: false }`. Merely opening or filtering
106
+ the dialog performs no writes. Sharing status comes from the existing scoped snapshot API;
107
+ disabling uses the same endpoint with both flags false and invalidates that project's old slug.
108
+
109
+ One `/reports/jefa` link aggregates the selected project capabilities in its URL fragment,
110
+ keeping them out of HTTP access logs and referrer query strings. The report loads only
111
+ `GET /employee-workspaces/jefa/<project>/api/public/<slug>` and uses the same board in read-only
112
+ mode. It never loads the console shell or private board/task endpoints. The existing public
113
+ projection controls visibility: private tasks, details, evidence, and unrelated projects are
114
+ not included. Partial enablement is reported without claiming a complete link, and selection
115
+ changes clear the previously generated link. “All” is the current selection, not consent to
116
+ publish future projects automatically.
117
+
118
+ Before presenting a report link, the dialog verifies the selected public projections and
119
+ shows their task count. A zero-task selection is not presented as a successful report;
120
+ existing empty links explain that no tasks have been approved for public viewing. This
121
+ never changes task privacy or substitutes private board data for a public projection.
122
+ Refreshing the preview preserves the selected projects.
123
+
124
+ These structured HTTP operations remain subject to the host's existing scope, Origin and LAN
125
+ authentication checks. The link is not a separate authorization layer for the entire console.
126
+ Loopback links work only on the same computer; this UI does not enable LAN access, tunnels,
127
+ production hosting, or new public task visibility. Do not expose the local management service
128
+ as a public multi-user report server.
129
+
90
130
  Run FULI's normal Node/web tests and typecheck. For an actual Jefa build:
91
131
 
92
132
  ```sh
@@ -97,3 +137,19 @@ node scripts/employee-package-smoke.js <built-package-directory> --serve
97
137
  The smoke fixture uses a synthetic FULI directory but the real installed employee bundle, SQLite and MCP transport. The UI mode does not query a personal provider or external model. It deletes its temporary test data on shutdown.
98
138
 
99
139
  Signing, remote catalog distribution, a runtime sandbox, database migration/uninstall UX, and production multi-user authorization remain separate release work. This feature does not authorize or publish a Feishu application.
140
+
141
+ ### Bole identity upgrades
142
+
143
+ Local startup calls the idempotent system-HR endpoint for both fresh and existing installs.
144
+ It consolidates the reserved legacy `fuli-project-hr` identity into `employee.bole` in one
145
+ transaction. Names never identify merge targets. The legacy node remains an audit alias;
146
+ assignments, recruitment references, attention and task history follow the canonical Bole.
147
+ Old profile links resolve to Bole, and the obsolete ID cannot be recreated as another role.
148
+ Working-memory heads and immutable checkpoint JSON/hashes are retained; equivalent old/new-ID
149
+ memory retries address the same checkpoint. Knowledge visibility metadata is migrated only
150
+ inside the owning space, without changing historical evidence UUIDs.
151
+
152
+ Conflicting memory heads, locked executor policies, relationship properties or overlapping
153
+ learning buckets abort the complete migration. Nothing is silently selected or overwritten.
154
+ The Neo4j acceptance tests require an explicitly disposable loopback database; they never
155
+ clear or seed an existing user's graph.
@@ -42,7 +42,7 @@ async def read_collaboration_context(
42
42
  status_code=422,
43
43
  detail='project Agent preferences require a personal project',
44
44
  )
45
- await authorize_project_agent(
45
+ agent = await authorize_project_agent(
46
46
  store,
47
47
  actor,
48
48
  space,
@@ -50,6 +50,7 @@ async def read_collaboration_context(
50
50
  project_agent_id,
51
51
  require_memory=True,
52
52
  )
53
+ project_agent_id = agent['agent_id']
53
54
  project_scopes = await _project_scopes(
54
55
  store,
55
56
  space,
@@ -17,7 +17,7 @@ from .models import (
17
17
  KnowledgeItemKind,
18
18
  KnowledgeRevisionRecord,
19
19
  PersonalProfileAspect,
20
- PreferenceScope,
20
+ ScopedPreferenceScope,
21
21
  StrictModel,
22
22
  )
23
23
  from .project_action_models import (
@@ -44,8 +44,9 @@ class GraphNode(StrictModel):
44
44
  confirmation_basis: ConfirmationBasis | None = None
45
45
  reasoning_summary: str | None = None
46
46
  profile_aspect: PersonalProfileAspect | None = None
47
- preference_scope: PreferenceScope | None = None
47
+ preference_scope: ScopedPreferenceScope | None = None
48
48
  preference_project_id: str | None = None
49
+ preference_agent_id: str | None = None
49
50
  inheritance_mode: KnowledgeInheritanceMode = 'local_only'
50
51
  inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
51
52
  human_edited: bool = False
@@ -97,8 +98,9 @@ class GraphEdge(StrictModel):
97
98
  confirmation_basis: ConfirmationBasis | None = None
98
99
  reasoning_summary: str | None = None
99
100
  profile_aspect: PersonalProfileAspect | None = None
100
- preference_scope: PreferenceScope | None = None
101
+ preference_scope: ScopedPreferenceScope | None = None
101
102
  preference_project_id: str | None = None
103
+ preference_agent_id: str | None = None
102
104
  inheritance_mode: KnowledgeInheritanceMode = 'local_only'
103
105
  inherited_project_ids: list[str] = Field(default_factory=list, max_length=32)
104
106
  human_edited: bool = False
@@ -216,6 +216,7 @@ def _node_query(project_scoped: bool, paginated: bool = False) -> str:
216
216
  ELSE coalesce(node.fuli_preference_scope, 'global') END
217
217
  AS preference_scope,
218
218
  node.fuli_preference_project_id AS preference_project_id,
219
+ node.fuli_preference_agent_id AS preference_agent_id,
219
220
  coalesce(node.fuli_inheritance_mode, 'local_only')
220
221
  AS inheritance_mode,
221
222
  coalesce(node.fuli_inherited_project_ids, [])
@@ -331,6 +332,7 @@ def _edge_query(project_scoped: bool, paginated: bool = False) -> str:
331
332
  ELSE coalesce(edge.fuli_preference_scope, 'global') END
332
333
  AS preference_scope,
333
334
  edge.fuli_preference_project_id AS preference_project_id,
335
+ edge.fuli_preference_agent_id AS preference_agent_id,
334
336
  coalesce(edge.fuli_inheritance_mode, 'local_only')
335
337
  AS inheritance_mode,
336
338
  coalesce(edge.fuli_inherited_project_ids, [])
@@ -499,6 +501,7 @@ def _graph_node(
499
501
  profile_aspect=record.get('profile_aspect'),
500
502
  preference_scope=record.get('preference_scope'),
501
503
  preference_project_id=record.get('preference_project_id'),
504
+ preference_agent_id=record.get('preference_agent_id'),
502
505
  inheritance_mode=record.get('inheritance_mode') or 'local_only',
503
506
  inherited_project_ids=record.get('inherited_project_ids') or [],
504
507
  human_edited=record.get('human_edited') is True,
@@ -580,6 +583,7 @@ def _graph_edge(
580
583
  profile_aspect=record.get('profile_aspect'),
581
584
  preference_scope=record.get('preference_scope'),
582
585
  preference_project_id=record.get('preference_project_id'),
586
+ preference_agent_id=record.get('preference_agent_id'),
583
587
  inheritance_mode=record.get('inheritance_mode') or 'local_only',
584
588
  inherited_project_ids=record.get('inherited_project_ids') or [],
585
589
  human_edited=record.get('human_edited') is True,
@@ -317,7 +317,7 @@ async def _personal_search_space(store, actor, spaces, request):
317
317
  for project_id in request.personal_project_ids:
318
318
  await authorize_personal_project(store, actor, space, project_id)
319
319
  if request.project_agent_id:
320
- await authorize_project_agent(
320
+ agent = await authorize_project_agent(
321
321
  store,
322
322
  actor,
323
323
  space,
@@ -326,6 +326,7 @@ async def _personal_search_space(store, actor, spaces, request):
326
326
  require_active=True,
327
327
  require_memory=True,
328
328
  )
329
+ request.project_agent_id = agent['agent_id']
329
330
  return space
330
331
 
331
332
 
@@ -1,6 +1,7 @@
1
1
  from fastapi import HTTPException
2
2
 
3
3
  from .personal_project_access import authorize_personal_project
4
+ from .system_hr_identity import resolve_hr_alias
4
5
 
5
6
 
6
7
  async def authorize_project_agent(
@@ -20,6 +21,7 @@ async def authorize_project_agent(
20
21
  detail='project Agent access requires a personal project',
21
22
  )
22
23
  await authorize_personal_project(store, actor, space, project_id)
24
+ agent_id = await resolve_hr_alias(store, space['id'], agent_id)
23
25
  records, _, _ = await store.runtime.driver.execute_query(
24
26
  '''
25
27
  MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})-
@@ -0,0 +1,64 @@
1
+ from datetime import datetime
2
+ from typing import Literal
3
+
4
+ from pydantic import Field, model_validator
5
+
6
+ from .model_base import StrictModel
7
+ from .model_validation import reject_credentials
8
+
9
+
10
+ AttentionStatus = Literal['open', 'resolved', 'cancelled']
11
+
12
+
13
+ class AgentAttentionCreate(StrictModel):
14
+ personal_space_id: str = Field(min_length=1, max_length=128)
15
+ personal_project_id: str = Field(min_length=1, max_length=128)
16
+ agent_id: str = Field(min_length=1, max_length=128)
17
+ task_id: str | None = Field(default=None, min_length=1, max_length=128)
18
+ idempotency_key: str = Field(min_length=8, max_length=256)
19
+ kind: Literal['question', 'approval', 'review', 'permission', 'blocked']
20
+ title: str = Field(min_length=1, max_length=160)
21
+ detail: str = Field(min_length=1, max_length=4096)
22
+ requested_action: str = Field(min_length=1, max_length=2048)
23
+
24
+ @model_validator(mode='after')
25
+ def reject_sensitive_values(self):
26
+ reject_credentials(self, 'Agent attention request')
27
+ return self
28
+
29
+
30
+ class AgentAttentionDecision(StrictModel):
31
+ personal_space_id: str = Field(min_length=1, max_length=128)
32
+ personal_project_id: str = Field(min_length=1, max_length=128)
33
+ request_id: str = Field(min_length=1, max_length=128)
34
+ expected_revision: int = Field(ge=0)
35
+ response: str = Field(min_length=1, max_length=4096)
36
+
37
+ @model_validator(mode='after')
38
+ def reject_sensitive_values(self):
39
+ reject_credentials(self, 'Agent attention response')
40
+ return self
41
+
42
+
43
+ class AgentAttentionRecord(StrictModel):
44
+ request_id: str
45
+ personal_space_id: str
46
+ personal_project_id: str
47
+ agent_id: str
48
+ task_id: str | None = None
49
+ kind: str
50
+ title: str
51
+ detail: str
52
+ requested_action: str
53
+ status: AttentionStatus = 'open'
54
+ revision: int = 0
55
+ response: str | None = None
56
+ responded_by: Literal['human', 'agent'] | None = None
57
+ created_at: datetime
58
+ updated_at: datetime
59
+
60
+
61
+ class AgentAttentionList(StrictModel):
62
+ items: list[AgentAttentionRecord]
63
+ total: int
64
+ counts: dict[str, int]
@@ -0,0 +1,38 @@
1
+ from typing import Annotated, Any
2
+
3
+ from fastapi import Query
4
+
5
+ from .project_agent_attention_models import (
6
+ AgentAttentionCreate, AgentAttentionDecision, AgentAttentionList,
7
+ AgentAttentionRecord, AttentionStatus,
8
+ )
9
+
10
+
11
+ def register_project_agent_attention_routes(application, store, Actor: Any):
12
+ @application.post('/v1/agent-attention', response_model=AgentAttentionRecord)
13
+ async def create(request: AgentAttentionCreate, actor: Actor):
14
+ return await store.create_agent_attention(actor, request)
15
+
16
+ @application.get('/v1/agent-attention', response_model=AgentAttentionList)
17
+ async def list_requests(
18
+ actor: Actor,
19
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
20
+ personal_project_id: Annotated[str | None, Query(min_length=1, max_length=128)] = None,
21
+ agent_id: Annotated[str | None, Query(min_length=1, max_length=128)] = None,
22
+ status: AttentionStatus | None = 'open',
23
+ limit: Annotated[int, Query(ge=1, le=200)] = 100,
24
+ offset: Annotated[int, Query(ge=0)] = 0,
25
+ ):
26
+ return await store.list_agent_attention(
27
+ actor, personal_space_id, personal_project_id, agent_id, status, limit, offset,
28
+ )
29
+
30
+ @application.post('/v1/agent-attention/cancel', response_model=AgentAttentionRecord)
31
+ async def cancel(request: AgentAttentionDecision, actor: Actor):
32
+ return await store.decide_agent_attention(actor, request, human=False)
33
+
34
+ @application.post('/v1/agent-attention/respond', response_model=AgentAttentionRecord)
35
+ async def respond(request: AgentAttentionDecision, actor: Actor):
36
+ # Only the trusted local-user HTTP workflow exposes this operation.
37
+ # A reply does not execute an approval, permission change or acceptance.
38
+ return await store.decide_agent_attention(actor, request, human=True)
@@ -90,6 +90,7 @@ class ProjectAgentExecutorPolicy(StrictModel):
90
90
 
91
91
  class ProjectAgentProfile(StrictModel):
92
92
  name: str = Field(min_length=1, max_length=160)
93
+ display_name: str | None = Field(default=None, min_length=1, max_length=160)
93
94
  # Keep the visual occupation marker as first-class profile data. It is
94
95
  # intentionally independent from ``name`` so clients can update either
95
96
  # value without rewriting the other and old profiles can omit it.
@@ -266,6 +267,7 @@ class ProjectAgentAssignmentReplaceResult(StrictModel):
266
267
 
267
268
  class ProjectAgentRecord(StrictModel):
268
269
  agent_id: str
270
+ legacy_agent_ids: list[str] = Field(default_factory=list)
269
271
  personal_space_id: str
270
272
  personal_project_id: str | None = None
271
273
  profile: ProjectAgentProfile
@@ -0,0 +1,33 @@
1
+ """Stable product nicknames, independent of identity, assignment and work status."""
2
+ import hashlib
3
+ import re
4
+
5
+
6
+ FIRST_NAMES = (
7
+ 'Milo', 'Nova', 'Arlo', 'Nora', 'Theo', 'Iris', 'Remy', 'Lena',
8
+ 'Finn', 'Cleo', 'Ezra', 'Mira', 'Jude', 'Lyra', 'Owen', 'Cora',
9
+ 'Alex', 'Robin', 'Jamie', 'Rowan', 'Casey', 'Avery', 'Morgan', 'Riley',
10
+ 'Ellis', 'Quinn', 'Sage', 'Blair', 'Reese', 'Emery', 'Drew', 'Sky',
11
+ )
12
+ LAST_NAMES = (
13
+ 'Reed', 'Lane', 'Vale', 'Brook', 'Wren', 'Stone', 'Park', 'Finch',
14
+ 'Lake', 'Ash', 'Birch', 'Hill', 'West', 'Woods', 'Dale', 'Hart',
15
+ 'Gray', 'Bell', 'Hayes', 'Blake', 'Lowe', 'Frost', 'Green', 'Page',
16
+ 'Wells', 'Shaw', 'Fox', 'Snow', 'Field', 'North', 'Ray', 'Cole',
17
+ )
18
+ ROLE_NAME = re.compile(
19
+ r'(工程师|协调人|开发者|设计师|架构师|审计员|项目经理|负责人|专员|助手|智能体|验证员|测试员|记录员)'
20
+ r'|\b(agent|engineer|coordinator|specialist|developer|reviewer|tester|verifier|auditor|designer|architect|implementer)\b',
21
+ re.IGNORECASE,
22
+ )
23
+
24
+
25
+ def agent_display_name(agent_id: str, name: str, display_name: str | None = None):
26
+ if display_name:
27
+ return display_name
28
+ if agent_id == 'fuli-project-coordinator' and name == '项目协调人':
29
+ return 'Orion'
30
+ if not ROLE_NAME.search(name):
31
+ return name
32
+ digest = hashlib.sha256(agent_id.encode()).digest()
33
+ return f'{FIRST_NAMES[digest[0] % len(FIRST_NAMES)]} {LAST_NAMES[digest[1] % len(LAST_NAMES)]}'
@@ -60,6 +60,8 @@ def register_project_agent_routes(
60
60
  """Attach Project Agent routes while keeping ``create_app`` orchestration small."""
61
61
 
62
62
  register_project_agent_memory_routes(application, store, Actor)
63
+ from .project_agent_attention_routes import register_project_agent_attention_routes
64
+ register_project_agent_attention_routes(application, store, Actor)
63
65
 
64
66
  @application.put('/v1/project-agents', response_model=ProjectAgentRecord)
65
67
  async def upsert_project_agent(
@@ -137,6 +139,16 @@ def register_project_agent_routes(
137
139
  ) -> ProjectAgentRecord:
138
140
  return await store.ensure_system_project_coordinator(actor, personal_space_id)
139
141
 
142
+ @application.post(
143
+ '/v1/project-agents/system-hr',
144
+ response_model=ProjectAgentRecord,
145
+ )
146
+ async def ensure_project_agent_hr(
147
+ actor: Actor,
148
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
149
+ ) -> ProjectAgentRecord:
150
+ return await store.ensure_system_project_hr(actor, personal_space_id)
151
+
140
152
  @application.post(
141
153
  '/v1/project-agent-assignments',
142
154
  response_model=ProjectAgentAssignmentRecord,
@@ -271,6 +271,8 @@ class GraphitiRuntime:
271
271
  for query in (
272
272
  'CREATE CONSTRAINT fuli_project_agent_id IF NOT EXISTS '
273
273
  'FOR (n:FuliProjectAgent) REQUIRE n.id IS UNIQUE',
274
+ 'CREATE CONSTRAINT fuli_agent_attention_id IF NOT EXISTS '
275
+ 'FOR (n:FuliAgentAttention) REQUIRE n.id IS UNIQUE',
274
276
  'CREATE CONSTRAINT fuli_project_agent_assignment_id IF NOT EXISTS '
275
277
  'FOR (n:FuliProjectAgentAssignment) REQUIRE n.id IS UNIQUE',
276
278
  'CREATE CONSTRAINT fuli_project_agent_memory_id IF NOT EXISTS '
@@ -516,7 +518,7 @@ class GraphitiRuntime:
516
518
  from .project_agent_models import ProjectAgentProfile
517
519
  from .provider_values import now_utc, stable_uuid
518
520
 
519
- profile = ProjectAgentProfile(
521
+ coordinator_profile = ProjectAgentProfile(
520
522
  name='项目协调人',
521
523
  responsibility='评估任务、应用用户配置并路由已有 Agent。',
522
524
  agent_type='coordinator',
@@ -525,26 +527,50 @@ class GraphitiRuntime:
525
527
  initial_preferences=['质量与可验收完成优先于成本和时间'],
526
528
  status='active',
527
529
  )
530
+ hr_profile = ProjectAgentProfile(
531
+ name='Bole',
532
+ occupation_emoji='🔎',
533
+ responsibility=(
534
+ '维护 Agent 人员分布、当前工作与可审计招募记录,'
535
+ '并在需要新角色时执行受策略约束的招募。'
536
+ ),
537
+ agent_type='hr',
538
+ work_kinds=['agent-recruitment', 'staffing-review'],
539
+ capabilities=[
540
+ 'Agent 招募', '人员分布', '工作状态', '招募审计',
541
+ 'fuli.employee:bole',
542
+ ],
543
+ initial_preferences=[
544
+ '每次招募保留任务、原因、触发来源与时间线。',
545
+ '只展示 Provider 已记录的人员状态,不推测未上报的工作。',
546
+ ],
547
+ status='active',
548
+ )
528
549
  updated_at = now_utc()
529
550
  for record in records:
530
551
  space_id = record['space_id']
531
- node_id = stable_uuid(
532
- self.settings.provider_id,
533
- space_id,
534
- 'project-agent',
535
- 'fuli-project-coordinator',
536
- )
537
- await self.driver.execute_query(
538
- '''
552
+ for agent_id, profile in (
553
+ ('fuli-project-coordinator', coordinator_profile),
554
+ ('employee.bole', hr_profile),
555
+ ):
556
+ node_id = stable_uuid(
557
+ self.settings.provider_id,
558
+ space_id,
559
+ 'project-agent',
560
+ agent_id,
561
+ )
562
+ await self.driver.execute_query(
563
+ '''
539
564
  MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
540
565
  MERGE (agent:FuliProjectAgent {id: $id})
541
- ON CREATE SET agent.agent_id = 'fuli-project-coordinator',
566
+ ON CREATE SET agent.agent_id = $agent_id,
542
567
  agent.profile_json = $profile_json,
543
568
  agent.name = $name,
569
+ agent.occupation_emoji = $occupation_emoji,
544
570
  agent.responsibility = $responsibility,
545
571
  agent.capabilities = $capabilities,
546
572
  agent.work_kinds = $work_kinds,
547
- agent.agent_type = 'coordinator',
573
+ agent.agent_type = $agent_type,
548
574
  agent.memory_scope = 'reviewed_agent',
549
575
  agent.status = 'active',
550
576
  agent.system_managed = true,
@@ -552,15 +578,18 @@ class GraphitiRuntime:
552
578
  agent.updated_at = $updated_at
553
579
  MERGE (space)-[:HAS_PROJECT_AGENT_IDENTITY]->(agent)
554
580
  ''',
555
- space_id=space_id,
556
- id=node_id,
557
- profile_json=profile.model_dump_json(),
558
- name=profile.name,
559
- responsibility=profile.responsibility,
560
- capabilities=profile.capabilities,
561
- work_kinds=profile.work_kinds,
562
- updated_at=updated_at,
563
- )
581
+ space_id=space_id,
582
+ id=node_id,
583
+ agent_id=agent_id,
584
+ profile_json=profile.model_dump_json(),
585
+ name=profile.name,
586
+ occupation_emoji=profile.occupation_emoji,
587
+ responsibility=profile.responsibility,
588
+ capabilities=profile.capabilities,
589
+ work_kinds=profile.work_kinds,
590
+ agent_type=profile.agent_type,
591
+ updated_at=updated_at,
592
+ )
564
593
 
565
594
  async def _migrate_personal_project_relation_review_defaults(self) -> None:
566
595
  await self.driver.execute_query(
@@ -7,6 +7,7 @@ from .store_project_agent_executors import StoreProjectAgentExecutors
7
7
  from .store_project_agent_memory import StoreProjectAgentMemory
8
8
  from .store_project_agent_context import StoreProjectAgentContext
9
9
  from .store_project_agents import StoreProjectAgents
10
+ from .store_project_agent_attention import StoreProjectAgentAttention
10
11
  from .store_project_agent_tasks import StoreProjectAgentTasks
11
12
  from .store_projects import StoreProjects
12
13
  from .store_publication import StorePublication
@@ -18,6 +19,7 @@ class GraphStore(
18
19
  StoreIdentity,
19
20
  StoreProjects,
20
21
  StoreProjectAgents,
22
+ StoreProjectAgentAttention,
21
23
  StoreProjectAgentMemory,
22
24
  StoreProjectAgentContext,
23
25
  StoreTaskContexts,