fuli-context 0.7.5 → 0.7.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.
- package/README.md +69 -13
- package/README.zh-CN.md +60 -14
- package/dist/web/assets/{AboutPage-ByCUor-j.js → AboutPage-BcZeFcHe.js} +1 -1
- package/dist/web/assets/{ConnectionsPage-DkdkZ40Q.js → ConnectionsPage-xAQYl1SQ.js} +1 -1
- package/dist/web/assets/EmployeeRecruitDialog-CbPAmhy7.js +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-CfFlyKSh.css +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-BQvTLoc8.css +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-DF3EL0Ui.js +1 -0
- package/dist/web/assets/KnowledgeEditDialog-UnnBDt0I.js +1 -0
- package/dist/web/assets/{KnowledgeEditDialog-DWUV_NPp.css → KnowledgeEditDialog-iv-dgUeD.css} +1 -1
- package/dist/web/assets/{KnowledgeInspector-CYAjAb9J.js → KnowledgeInspector-BqPJH5Rr.js} +1 -1
- package/dist/web/assets/{KnowledgeOrganizerPage-CV4F54R_.js → KnowledgeOrganizerPage-CyvRflXM.js} +1 -1
- package/dist/web/assets/KnowledgePage-59eczTqe.js +1 -0
- package/dist/web/assets/{KnowledgeWorkspace-z4u5mXOG.js → KnowledgeWorkspace-CfIjhOog.js} +1 -1
- package/dist/web/assets/{OverviewPage-BWOfeS6k.js → OverviewPage-Xyfm5xkZ.js} +1 -1
- package/dist/web/assets/{PersonalProfilePage-c3w4-h7a.js → PersonalProfilePage-DB9mByB3.js} +1 -1
- package/dist/web/assets/PersonalProjectsPage-_-YXVi5O.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-C3vW6wmk.css +1 -0
- package/dist/web/assets/ProjectAgentsPage-DeeQByPX.js +11 -0
- package/dist/web/assets/{PublicProjectsPage-DdH0oGB3.js → PublicProjectsPage-BEZ1YPs3.js} +1 -1
- package/dist/web/assets/{ReviewPage-BYIpcIHG.js → ReviewPage-CQYGwFBH.js} +1 -1
- package/dist/web/assets/SearchableSelect-D27H0MoP.js +1 -0
- package/dist/web/assets/{SettingsPage-2VkbXgZi.js → SettingsPage-BlEZ03Yi.js} +1 -1
- package/dist/web/assets/{WritingTastePage-BOPQZuva.js → WritingTastePage-DvRt_nmP.js} +1 -1
- package/dist/web/assets/catalog-DFvIaHVS.js +1 -0
- package/dist/web/assets/{console-BlAhujsH.js → console-tOxOl8Vq.js} +1 -1
- package/dist/web/assets/conversation-launcher-settings-DuFSpPAo.js +1 -0
- package/dist/web/assets/{i18n-Ci3heuiL.js → i18n-CgBHZthI.js} +3 -3
- package/dist/web/assets/{identity-4_-v6cB7.js → identity-CFo_Uw1H.js} +1 -1
- package/dist/web/assets/index-CJc5ppXH.css +1 -0
- package/dist/web/assets/index-yCi3cz2l.js +2 -0
- package/dist/web/assets/{model-yG0YqUyZ.js → model-BtnVr7fX.js} +1 -1
- package/dist/web/assets/{useApi-CROJJdhE-twhdZArA.js → useApi-CROJJdhE-B6MHm_Av.js} +1 -1
- package/dist/web/assets/{vue-router-C-wot_Di.js → vue-router-CzHm-y-x.js} +1 -1
- package/dist/web/index.html +7 -6
- package/docs/employee-agents.md +99 -0
- package/docs/project-agent-memory.md +479 -0
- package/graph-provider/fuli_graph/knowledge_audit_models.py +171 -0
- package/graph-provider/fuli_graph/model_base.py +5 -0
- package/graph-provider/fuli_graph/model_validation.py +32 -0
- package/graph-provider/fuli_graph/models.py +26 -170
- package/graph-provider/fuli_graph/project_agent_context_models.py +29 -0
- package/graph-provider/fuli_graph/project_agent_coordination_models.py +22 -0
- package/graph-provider/fuli_graph/project_agent_executor_models.py +35 -7
- package/graph-provider/fuli_graph/project_agent_memory_models.py +79 -0
- package/graph-provider/fuli_graph/project_agent_memory_routes.py +46 -0
- package/graph-provider/fuli_graph/project_agent_models.py +9 -0
- package/graph-provider/fuli_graph/project_agent_recruitment_values.py +48 -0
- package/graph-provider/fuli_graph/project_agent_routes.py +50 -1
- package/graph-provider/fuli_graph/project_agent_task_models.py +220 -29
- package/graph-provider/fuli_graph/runtime.py +37 -1
- package/graph-provider/fuli_graph/store.py +6 -0
- package/graph-provider/fuli_graph/store_project_agent_context.py +65 -0
- package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +122 -0
- package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +175 -11
- package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +33 -10
- package/graph-provider/fuli_graph/store_project_agent_executors.py +181 -14
- package/graph-provider/fuli_graph/store_project_agent_memory.py +174 -0
- package/graph-provider/fuli_graph/store_project_agent_task_activity.py +423 -0
- package/graph-provider/fuli_graph/store_project_agent_task_persistence.py +152 -0
- package/graph-provider/fuli_graph/store_project_agent_task_reads.py +187 -21
- package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +756 -164
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +396 -7
- package/graph-provider/fuli_graph/store_project_agent_tasks.py +265 -465
- package/graph-provider/fuli_graph/store_project_agents.py +214 -46
- package/graph-provider/fuli_graph/store_task_contexts.py +217 -0
- package/graph-provider/fuli_graph/store_transactions.py +40 -0
- package/graph-provider/fuli_graph/task_context_models.py +50 -0
- package/graph-provider/fuli_graph/task_context_routes.py +31 -0
- package/graph-provider/fuli_graph/workflow_observation_models.py +1 -1
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +8 -8
- package/package.json +5 -2
- package/skills/capturing-session-knowledge/SKILL.md +18 -6
- package/src/adaptive-runtime/executor-pool.js +46 -5
- package/src/adaptive-runtime/graph-services.js +10 -3
- package/src/adaptive-runtime/lease-client.js +66 -14
- package/src/adaptive-runtime/runtime-broker.js +24 -9
- package/src/agent-tools/employee-definitions.js +43 -0
- package/src/agent-tools/graph-definitions.js +48 -6
- package/src/agent-tools/graph-handlers.js +15 -0
- package/src/agent-tools/project-agent-definitions.js +35 -4
- package/src/agent-tools/project-agent-memory-definitions.js +36 -0
- package/src/agent-tools.js +6 -2
- package/src/agents/codex/lifecycle-hook.js +107 -0
- package/src/agents/codex/lifecycle-hooks.js +231 -0
- package/src/agents/cursor/lifecycle-hook.js +133 -0
- package/src/agents/cursor/lifecycle-hooks.js +50 -0
- package/src/agents/lifecycle-message.js +19 -0
- package/src/app/agent-request-context.js +23 -0
- package/src/cli/command-registry.js +2 -0
- package/src/cli/employee-command.js +17 -0
- package/src/cli/remote-mcp-command.js +282 -0
- package/src/cli/setup-command.js +5 -0
- package/src/cli.js +10 -0
- package/src/employees/catalog/jefa.json +21 -0
- package/src/employees/install-package.js +77 -0
- package/src/employees/management-policy.js +88 -0
- package/src/employees/manifest.js +53 -0
- package/src/employees/package-registry.js +107 -0
- package/src/employees/project-selection.js +50 -0
- package/src/employees/recruitment.js +199 -0
- package/src/employees/service.js +83 -0
- package/src/employees/task-entry.js +39 -0
- package/src/graphiti/agent-knowledge-workflows.js +135 -25
- package/src/graphiti/federated-application.js +78 -35
- package/src/graphiti/project-agent-control-plane.js +32 -3
- package/src/graphiti/project-agent-mapping.js +175 -4
- package/src/graphiti/project-agent-memory.js +115 -0
- package/src/graphiti/project-agent-task-entry.js +126 -0
- package/src/graphiti/project-agent-workflows.js +49 -14
- package/src/graphiti/provider-client.js +78 -4
- package/src/http/api-router.js +25 -5
- package/src/http/employee-api-router.js +64 -0
- package/src/http/error-mapping.js +4 -0
- package/src/http/graph-api-router.js +14 -0
- package/src/http/request-policy.js +6 -3
- package/src/http/static-handler.js +4 -3
- package/src/mcp/create-mcp-server.js +98 -37
- package/src/mcp/direct-call.js +38 -0
- package/src/mcp/instructions.js +8 -7
- package/src/mcp/provider-task-context-registry.js +74 -0
- package/src/mcp/remote-http-server.js +761 -0
- package/src/mcp/session-id.js +29 -3
- package/src/mcp/task-context-registry.js +88 -29
- package/src/mcp/tool-annotations.js +12 -2
- package/src/mcp/tool-result.js +8 -3
- package/src/mcp-server.js +20 -8
- package/src/native-runtime/runtime.js +58 -8
- package/src/server.js +76 -28
- package/src/setup/agent-installation-status.js +38 -6
- package/src/setup/agents.js +20 -8
- package/src/setup/claude-code-config.js +55 -13
- package/src/setup/codex-bootstrap.js +11 -10
- package/src/setup/codex-config.js +74 -7
- package/src/setup/config-backup.js +18 -2
- package/src/setup/cursor-config.js +22 -5
- package/src/setup/setup.js +30 -22
- package/src/storage/json-file.js +11 -3
- package/dist/web/assets/KnowledgeEditDialog-DSe3Gwep.js +0 -1
- package/dist/web/assets/KnowledgePage-vYYXipOi.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-B7AlLEwf.js +0 -1
- package/dist/web/assets/ProjectAgentsPage-CgftpIPB.css +0 -1
- package/dist/web/assets/ProjectAgentsPage-DTHcL_ox.js +0 -11
- package/dist/web/assets/SearchableSelect-Dz13ClQb.js +0 -1
- package/dist/web/assets/conversation-launcher-settings-DTYxiLJt.js +0 -1
- package/dist/web/assets/index-BnH5mcZx.css +0 -1
- package/dist/web/assets/index-C67Q0JY2.js +0 -2
|
@@ -3,7 +3,11 @@ from typing import Literal
|
|
|
3
3
|
|
|
4
4
|
from pydantic import AliasChoices, Field, field_validator, model_validator
|
|
5
5
|
|
|
6
|
-
from .model_validation import
|
|
6
|
+
from .model_validation import (
|
|
7
|
+
normalize_source_session_url,
|
|
8
|
+
reject_credentials,
|
|
9
|
+
validate_emoji_sequence,
|
|
10
|
+
)
|
|
7
11
|
from .models import SourceApplication, StrictModel
|
|
8
12
|
from .project_agent_models import (
|
|
9
13
|
ProjectAgentExecutorPolicy,
|
|
@@ -41,6 +45,10 @@ ProjectAgentRoutingOutcome = Literal[
|
|
|
41
45
|
]
|
|
42
46
|
ProjectAgentRoutingReason = Literal[
|
|
43
47
|
'explicit_agent',
|
|
48
|
+
'project_continuity',
|
|
49
|
+
'project_default',
|
|
50
|
+
'sole_active_assignment',
|
|
51
|
+
'manual_agent_selection',
|
|
44
52
|
'exact_work_kind',
|
|
45
53
|
'exact_capability',
|
|
46
54
|
'no_match',
|
|
@@ -69,6 +77,16 @@ ProjectAgentOptimizationPriority = Literal[
|
|
|
69
77
|
'token_and_cost',
|
|
70
78
|
'time',
|
|
71
79
|
]
|
|
80
|
+
ProjectAgentTokenUsageSource = Literal['executor', 'host', 'dingdong']
|
|
81
|
+
|
|
82
|
+
_WORKER_EVIDENCE_ALIASES = {
|
|
83
|
+
'worker_id': 'workerId',
|
|
84
|
+
'worker_label': 'workerLabel',
|
|
85
|
+
'worker_occupation_emoji': 'workerOccupationEmoji',
|
|
86
|
+
'worker_status': 'workerStatus',
|
|
87
|
+
'token_usage': 'tokenUsage',
|
|
88
|
+
'worker_runtime': 'workerRuntime',
|
|
89
|
+
}
|
|
72
90
|
|
|
73
91
|
|
|
74
92
|
def _unique_text(value: list[str], label: str) -> list[str]:
|
|
@@ -80,6 +98,19 @@ def _unique_text(value: list[str], label: str) -> list[str]:
|
|
|
80
98
|
return normalized
|
|
81
99
|
|
|
82
100
|
|
|
101
|
+
def _normalize_worker_evidence_aliases(value):
|
|
102
|
+
if not isinstance(value, dict):
|
|
103
|
+
return value
|
|
104
|
+
normalized = dict(value)
|
|
105
|
+
for field_name, alias in _WORKER_EVIDENCE_ALIASES.items():
|
|
106
|
+
if alias not in normalized:
|
|
107
|
+
continue
|
|
108
|
+
if field_name in normalized and normalized[field_name] != normalized[alias]:
|
|
109
|
+
raise ValueError(f'conflicting {field_name} and {alias} values')
|
|
110
|
+
normalized[field_name] = normalized.pop(alias)
|
|
111
|
+
return normalized
|
|
112
|
+
|
|
113
|
+
|
|
83
114
|
class ProjectAgentParallelPlan(StrictModel):
|
|
84
115
|
enabled: bool = False
|
|
85
116
|
independent_verification: bool = False
|
|
@@ -117,6 +148,10 @@ class ProjectAgentTaskSubmit(StrictModel):
|
|
|
117
148
|
objective: str = Field(min_length=1, max_length=4096)
|
|
118
149
|
work_kind: str = Field(min_length=1, max_length=128)
|
|
119
150
|
required_capabilities: list[str] = Field(default_factory=list, max_length=16)
|
|
151
|
+
executor_capability_hints: list[str] = Field(
|
|
152
|
+
default_factory=list,
|
|
153
|
+
max_length=16,
|
|
154
|
+
)
|
|
120
155
|
duration: ProjectAgentTaskDuration = 'ongoing'
|
|
121
156
|
staffing_intent: ProjectAgentStaffingIntent = 'reuse_preferred'
|
|
122
157
|
lead_agent_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
@@ -140,6 +175,14 @@ class ProjectAgentTaskSubmit(StrictModel):
|
|
|
140
175
|
def validate_capabilities(cls, value: list[str]) -> list[str]:
|
|
141
176
|
return _unique_text(value, 'required capabilities')
|
|
142
177
|
|
|
178
|
+
@field_validator('executor_capability_hints')
|
|
179
|
+
@classmethod
|
|
180
|
+
def validate_executor_capability_hints(
|
|
181
|
+
cls,
|
|
182
|
+
value: list[str],
|
|
183
|
+
) -> list[str]:
|
|
184
|
+
return _unique_text(value, 'executor capability hints')
|
|
185
|
+
|
|
143
186
|
@field_validator('collaborator_agent_ids')
|
|
144
187
|
@classmethod
|
|
145
188
|
def validate_collaborators(cls, value: list[str]) -> list[str]:
|
|
@@ -182,6 +225,80 @@ class ProjectAgentTaskParticipantRecord(StrictModel):
|
|
|
182
225
|
ended_at: datetime | None = None
|
|
183
226
|
|
|
184
227
|
|
|
228
|
+
class ProjectAgentTokenUsage(StrictModel):
|
|
229
|
+
"""Cumulative token snapshot reported for one concrete worker run."""
|
|
230
|
+
|
|
231
|
+
source: ProjectAgentTokenUsageSource
|
|
232
|
+
total_tokens: int = Field(
|
|
233
|
+
ge=0,
|
|
234
|
+
validation_alias=AliasChoices('total_tokens', 'totalTokens'),
|
|
235
|
+
)
|
|
236
|
+
input_tokens: int | None = Field(
|
|
237
|
+
default=None,
|
|
238
|
+
ge=0,
|
|
239
|
+
validation_alias=AliasChoices('input_tokens', 'inputTokens'),
|
|
240
|
+
)
|
|
241
|
+
output_tokens: int | None = Field(
|
|
242
|
+
default=None,
|
|
243
|
+
ge=0,
|
|
244
|
+
validation_alias=AliasChoices('output_tokens', 'outputTokens'),
|
|
245
|
+
)
|
|
246
|
+
cached_input_tokens: int | None = Field(
|
|
247
|
+
default=None,
|
|
248
|
+
ge=0,
|
|
249
|
+
validation_alias=AliasChoices(
|
|
250
|
+
'cached_input_tokens',
|
|
251
|
+
'cachedInputTokens',
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
cache_write_input_tokens: int | None = Field(
|
|
255
|
+
default=None,
|
|
256
|
+
ge=0,
|
|
257
|
+
validation_alias=AliasChoices(
|
|
258
|
+
'cache_write_input_tokens',
|
|
259
|
+
'cacheWriteInputTokens',
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
reasoning_output_tokens: int | None = Field(
|
|
263
|
+
default=None,
|
|
264
|
+
ge=0,
|
|
265
|
+
validation_alias=AliasChoices(
|
|
266
|
+
'reasoning_output_tokens',
|
|
267
|
+
'reasoningOutputTokens',
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class ProjectAgentWorkerRuntime(StrictModel):
|
|
273
|
+
"""Host-reported worker location, never the reporter's trusted identity."""
|
|
274
|
+
|
|
275
|
+
application: SourceApplication
|
|
276
|
+
session_id: str | None = Field(
|
|
277
|
+
default=None, min_length=1, max_length=256,
|
|
278
|
+
validation_alias=AliasChoices('session_id', 'sessionId'),
|
|
279
|
+
)
|
|
280
|
+
session_url: str | None = Field(
|
|
281
|
+
default=None, max_length=2048,
|
|
282
|
+
validation_alias=AliasChoices('session_url', 'sessionUrl'),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
_validate_session_url = field_validator('session_url')(
|
|
286
|
+
normalize_source_session_url
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
@field_validator('session_id')
|
|
290
|
+
@classmethod
|
|
291
|
+
def validate_session_id(cls, value):
|
|
292
|
+
if value is not None and not value.strip():
|
|
293
|
+
raise ValueError('worker session ID must not be blank')
|
|
294
|
+
return value.strip() if value is not None else None
|
|
295
|
+
|
|
296
|
+
@model_validator(mode='after')
|
|
297
|
+
def validate_evidence(self):
|
|
298
|
+
reject_credentials(self, 'worker runtime')
|
|
299
|
+
return self
|
|
300
|
+
|
|
301
|
+
|
|
185
302
|
class ProjectAgentTaskExecutionSummary(StrictModel):
|
|
186
303
|
"""Auditable execution evidence projected onto a real task participant.
|
|
187
304
|
|
|
@@ -200,17 +317,31 @@ class ProjectAgentTaskExecutionSummary(StrictModel):
|
|
|
200
317
|
source_application: SourceApplication | None = None
|
|
201
318
|
actual_model_provider: str | None = None
|
|
202
319
|
actual_model: str | None = None
|
|
320
|
+
source_session_id: str | None = None
|
|
321
|
+
source_session_url: str | None = None
|
|
322
|
+
tools_used: list[str] | None = Field(default=None, max_length=32)
|
|
323
|
+
token_usage: ProjectAgentTokenUsage | None = None
|
|
203
324
|
work_summary: str | None = None
|
|
204
325
|
status: ProjectAgentTaskStatus
|
|
205
326
|
worker_id: str | None = None
|
|
206
327
|
worker_label: str | None = None
|
|
207
328
|
worker_occupation_emoji: str | None = None
|
|
329
|
+
worker_runtime: ProjectAgentWorkerRuntime | None = None
|
|
208
330
|
|
|
209
331
|
@field_validator('occupation_emoji', 'worker_occupation_emoji', mode='before')
|
|
210
332
|
@classmethod
|
|
211
333
|
def validate_emojis(cls, value):
|
|
212
334
|
return validate_emoji_sequence(value, 'occupation emoji')
|
|
213
335
|
|
|
336
|
+
_validate_session_url = field_validator('source_session_url')(
|
|
337
|
+
normalize_source_session_url
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
@field_validator('tools_used')
|
|
341
|
+
@classmethod
|
|
342
|
+
def validate_tools_used(cls, value):
|
|
343
|
+
return _unique_text(value, 'tools used') if value is not None else None
|
|
344
|
+
|
|
214
345
|
|
|
215
346
|
class ProjectAgentTaskEventRecord(StrictModel):
|
|
216
347
|
event_id: str
|
|
@@ -221,6 +352,8 @@ class ProjectAgentTaskEventRecord(StrictModel):
|
|
|
221
352
|
summary: str
|
|
222
353
|
source_application: SourceApplication | None = None
|
|
223
354
|
source_session_id: str | None = None
|
|
355
|
+
source_session_url: str | None = None
|
|
356
|
+
tools_used: list[str] | None = Field(default=None, max_length=32)
|
|
224
357
|
actual_model_provider: str | None = None
|
|
225
358
|
actual_model: str | None = None
|
|
226
359
|
actual_executor_id: str | None = None
|
|
@@ -229,32 +362,32 @@ class ProjectAgentTaskEventRecord(StrictModel):
|
|
|
229
362
|
executor_selection_reason: str | None = None
|
|
230
363
|
executor_fallback_reason: str | None = None
|
|
231
364
|
executor_blocked_reason: str | None = None
|
|
232
|
-
worker_id: str | None =
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
validation_alias=AliasChoices('worker_label', 'workerLabel'),
|
|
239
|
-
)
|
|
240
|
-
worker_occupation_emoji: str | None = Field(
|
|
241
|
-
default=None,
|
|
242
|
-
validation_alias=AliasChoices(
|
|
243
|
-
'worker_occupation_emoji',
|
|
244
|
-
'workerOccupationEmoji',
|
|
245
|
-
),
|
|
246
|
-
)
|
|
247
|
-
worker_status: ProjectAgentTaskStatus | None = Field(
|
|
248
|
-
default=None,
|
|
249
|
-
validation_alias=AliasChoices('worker_status', 'workerStatus'),
|
|
250
|
-
)
|
|
365
|
+
worker_id: str | None = None
|
|
366
|
+
worker_label: str | None = None
|
|
367
|
+
worker_occupation_emoji: str | None = None
|
|
368
|
+
worker_status: ProjectAgentTaskStatus | None = None
|
|
369
|
+
token_usage: ProjectAgentTokenUsage | None = None
|
|
370
|
+
worker_runtime: ProjectAgentWorkerRuntime | None = None
|
|
251
371
|
created_at: datetime
|
|
252
372
|
|
|
373
|
+
_normalize_worker_aliases = model_validator(mode='before')(
|
|
374
|
+
_normalize_worker_evidence_aliases
|
|
375
|
+
)
|
|
376
|
+
|
|
253
377
|
@field_validator('worker_occupation_emoji', mode='before')
|
|
254
378
|
@classmethod
|
|
255
379
|
def validate_worker_occupation_emoji(cls, value):
|
|
256
380
|
return validate_emoji_sequence(value, 'worker occupation emoji')
|
|
257
381
|
|
|
382
|
+
_validate_session_url = field_validator('source_session_url')(
|
|
383
|
+
normalize_source_session_url
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
@field_validator('tools_used')
|
|
387
|
+
@classmethod
|
|
388
|
+
def validate_tools_used(cls, value):
|
|
389
|
+
return _unique_text(value, 'tools used') if value is not None else None
|
|
390
|
+
|
|
258
391
|
|
|
259
392
|
class ProjectAgentRoutingDecisionRecord(StrictModel):
|
|
260
393
|
decision_id: str
|
|
@@ -299,6 +432,7 @@ class ProjectAgentTaskRecord(StrictModel):
|
|
|
299
432
|
objective: str
|
|
300
433
|
work_kind: str
|
|
301
434
|
required_capabilities: list[str]
|
|
435
|
+
executor_capability_hints: list[str] = Field(default_factory=list)
|
|
302
436
|
duration: ProjectAgentTaskDuration
|
|
303
437
|
staffing_intent: ProjectAgentStaffingIntent
|
|
304
438
|
status: ProjectAgentTaskStatus
|
|
@@ -363,6 +497,8 @@ class ProjectAgentTaskActivityCreate(StrictModel):
|
|
|
363
497
|
actor_kind: Literal['agent', 'human'] = 'agent'
|
|
364
498
|
source_application: SourceApplication | None = None
|
|
365
499
|
source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
|
|
500
|
+
source_session_url: str | None = Field(default=None, max_length=2048)
|
|
501
|
+
tools_used: list[str] | None = Field(default=None, max_length=32)
|
|
366
502
|
actual_model_provider: str | None = Field(default=None, min_length=1, max_length=128)
|
|
367
503
|
actual_model: str | None = Field(default=None, min_length=1, max_length=256)
|
|
368
504
|
actual_executor_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
@@ -390,33 +526,46 @@ class ProjectAgentTaskActivityCreate(StrictModel):
|
|
|
390
526
|
default=None,
|
|
391
527
|
min_length=1,
|
|
392
528
|
max_length=128,
|
|
393
|
-
validation_alias=AliasChoices('worker_id', 'workerId'),
|
|
394
529
|
)
|
|
395
530
|
worker_label: str | None = Field(
|
|
396
531
|
default=None,
|
|
397
532
|
min_length=1,
|
|
398
533
|
max_length=160,
|
|
399
|
-
validation_alias=AliasChoices('worker_label', 'workerLabel'),
|
|
400
534
|
)
|
|
401
535
|
worker_occupation_emoji: str | None = Field(
|
|
402
536
|
default=None,
|
|
403
537
|
min_length=1,
|
|
404
538
|
max_length=32,
|
|
405
|
-
validation_alias=AliasChoices(
|
|
406
|
-
'worker_occupation_emoji',
|
|
407
|
-
'workerOccupationEmoji',
|
|
408
|
-
),
|
|
409
539
|
)
|
|
410
|
-
worker_status: ProjectAgentTaskStatus | None =
|
|
411
|
-
|
|
412
|
-
|
|
540
|
+
worker_status: ProjectAgentTaskStatus | None = None
|
|
541
|
+
token_usage: ProjectAgentTokenUsage | None = None
|
|
542
|
+
worker_runtime: ProjectAgentWorkerRuntime | None = None
|
|
543
|
+
|
|
544
|
+
_normalize_worker_aliases = model_validator(mode='before')(
|
|
545
|
+
_normalize_worker_evidence_aliases
|
|
413
546
|
)
|
|
414
547
|
|
|
548
|
+
@property
|
|
549
|
+
def reported_client_applications(self):
|
|
550
|
+
# Worker claims can impose another restriction, never replace the
|
|
551
|
+
# host identity used for authorization and audit attribution.
|
|
552
|
+
return (self.source_application, self.worker_runtime.application) \
|
|
553
|
+
if self.worker_runtime else (self.source_application,)
|
|
554
|
+
|
|
415
555
|
@field_validator('worker_occupation_emoji', mode='before')
|
|
416
556
|
@classmethod
|
|
417
557
|
def validate_worker_occupation_emoji(cls, value):
|
|
418
558
|
return validate_emoji_sequence(value, 'worker occupation emoji')
|
|
419
559
|
|
|
560
|
+
_validate_session_url = field_validator('source_session_url')(
|
|
561
|
+
normalize_source_session_url
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
@field_validator('tools_used')
|
|
565
|
+
@classmethod
|
|
566
|
+
def validate_tools_used(cls, value):
|
|
567
|
+
return _unique_text(value, 'tools used') if value is not None else None
|
|
568
|
+
|
|
420
569
|
@model_validator(mode='after')
|
|
421
570
|
def validate_actual_model(self):
|
|
422
571
|
if self.status in {'completed', 'failed'} and not self.agent_id:
|
|
@@ -433,6 +582,24 @@ class ProjectAgentTaskActivityCreate(StrictModel):
|
|
|
433
582
|
raise ValueError('actual execution report requires the participating Agent ID')
|
|
434
583
|
if self.worker_id and not self.agent_id:
|
|
435
584
|
raise ValueError('worker execution report requires the participating Agent ID')
|
|
585
|
+
if self.worker_status and not self.worker_id:
|
|
586
|
+
raise ValueError('worker status requires a worker ID')
|
|
587
|
+
if self.worker_runtime and not self.worker_id:
|
|
588
|
+
raise ValueError('worker runtime requires a worker ID')
|
|
589
|
+
if self.token_usage and not self.agent_id:
|
|
590
|
+
raise ValueError('token usage report requires the participating Agent ID')
|
|
591
|
+
if (self.source_session_url or self.tools_used) and not self.agent_id:
|
|
592
|
+
raise ValueError(
|
|
593
|
+
'execution evidence requires the participating Agent ID'
|
|
594
|
+
)
|
|
595
|
+
if (
|
|
596
|
+
self.token_usage
|
|
597
|
+
and self.token_usage.source == 'executor'
|
|
598
|
+
and not self.actual_executor_id
|
|
599
|
+
):
|
|
600
|
+
raise ValueError(
|
|
601
|
+
'executor token usage requires the actual executor report'
|
|
602
|
+
)
|
|
436
603
|
if (self.worker_label or self.worker_occupation_emoji) and not self.worker_id:
|
|
437
604
|
raise ValueError(
|
|
438
605
|
'worker label or occupation emoji requires a worker ID'
|
|
@@ -457,6 +624,8 @@ class ProjectAgentRecruitmentRecord(StrictModel):
|
|
|
457
624
|
confirmation_mode: ProjectAgentRecruitmentConfirmationMode
|
|
458
625
|
proposed_agent_id: str
|
|
459
626
|
proposed_profile: ProjectAgentProfile
|
|
627
|
+
participant_role: ProjectAgentParticipantRole = 'lead'
|
|
628
|
+
recruitment_slot: str = Field(default='lead', min_length=1, max_length=128)
|
|
460
629
|
trigger_source_application: SourceApplication | None = None
|
|
461
630
|
trigger_source_session_id: str | None = None
|
|
462
631
|
revision: int = Field(ge=0)
|
|
@@ -472,6 +641,7 @@ class ProjectAgentTaskRouteResult(StrictModel):
|
|
|
472
641
|
task: ProjectAgentTaskRecord
|
|
473
642
|
assigned_agent: ProjectAgentRecord | None = None
|
|
474
643
|
recruitment: ProjectAgentRecruitmentRecord | None = None
|
|
644
|
+
recruitments: list[ProjectAgentRecruitmentRecord] = Field(default_factory=list)
|
|
475
645
|
decision: Literal[
|
|
476
646
|
'reused',
|
|
477
647
|
'recruited',
|
|
@@ -487,6 +657,13 @@ class ProjectAgentRecruitmentPolicyRecord(StrictModel):
|
|
|
487
657
|
personal_space_id: str
|
|
488
658
|
confirmation_mode: ProjectAgentRecruitmentConfirmationMode = 'automatic'
|
|
489
659
|
updated_at: datetime | None = None
|
|
660
|
+
policy_status: Literal['superseded'] = 'superseded'
|
|
661
|
+
applies_to_recruitment: Literal[False] = False
|
|
662
|
+
warning: str = (
|
|
663
|
+
'Legacy storage only; this setting does not control recruitment. '
|
|
664
|
+
'Use update_project_agent_coordination_policy with the exact project '
|
|
665
|
+
'and askBeforeRecruitment to change effective authorization.'
|
|
666
|
+
)
|
|
490
667
|
|
|
491
668
|
|
|
492
669
|
class ProjectAgentRecruitmentPolicyUpdate(StrictModel):
|
|
@@ -518,12 +695,26 @@ class ProjectAgentActivityTask(StrictModel):
|
|
|
518
695
|
actual_model_provider: str | None = None
|
|
519
696
|
actual_model: str | None = None
|
|
520
697
|
actual_executor_id: str | None = None
|
|
698
|
+
source_session_id: str | None = None
|
|
699
|
+
source_session_url: str | None = Field(default=None, max_length=2048)
|
|
700
|
+
tools_used: list[str] | None = Field(default=None, max_length=32)
|
|
701
|
+
token_usage: ProjectAgentTokenUsage | None = None
|
|
702
|
+
worker_runtime: ProjectAgentWorkerRuntime | None = None
|
|
521
703
|
matched_executor_rule_id: str | None = None
|
|
522
704
|
worker_id: str | None = None
|
|
523
705
|
worker_label: str | None = None
|
|
524
706
|
worker_occupation_emoji: str | None = None
|
|
525
707
|
worker_status: ProjectAgentTaskStatus | None = None
|
|
526
708
|
|
|
709
|
+
_validate_session_url = field_validator('source_session_url')(
|
|
710
|
+
normalize_source_session_url
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
@field_validator('tools_used')
|
|
714
|
+
@classmethod
|
|
715
|
+
def validate_tools_used(cls, value):
|
|
716
|
+
return _unique_text(value, 'tools used') if value is not None else None
|
|
717
|
+
|
|
527
718
|
|
|
528
719
|
class ProjectAgentActivityDay(StrictModel):
|
|
529
720
|
date: date
|
|
@@ -108,11 +108,37 @@ class LocalLexicalReranker(CrossEncoderClient):
|
|
|
108
108
|
)
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
class ManagedNeo4jDriver(Neo4jDriver):
|
|
112
|
+
"""Let GraphitiRuntime own index initialization exactly once.
|
|
113
|
+
|
|
114
|
+
graphiti-core schedules an eager background index build whenever its driver
|
|
115
|
+
is constructed inside a running event loop. Fuli has an explicit awaited
|
|
116
|
+
initialization sequence, so the dependency's untracked task would race it
|
|
117
|
+
and can leak unawaited query coroutines when one index request fails.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self, *args, **kwargs):
|
|
121
|
+
self._fuli_constructing = True
|
|
122
|
+
try:
|
|
123
|
+
super().__init__(*args, **kwargs)
|
|
124
|
+
finally:
|
|
125
|
+
self._fuli_constructing = False
|
|
126
|
+
|
|
127
|
+
def build_indices_and_constraints(self, delete_existing: bool = False):
|
|
128
|
+
if self._fuli_constructing:
|
|
129
|
+
return _completed_index_build()
|
|
130
|
+
return super().build_indices_and_constraints(delete_existing)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def _completed_index_build() -> None:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
111
137
|
class GraphitiRuntime:
|
|
112
138
|
def __init__(self, settings: Settings):
|
|
113
139
|
self.settings = settings
|
|
114
140
|
self.embedder = LocalHashEmbedder(settings.embedding_dim)
|
|
115
|
-
driver =
|
|
141
|
+
driver = ManagedNeo4jDriver(
|
|
116
142
|
uri=settings.neo4j_uri,
|
|
117
143
|
user=settings.neo4j_user,
|
|
118
144
|
password=settings.neo4j_password,
|
|
@@ -247,6 +273,16 @@ class GraphitiRuntime:
|
|
|
247
273
|
'FOR (n:FuliProjectAgent) REQUIRE n.id IS UNIQUE',
|
|
248
274
|
'CREATE CONSTRAINT fuli_project_agent_assignment_id IF NOT EXISTS '
|
|
249
275
|
'FOR (n:FuliProjectAgentAssignment) REQUIRE n.id IS UNIQUE',
|
|
276
|
+
'CREATE CONSTRAINT fuli_project_agent_memory_id IF NOT EXISTS '
|
|
277
|
+
'FOR (n:FuliProjectAgentMemory) REQUIRE n.id IS UNIQUE',
|
|
278
|
+
'CREATE CONSTRAINT fuli_project_agent_memory_checkpoint_id IF NOT EXISTS '
|
|
279
|
+
'FOR (n:FuliProjectAgentMemoryCheckpoint) REQUIRE n.id IS UNIQUE',
|
|
280
|
+
'CREATE CONSTRAINT fuli_task_context_session_id IF NOT EXISTS '
|
|
281
|
+
'FOR (n:FuliTaskContextSession) REQUIRE n.id IS UNIQUE',
|
|
282
|
+
'CREATE CONSTRAINT fuli_task_context_id IF NOT EXISTS '
|
|
283
|
+
'FOR (n:FuliTaskContext) REQUIRE n.id IS UNIQUE',
|
|
284
|
+
'CREATE INDEX fuli_task_context_agent_scope IF NOT EXISTS '
|
|
285
|
+
'FOR (n:FuliTaskContext) ON (n.personal_space_id, n.project_agent_id)',
|
|
250
286
|
'CREATE CONSTRAINT fuli_project_agent_task_id IF NOT EXISTS '
|
|
251
287
|
'FOR (n:FuliProjectAgentTask) REQUIRE n.id IS UNIQUE',
|
|
252
288
|
'CREATE CONSTRAINT fuli_project_agent_task_event_id IF NOT EXISTS '
|
|
@@ -4,17 +4,23 @@ from .runtime import GraphitiRuntime
|
|
|
4
4
|
from .store_identity import StoreIdentity
|
|
5
5
|
from .store_knowledge import StoreKnowledge
|
|
6
6
|
from .store_project_agent_executors import StoreProjectAgentExecutors
|
|
7
|
+
from .store_project_agent_memory import StoreProjectAgentMemory
|
|
8
|
+
from .store_project_agent_context import StoreProjectAgentContext
|
|
7
9
|
from .store_project_agents import StoreProjectAgents
|
|
8
10
|
from .store_project_agent_tasks import StoreProjectAgentTasks
|
|
9
11
|
from .store_projects import StoreProjects
|
|
10
12
|
from .store_publication import StorePublication
|
|
11
13
|
from .store_records import StoreRecords
|
|
14
|
+
from .store_task_contexts import StoreTaskContexts
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class GraphStore(
|
|
15
18
|
StoreIdentity,
|
|
16
19
|
StoreProjects,
|
|
17
20
|
StoreProjectAgents,
|
|
21
|
+
StoreProjectAgentMemory,
|
|
22
|
+
StoreProjectAgentContext,
|
|
23
|
+
StoreTaskContexts,
|
|
18
24
|
StoreProjectAgentExecutors,
|
|
19
25
|
StoreProjectAgentTasks,
|
|
20
26
|
StorePublication,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Resolve a durable role for the already-running caller, without execution."""
|
|
2
|
+
|
|
3
|
+
from .personal_project_access import authorize_personal_project
|
|
4
|
+
from .project_agent_context_models import ProjectAgentContextResolution
|
|
5
|
+
from .project_agent_task_models import ProjectAgentTaskSubmit
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StoreProjectAgentContext:
|
|
9
|
+
async def resolve_project_agent_context(self, actor, request):
|
|
10
|
+
self._require_personal()
|
|
11
|
+
space = await self.authorize(actor, request.personal_space_id, 'reader')
|
|
12
|
+
await authorize_personal_project(self, actor, space, request.personal_project_id)
|
|
13
|
+
owner = None
|
|
14
|
+
if request.session_id and not request.agent_id:
|
|
15
|
+
rows, _, _ = await self.runtime.driver.execute_query(
|
|
16
|
+
'''
|
|
17
|
+
MATCH (session:FuliTaskContextSession {id: $session_id})-[:HAS_CONTEXT]->
|
|
18
|
+
(context:FuliTaskContext {personal_space_id: $space_id,
|
|
19
|
+
personal_project_id: $project_id, completed: false})
|
|
20
|
+
WHERE session.current_token = context.token
|
|
21
|
+
AND ($turn_id IS NULL OR context.turn_id = $turn_id)
|
|
22
|
+
RETURN context.project_agent_id AS agent_id
|
|
23
|
+
''', session_id=self._task_session_id(request.personal_space_id,
|
|
24
|
+
request.source_application, request.session_id),
|
|
25
|
+
space_id=request.personal_space_id, project_id=request.personal_project_id,
|
|
26
|
+
turn_id=request.turn_id,
|
|
27
|
+
routing_='r',
|
|
28
|
+
)
|
|
29
|
+
owner = rows[0]['agent_id'] if rows else None
|
|
30
|
+
# Reuse the task router's staffing policy. These fixed values and the
|
|
31
|
+
# request are not persisted; raw user prompts never enter this API.
|
|
32
|
+
selection = ProjectAgentTaskSubmit(
|
|
33
|
+
personal_space_id=request.personal_space_id,
|
|
34
|
+
personal_project_id=request.personal_project_id,
|
|
35
|
+
idempotency_key='read-only-task-context',
|
|
36
|
+
title='Resolve task context', objective='Select one existing durable role.',
|
|
37
|
+
work_kind=request.work_kind, required_capabilities=request.required_capabilities,
|
|
38
|
+
lead_agent_id=request.agent_id or owner, source_application=request.source_application,
|
|
39
|
+
routing_reason='Read-only task-entry context recovery',
|
|
40
|
+
)
|
|
41
|
+
selected, candidates, reason, basis = await self._select_agents(actor, space, selection)
|
|
42
|
+
if not selected and reason == 'no_match' and not request.required_capabilities:
|
|
43
|
+
selected, candidates, reason, basis = await self._select_agents(
|
|
44
|
+
actor, space, selection.model_copy(update={'work_kind': 'project_context'}),
|
|
45
|
+
)
|
|
46
|
+
if selected:
|
|
47
|
+
reason = 'project_context_fallback'
|
|
48
|
+
basis = [
|
|
49
|
+
f'no role matched requested work kind: {request.work_kind}; '
|
|
50
|
+
'using project_context for continuity only',
|
|
51
|
+
*basis,
|
|
52
|
+
]
|
|
53
|
+
if owner and selected:
|
|
54
|
+
reason, basis = 'active_task_owner', ['reuse the owner of this host session task']
|
|
55
|
+
agent = await self.get_project_agent(
|
|
56
|
+
actor, request.personal_space_id, request.personal_project_id,
|
|
57
|
+
selected[0]['agent_id'],
|
|
58
|
+
) if selected else None
|
|
59
|
+
return ProjectAgentContextResolution(
|
|
60
|
+
status='ready' if agent else (
|
|
61
|
+
'manual_selection' if reason == 'manual_agent_selection'
|
|
62
|
+
else 'agent_unavailable' if reason == 'agent_unavailable' else 'unassigned'
|
|
63
|
+
),
|
|
64
|
+
agent=agent, reason=reason, match_basis=basis, candidate_count=len(candidates),
|
|
65
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Project-local Agent continuity and recruitment authorization."""
|
|
2
|
+
|
|
3
|
+
from .personal_project_access import authorize_personal_project
|
|
4
|
+
from .project_agent_coordination_models import (
|
|
5
|
+
ProjectAgentCoordinationPolicyRecord,
|
|
6
|
+
ProjectAgentCoordinationPolicyUpdate,
|
|
7
|
+
)
|
|
8
|
+
from .provider_values import native_datetime, now_utc, stable_uuid
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StoreProjectAgentCoordinationPolicy:
|
|
12
|
+
"""Persist the two user-facing project Agent automation switches."""
|
|
13
|
+
|
|
14
|
+
async def get_project_agent_coordination_policy(
|
|
15
|
+
self,
|
|
16
|
+
actor: dict,
|
|
17
|
+
personal_space_id: str,
|
|
18
|
+
personal_project_id: str,
|
|
19
|
+
) -> ProjectAgentCoordinationPolicyRecord:
|
|
20
|
+
self._require_personal()
|
|
21
|
+
space = await self.authorize(actor, personal_space_id, 'reader')
|
|
22
|
+
await authorize_personal_project(
|
|
23
|
+
self,
|
|
24
|
+
actor,
|
|
25
|
+
space,
|
|
26
|
+
personal_project_id,
|
|
27
|
+
)
|
|
28
|
+
records, _, _ = await self.runtime.driver.execute_query(
|
|
29
|
+
'''
|
|
30
|
+
MATCH (:FuliSpace {id: $personal_space_id, kind: 'personal'})-
|
|
31
|
+
[:CONTAINS_PROJECT]->
|
|
32
|
+
(project:FuliPersonalProject {project_id: $personal_project_id})
|
|
33
|
+
OPTIONAL MATCH (project)-[:HAS_PROJECT_AGENT_COORDINATION_POLICY]->
|
|
34
|
+
(policy:FuliProjectAgentCoordinationPolicy {
|
|
35
|
+
policy_id: $policy_id
|
|
36
|
+
})
|
|
37
|
+
RETURN policy
|
|
38
|
+
''',
|
|
39
|
+
personal_space_id=personal_space_id,
|
|
40
|
+
personal_project_id=personal_project_id,
|
|
41
|
+
policy_id=self._project_agent_coordination_policy_id(
|
|
42
|
+
personal_space_id,
|
|
43
|
+
personal_project_id,
|
|
44
|
+
),
|
|
45
|
+
routing_='r',
|
|
46
|
+
)
|
|
47
|
+
raw = dict(records[0]['policy']) if records and records[0].get('policy') else {}
|
|
48
|
+
return ProjectAgentCoordinationPolicyRecord(
|
|
49
|
+
personal_space_id=personal_space_id,
|
|
50
|
+
personal_project_id=personal_project_id,
|
|
51
|
+
ask_before_recruitment=raw.get('ask_before_recruitment', True),
|
|
52
|
+
auto_reuse_previous_agent=raw.get(
|
|
53
|
+
'auto_reuse_previous_agent',
|
|
54
|
+
True,
|
|
55
|
+
),
|
|
56
|
+
updated_at=native_datetime(raw.get('updated_at')),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def update_project_agent_coordination_policy(
|
|
60
|
+
self,
|
|
61
|
+
actor: dict,
|
|
62
|
+
request: ProjectAgentCoordinationPolicyUpdate,
|
|
63
|
+
) -> ProjectAgentCoordinationPolicyRecord:
|
|
64
|
+
self._require_personal()
|
|
65
|
+
space = await self.authorize(
|
|
66
|
+
actor,
|
|
67
|
+
request.personal_space_id,
|
|
68
|
+
'maintainer',
|
|
69
|
+
)
|
|
70
|
+
await authorize_personal_project(
|
|
71
|
+
self,
|
|
72
|
+
actor,
|
|
73
|
+
space,
|
|
74
|
+
request.personal_project_id,
|
|
75
|
+
)
|
|
76
|
+
policy_id = self._project_agent_coordination_policy_id(
|
|
77
|
+
request.personal_space_id,
|
|
78
|
+
request.personal_project_id,
|
|
79
|
+
)
|
|
80
|
+
updated_at = now_utc()
|
|
81
|
+
await self.runtime.driver.execute_query(
|
|
82
|
+
'''
|
|
83
|
+
MATCH (:FuliSpace {id: $personal_space_id, kind: 'personal'})-
|
|
84
|
+
[:CONTAINS_PROJECT]->
|
|
85
|
+
(project:FuliPersonalProject {project_id: $personal_project_id})
|
|
86
|
+
MERGE (policy:FuliProjectAgentCoordinationPolicy {
|
|
87
|
+
policy_id: $policy_id
|
|
88
|
+
})
|
|
89
|
+
ON CREATE SET policy.personal_space_id = $personal_space_id,
|
|
90
|
+
policy.personal_project_id = $personal_project_id,
|
|
91
|
+
policy.created_at = $updated_at
|
|
92
|
+
SET policy.ask_before_recruitment = $ask_before_recruitment,
|
|
93
|
+
policy.auto_reuse_previous_agent = $auto_reuse_previous_agent,
|
|
94
|
+
policy.updated_at = $updated_at
|
|
95
|
+
MERGE (project)-[:HAS_PROJECT_AGENT_COORDINATION_POLICY]->(policy)
|
|
96
|
+
''',
|
|
97
|
+
policy_id=policy_id,
|
|
98
|
+
personal_space_id=request.personal_space_id,
|
|
99
|
+
personal_project_id=request.personal_project_id,
|
|
100
|
+
ask_before_recruitment=request.ask_before_recruitment,
|
|
101
|
+
auto_reuse_previous_agent=request.auto_reuse_previous_agent,
|
|
102
|
+
updated_at=updated_at,
|
|
103
|
+
)
|
|
104
|
+
return ProjectAgentCoordinationPolicyRecord(
|
|
105
|
+
personal_space_id=request.personal_space_id,
|
|
106
|
+
personal_project_id=request.personal_project_id,
|
|
107
|
+
ask_before_recruitment=request.ask_before_recruitment,
|
|
108
|
+
auto_reuse_previous_agent=request.auto_reuse_previous_agent,
|
|
109
|
+
updated_at=updated_at,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def _project_agent_coordination_policy_id(
|
|
113
|
+
self,
|
|
114
|
+
personal_space_id: str,
|
|
115
|
+
personal_project_id: str,
|
|
116
|
+
) -> str:
|
|
117
|
+
return stable_uuid(
|
|
118
|
+
self.settings.provider_id,
|
|
119
|
+
personal_space_id,
|
|
120
|
+
'project-agent-coordination-policy',
|
|
121
|
+
personal_project_id,
|
|
122
|
+
)
|