fuli-context 0.7.10-beta.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/README.zh-CN.md +5 -3
- package/dist/web/assets/{AboutPage-BvXH4hsO.js → AboutPage-BjMmpwJB.js} +1 -1
- package/dist/web/assets/AboutPage-vYIBifKw.css +1 -0
- package/dist/web/assets/AgentHand-BfLYPGjg.js +1 -0
- package/dist/web/assets/AgentHand-D9sQu2vc.css +1 -0
- package/dist/web/assets/ConnectionsPage-BKGxFDos.js +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-B59X3rOi.css +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-D-W043v4.js +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-Cz7y-IFD.css +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-HHQPxdqI.js +3 -0
- package/dist/web/assets/GrowthLoading-B_Hh0CRS.css +1 -0
- package/dist/web/assets/GrowthLoading-WXCj8b0r.js +1 -0
- package/dist/web/assets/KnowledgeEditDialog-Bkie08ZA.js +1 -0
- package/dist/web/assets/KnowledgeEditDialog-D9NuRHvp.css +1 -0
- package/dist/web/assets/KnowledgeInspector-Cez-Z_95.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BYmTPA_8.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-h2iKvQpO.css +1 -0
- package/dist/web/assets/KnowledgePage-Ck7tIBDn.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DKij2_B4.js +4 -0
- package/dist/web/assets/KnowledgeWorkspace-sb8OJ97I.css +1 -0
- package/dist/web/assets/NotFoundPage-HGV-q-bh.js +1 -0
- package/dist/web/assets/{OverviewPage-D4vLhIFD.js → OverviewPage-DE29_F_Y.js} +1 -1
- package/dist/web/assets/PersonalProfilePage-B95DFc7x.js +1 -0
- package/dist/web/assets/PersonalProfilePage-L8T5xMEC.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-Bt7tFF5Z.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-BioDR9OW.css +1 -0
- package/dist/web/assets/ProjectAgentsPage-CiiPNe_G.js +12 -0
- package/dist/web/assets/ProjectScopePicker-BvaGTMFx.css +1 -0
- package/dist/web/assets/ProjectScopePicker-Cu5PdxF6.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BY4B3_t3.js +1 -0
- package/dist/web/assets/ReviewPage-C9vCesNu.js +1 -0
- package/dist/web/assets/{SearchableSelect-DY-TWC2i.js → SearchableSelect-214TvfYg.js} +1 -1
- package/dist/web/assets/SettingsPage-Brl-SL33.css +1 -0
- package/dist/web/assets/SettingsPage-D9TnzcFc.js +1 -0
- package/dist/web/assets/SharedJefaReportPage-CdSGI_OE.js +1 -0
- package/dist/web/assets/SharedJefaReportPage-DXbj_JhO.css +1 -0
- package/dist/web/assets/WritingTastePage-Bzg6AvnB.css +1 -0
- package/dist/web/assets/WritingTastePage-CUE1dU8f.js +1 -0
- package/dist/web/assets/bole-avatar-CVIr-bU_.webp +0 -0
- package/dist/web/assets/client-Bl4oiW3E.js +1 -0
- package/dist/web/assets/console-BAIVNC-N.js +1 -0
- package/dist/web/assets/conversation-launcher-settings-DpRI_Jp2.js +1 -0
- package/dist/web/assets/employee-dialog-Bh5ccIbK.css +1 -0
- package/dist/web/assets/{i18n-C7r_00q2.js → i18n-CVFADb_M.js} +3 -3
- package/dist/web/assets/{identity-COvBmUF4.js → identity-Dt0WZgqo.js} +1 -1
- package/dist/web/assets/index-BUMq5zNh.js +2 -0
- package/dist/web/assets/index-C0ci8dnU.css +1 -0
- package/dist/web/assets/{model-CL1JILt4.js → model-B8sOxih0.js} +1 -1
- package/dist/web/assets/personal-profile-graph-aAqex7ty.js +1 -0
- package/dist/web/assets/shared-report-BQD6l_cS.js +1 -0
- package/dist/web/assets/shared-report-ezeb8dW4.css +1 -0
- package/dist/web/assets/{useApi-CROJJdhE-DERnOR_k.js → useApi-CROJJdhE-CuJ16v2S.js} +1 -1
- package/dist/web/assets/useModalDialog-Ddv2XJQi.js +1 -0
- package/dist/web/assets/{vue-router-DJ6C6-8v.js → vue-router-C8KuEZ6a.js} +1 -1
- package/dist/web/index.html +14 -8
- package/docs/agent-interface-architecture.md +97 -0
- package/docs/employee-agents.md +58 -2
- package/docs/project-agent-memory.md +69 -449
- package/graph-provider/fuli_graph/app.py +2 -0
- package/graph-provider/fuli_graph/collaboration_context.py +2 -1
- package/graph-provider/fuli_graph/graph_models.py +5 -3
- package/graph-provider/fuli_graph/graph_query.py +4 -0
- package/graph-provider/fuli_graph/knowledge_search.py +2 -1
- package/graph-provider/fuli_graph/preference_conflicts.py +4 -1
- package/graph-provider/fuli_graph/project_agent_access.py +2 -0
- package/graph-provider/fuli_graph/project_agent_attention_models.py +64 -0
- package/graph-provider/fuli_graph/project_agent_attention_routes.py +38 -0
- package/graph-provider/fuli_graph/project_agent_coordination_models.py +23 -1
- package/graph-provider/fuli_graph/project_agent_memory_models.py +11 -0
- package/graph-provider/fuli_graph/project_agent_models.py +2 -0
- package/graph-provider/fuli_graph/project_agent_names.py +33 -0
- package/graph-provider/fuli_graph/project_agent_routes.py +12 -0
- package/graph-provider/fuli_graph/project_agent_team_growth.py +56 -0
- package/graph-provider/fuli_graph/project_agent_work_log.py +34 -0
- package/graph-provider/fuli_graph/runtime.py +49 -20
- package/graph-provider/fuli_graph/store.py +2 -0
- package/graph-provider/fuli_graph/store_knowledge.py +4 -2
- package/graph-provider/fuli_graph/store_project_agent_attention.py +138 -0
- package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +56 -2
- package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +8 -0
- package/graph-provider/fuli_graph/store_project_agent_memory.py +43 -13
- package/graph-provider/fuli_graph/store_project_agent_task_activity.py +1 -1
- package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +6 -1
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +21 -3
- package/graph-provider/fuli_graph/store_project_agent_tasks.py +4 -0
- package/graph-provider/fuli_graph/store_project_agents.py +109 -0
- package/graph-provider/fuli_graph/store_task_contexts.py +12 -2
- package/graph-provider/fuli_graph/system_hr_identity.py +218 -0
- package/graph-provider/fuli_graph/task_context_adoption.py +65 -0
- package/graph-provider/fuli_graph/task_context_models.py +23 -1
- package/graph-provider/fuli_graph/task_context_routes.py +5 -1
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +52 -60
- package/package.json +2 -2
- package/skills/operating-fuli/SKILL.md +47 -0
- package/skills/operating-fuli/agents/openai.yaml +4 -0
- package/skills/operating-fuli/references/workflow-recipes.md +33 -0
- package/src/agent-tools/agent-attention-definitions.js +33 -0
- package/src/agent-tools/graph-definitions.js +22 -7
- package/src/agent-tools/graph-handlers.js +41 -0
- package/src/agent-tools/interface-catalog.js +101 -0
- package/src/agent-tools/interface-definitions.js +184 -0
- package/src/agent-tools/project-agent-definitions.js +2 -0
- package/src/agents/codex/lifecycle-hook.js +1 -0
- package/src/cli/local-runtime-command.js +2 -1
- package/src/cli/setup-command.js +1 -1
- package/src/employees/catalog/bole.json +19 -0
- package/src/employees/install-package.js +3 -0
- package/src/employees/manifest.js +4 -0
- package/src/employees/package-registry.js +2 -0
- package/src/employees/recruitment.js +3 -0
- package/src/employees/service.js +10 -0
- package/src/external-knowledge/registry.js +24 -0
- package/src/external-knowledge/service.js +34 -1
- package/src/graphiti/agent-attention.js +26 -0
- package/src/graphiti/agent-knowledge-workflows.js +14 -4
- package/src/graphiti/project-agent-control-plane.js +30 -0
- package/src/graphiti/project-agent-mapping.js +18 -1
- package/src/graphiti/project-agent-memory.js +10 -2
- package/src/graphiti/project-agent-task-entry.js +4 -1
- package/src/graphiti/project-agent-team-context.js +21 -0
- package/src/graphiti/project-agent-workflows.js +18 -0
- package/src/graphiti/provider-client.js +20 -1
- package/src/graphiti/workspace-provider-client.js +9 -1
- package/src/graphiti/writing-taste-profile-workflow.js +62 -9
- package/src/graphiti/writing-taste-profile.js +43 -16
- package/src/http/agent-attention-api-router.js +28 -0
- package/src/http/error-mapping.js +3 -0
- package/src/http/graph-api-router.js +11 -0
- package/src/local-runtime/lifecycle.js +8 -2
- package/src/mcp/create-mcp-server.js +15 -4
- package/src/mcp/instructions.js +3 -3
- package/src/mcp/provider-task-context-registry.js +11 -1
- package/src/mcp/task-context-registry.js +2 -1
- package/src/mcp/tool-annotations.js +31 -0
- package/src/mcp/tool-result.js +11 -7
- package/src/setup/agent-installation-status.js +2 -1
- package/src/setup/agents.js +4 -0
- package/src/setup/graph-runtime.js +4 -0
- package/src/setup/paths.js +2 -1
- package/src/setup/setup.js +4 -2
- package/src/setup/uninstall.js +4 -2
- package/src/system/resource-monitor.js +11 -1
- package/src/system/runtime-settings.js +7 -0
- package/dist/web/assets/AboutPage-DXh7Fwxt.css +0 -1
- package/dist/web/assets/ConnectionsPage-WVc6Srbd.js +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-CnA7rOnB.css +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-Dal5wy8a.js +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-BDJvrpcn.css +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-CW5pu_Yb.js +0 -1
- package/dist/web/assets/KnowledgeEditDialog-BBvm74ln.js +0 -1
- package/dist/web/assets/KnowledgeEditDialog-iv-dgUeD.css +0 -1
- package/dist/web/assets/KnowledgeInspector-CE9ADmtT.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-BRtizGUS.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-Cx1w1jb2.css +0 -1
- package/dist/web/assets/KnowledgePage-D5clXGCw.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-CKLH6cIf.js +0 -4
- package/dist/web/assets/PersonalProfilePage-7w8rAzkg.js +0 -1
- package/dist/web/assets/PersonalProfilePage-B7T09bow.css +0 -1
- package/dist/web/assets/PersonalProjectsPage-BY0B1t24.js +0 -1
- package/dist/web/assets/ProjectAgentsPage-C3vW6wmk.css +0 -1
- package/dist/web/assets/ProjectAgentsPage-LHPOuLWw.js +0 -11
- package/dist/web/assets/PublicProjectsPage-PSp6RzVZ.js +0 -1
- package/dist/web/assets/ReviewPage-DPP23xLF.js +0 -1
- package/dist/web/assets/SettingsPage-C59OPsNP.js +0 -1
- package/dist/web/assets/SettingsPage-CwZp2ka4.css +0 -1
- package/dist/web/assets/WritingTastePage-DNXhCdgn.js +0 -1
- package/dist/web/assets/WritingTastePage-DSgvTH6x.css +0 -1
- package/dist/web/assets/avatars-BfZueqlm.js +0 -1
- package/dist/web/assets/console-CYXvhGCR.js +0 -1
- package/dist/web/assets/conversation-launcher-settings-SwmUP91W.js +0 -1
- package/dist/web/assets/index-B-rXoqYP.js +0 -2
- package/dist/web/assets/index-Ch1jmkyO.css +0 -1
|
@@ -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
|
|
|
@@ -92,6 +92,7 @@ async def list_preference_conflicts(
|
|
|
92
92
|
*,
|
|
93
93
|
status: str | None = None,
|
|
94
94
|
limit: int = 500,
|
|
95
|
+
offset: int = 0,
|
|
95
96
|
) -> list[PreferenceConflictRecord]:
|
|
96
97
|
await _authorize_personal_space(
|
|
97
98
|
store,
|
|
@@ -105,12 +106,14 @@ async def list_preference_conflicts(
|
|
|
105
106
|
[:HAS_PREFERENCE_CONFLICT]->(conflict:FuliPreferenceConflict)
|
|
106
107
|
WHERE $status IS NULL OR conflict.status = $status
|
|
107
108
|
RETURN conflict
|
|
108
|
-
ORDER BY conflict.updated_at DESC
|
|
109
|
+
ORDER BY conflict.updated_at DESC, conflict.id ASC
|
|
110
|
+
SKIP $offset
|
|
109
111
|
LIMIT $limit
|
|
110
112
|
''',
|
|
111
113
|
space_id=personal_space_id,
|
|
112
114
|
status=status,
|
|
113
115
|
limit=min(max(limit, 1), 1000),
|
|
116
|
+
offset=max(offset, 0),
|
|
114
117
|
routing_='r',
|
|
115
118
|
)
|
|
116
119
|
return [_record(record['conflict']) for record in records]
|
|
@@ -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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
|
|
3
|
-
from pydantic import Field
|
|
3
|
+
from pydantic import Field, field_validator, model_validator
|
|
4
4
|
|
|
5
5
|
from .models import StrictModel
|
|
6
6
|
|
|
@@ -12,6 +12,9 @@ class ProjectAgentCoordinationPolicyRecord(StrictModel):
|
|
|
12
12
|
personal_project_id: str
|
|
13
13
|
ask_before_recruitment: bool = True
|
|
14
14
|
auto_reuse_previous_agent: bool = True
|
|
15
|
+
auto_grow_team: bool = True
|
|
16
|
+
team_lead_agent_id: str | None = None
|
|
17
|
+
team_member_agent_ids: list[str] = Field(default_factory=list)
|
|
15
18
|
updated_at: datetime | None = None
|
|
16
19
|
|
|
17
20
|
|
|
@@ -20,3 +23,22 @@ class ProjectAgentCoordinationPolicyUpdate(StrictModel):
|
|
|
20
23
|
personal_project_id: str = Field(min_length=1, max_length=128)
|
|
21
24
|
ask_before_recruitment: bool = True
|
|
22
25
|
auto_reuse_previous_agent: bool = True
|
|
26
|
+
auto_grow_team: bool | None = None
|
|
27
|
+
team_lead_agent_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
28
|
+
team_member_agent_ids: list[str] = Field(default_factory=list, max_length=32)
|
|
29
|
+
expected_updated_at: datetime | None = None
|
|
30
|
+
|
|
31
|
+
@field_validator('team_member_agent_ids')
|
|
32
|
+
@classmethod
|
|
33
|
+
def validate_members(cls, value):
|
|
34
|
+
if any(not item.strip() or len(item) > 128 for item in value):
|
|
35
|
+
raise ValueError('team member IDs must contain 1 to 128 characters')
|
|
36
|
+
if len(set(value)) != len(value):
|
|
37
|
+
raise ValueError('team member IDs must be unique')
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
@model_validator(mode='after')
|
|
41
|
+
def validate_team(self):
|
|
42
|
+
if self.team_lead_agent_id in self.team_member_agent_ids:
|
|
43
|
+
raise ValueError('the team lead cannot also be a team member')
|
|
44
|
+
return self
|
|
@@ -65,6 +65,16 @@ class ProjectAgentMemoryRecord(StrictModel):
|
|
|
65
65
|
created_at: datetime
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
class EmployeeWorkLogRecord(StrictModel):
|
|
69
|
+
task_context_token: str
|
|
70
|
+
source_application: SourceApplication
|
|
71
|
+
session_id: str
|
|
72
|
+
created_at: datetime
|
|
73
|
+
status: Literal['reported', 'completed', 'incomplete', 'failed', 'no_change', 'unreported', 'running']
|
|
74
|
+
summary: str = Field(min_length=1, max_length=2000)
|
|
75
|
+
memory_updated: bool
|
|
76
|
+
|
|
77
|
+
|
|
68
78
|
class ProjectAgentMemoryView(StrictModel):
|
|
69
79
|
personal_space_id: str
|
|
70
80
|
personal_project_id: str
|
|
@@ -77,3 +87,4 @@ class ProjectAgentMemoryView(StrictModel):
|
|
|
77
87
|
revision: int = Field(default=0, ge=0)
|
|
78
88
|
current: ProjectAgentMemoryRecord | None = None
|
|
79
89
|
history: list[ProjectAgentMemoryRecord] = Field(default_factory=list)
|
|
90
|
+
work_log: list[EmployeeWorkLogRecord] = Field(default_factory=list, max_length=6)
|
|
@@ -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,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Grow a project team inside the recruitment transaction, without changing authority."""
|
|
2
|
+
|
|
3
|
+
from fastapi import HTTPException
|
|
4
|
+
|
|
5
|
+
from .provider_values import now_utc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def enroll_recruited_team_member(store, recruitment, profile):
|
|
9
|
+
# Temporary workers and system employees do not become permanent teammates.
|
|
10
|
+
if (profile.agent_type != 'durable' or profile.status != 'active'
|
|
11
|
+
or any(cap.startswith('fuli.employee:') for cap in profile.capabilities)):
|
|
12
|
+
return
|
|
13
|
+
space_id = recruitment['personal_space_id']
|
|
14
|
+
project_id = recruitment['personal_project_id']
|
|
15
|
+
rows, _, _ = await store.runtime.driver.execute_query(
|
|
16
|
+
'''
|
|
17
|
+
MATCH (:FuliSpace {id: $space_id})-[:CONTAINS_PROJECT]->
|
|
18
|
+
(project:FuliPersonalProject {project_id: $project_id})
|
|
19
|
+
MATCH (project)-[:HAS_PROJECT_AGENT_ASSIGNMENT]->
|
|
20
|
+
(:FuliProjectAgentAssignment {status: 'active'})-[:ASSIGNED_AGENT]->
|
|
21
|
+
(agent:FuliProjectAgent {agent_id: $agent_id, status: 'active'})
|
|
22
|
+
WITH DISTINCT project, agent
|
|
23
|
+
MERGE (policy:FuliProjectAgentCoordinationPolicy {policy_id: $policy_id})
|
|
24
|
+
ON CREATE SET policy.personal_space_id = $space_id,
|
|
25
|
+
policy.personal_project_id = $project_id,
|
|
26
|
+
policy.created_at = $updated_at
|
|
27
|
+
SET policy._fuli_write_lock = true
|
|
28
|
+
REMOVE policy._fuli_write_lock
|
|
29
|
+
MERGE (project)-[:HAS_PROJECT_AGENT_COORDINATION_POLICY]->(policy)
|
|
30
|
+
WITH policy, agent, coalesce(policy.auto_grow_team, true) AS enabled,
|
|
31
|
+
coalesce(policy.team_member_agent_ids, []) AS members
|
|
32
|
+
WITH policy, agent, members, enabled,
|
|
33
|
+
policy.team_lead_agent_id IS NULL AS needs_lead,
|
|
34
|
+
coalesce(policy.team_lead_agent_id = agent.agent_id, false)
|
|
35
|
+
OR agent.agent_id IN members AS already_member
|
|
36
|
+
WITH *, enabled AND NOT already_member
|
|
37
|
+
AND (needs_lead OR size(members) < 32) AS changed
|
|
38
|
+
FOREACH (_ IN CASE WHEN changed THEN [1] ELSE [] END |
|
|
39
|
+
SET policy.team_lead_agent_id = CASE WHEN needs_lead
|
|
40
|
+
THEN agent.agent_id ELSE policy.team_lead_agent_id END,
|
|
41
|
+
policy.team_member_agent_ids = CASE WHEN needs_lead
|
|
42
|
+
THEN members ELSE members + agent.agent_id END,
|
|
43
|
+
policy.updated_at = $updated_at
|
|
44
|
+
)
|
|
45
|
+
RETURN enabled AND NOT already_member AND NOT needs_lead
|
|
46
|
+
AND size(members) >= 32 AS full
|
|
47
|
+
''',
|
|
48
|
+
space_id=space_id, project_id=project_id,
|
|
49
|
+
agent_id=recruitment['proposed_agent_id'],
|
|
50
|
+
policy_id=store._project_agent_coordination_policy_id(space_id, project_id),
|
|
51
|
+
updated_at=now_utc(),
|
|
52
|
+
)
|
|
53
|
+
if not rows:
|
|
54
|
+
raise HTTPException(409, 'recruited Agent has no active project assignment')
|
|
55
|
+
if rows[0]['full']:
|
|
56
|
+
raise HTTPException(409, 'project team is full; change the team before recruiting')
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Recover bounded employee work records independently of distilled memory updates."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def read_employee_work_log(store, space_id, project_id, agent_id):
|
|
7
|
+
rows, _, _ = await store.runtime.driver.execute_query(
|
|
8
|
+
'''
|
|
9
|
+
MATCH (:FuliSpace {id: $space_id})-[:HAS_TASK_CONTEXT_SESSION]->
|
|
10
|
+
(session:FuliTaskContextSession)-[:HAS_CONTEXT]->(task:FuliTaskContext)
|
|
11
|
+
WHERE task.personal_project_id = $project_id AND task.project_agent_id = $agent_id
|
|
12
|
+
RETURN task.record_json AS record_json, task.completed AS completed,
|
|
13
|
+
task.agent_memory_claimed AS memory_updated,
|
|
14
|
+
task.token = session.current_token AS current
|
|
15
|
+
ORDER BY task.created_at DESC, task.token LIMIT 6
|
|
16
|
+
''', space_id=space_id, project_id=project_id, agent_id=agent_id, routing_='r',
|
|
17
|
+
)
|
|
18
|
+
result = []
|
|
19
|
+
for row in rows:
|
|
20
|
+
record = json.loads(row['record_json'])
|
|
21
|
+
checkpoint = record.get('checkpoint') or {}
|
|
22
|
+
log = checkpoint.get('work_log') or {}
|
|
23
|
+
result.append({
|
|
24
|
+
'task_context_token': record['token'],
|
|
25
|
+
'source_application': record['source_application'],
|
|
26
|
+
'session_id': record['session_id'],
|
|
27
|
+
'created_at': record['created_at'],
|
|
28
|
+
'status': (log.get('status', 'unreported') if row['completed']
|
|
29
|
+
else 'running' if row.get('current') else 'incomplete'),
|
|
30
|
+
'summary': log.get('summary') or checkpoint.get('reason')
|
|
31
|
+
or 'The employee has not submitted a work summary for this task.',
|
|
32
|
+
'memory_updated': bool(row.get('memory_updated')),
|
|
33
|
+
})
|
|
34
|
+
return result
|
|
@@ -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
|
-
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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,
|
|
@@ -32,7 +32,7 @@ class StoreKnowledge:
|
|
|
32
32
|
self, actor, space, request.personal_project_id
|
|
33
33
|
)
|
|
34
34
|
if request.project_agent_id:
|
|
35
|
-
await authorize_project_agent(
|
|
35
|
+
agent = await authorize_project_agent(
|
|
36
36
|
self,
|
|
37
37
|
actor,
|
|
38
38
|
space,
|
|
@@ -41,6 +41,7 @@ class StoreKnowledge:
|
|
|
41
41
|
require_active=True,
|
|
42
42
|
require_memory=True,
|
|
43
43
|
)
|
|
44
|
+
request = request.model_copy(update={'project_agent_id': agent['agent_id']})
|
|
44
45
|
return await self._commit_episode(
|
|
45
46
|
space,
|
|
46
47
|
request.episode,
|
|
@@ -65,7 +66,7 @@ class StoreKnowledge:
|
|
|
65
66
|
self, actor, space, request.personal_project_id
|
|
66
67
|
)
|
|
67
68
|
if request.project_agent_id:
|
|
68
|
-
await authorize_project_agent(
|
|
69
|
+
agent = await authorize_project_agent(
|
|
69
70
|
self,
|
|
70
71
|
actor,
|
|
71
72
|
space,
|
|
@@ -74,6 +75,7 @@ class StoreKnowledge:
|
|
|
74
75
|
require_active=True,
|
|
75
76
|
require_memory=True,
|
|
76
77
|
)
|
|
78
|
+
request = request.model_copy(update={'project_agent_id': agent['agent_id']})
|
|
77
79
|
return await self._commit_episode(
|
|
78
80
|
space,
|
|
79
81
|
request.episode,
|