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.
Files changed (148) hide show
  1. package/README.md +69 -13
  2. package/README.zh-CN.md +60 -14
  3. package/dist/web/assets/{AboutPage-ByCUor-j.js → AboutPage-BcZeFcHe.js} +1 -1
  4. package/dist/web/assets/{ConnectionsPage-DkdkZ40Q.js → ConnectionsPage-xAQYl1SQ.js} +1 -1
  5. package/dist/web/assets/EmployeeRecruitDialog-CbPAmhy7.js +1 -0
  6. package/dist/web/assets/EmployeeRecruitDialog-CfFlyKSh.css +1 -0
  7. package/dist/web/assets/EmployeeWorkbenchPage-BQvTLoc8.css +1 -0
  8. package/dist/web/assets/EmployeeWorkbenchPage-DF3EL0Ui.js +1 -0
  9. package/dist/web/assets/KnowledgeEditDialog-UnnBDt0I.js +1 -0
  10. package/dist/web/assets/{KnowledgeEditDialog-DWUV_NPp.css → KnowledgeEditDialog-iv-dgUeD.css} +1 -1
  11. package/dist/web/assets/{KnowledgeInspector-CYAjAb9J.js → KnowledgeInspector-BqPJH5Rr.js} +1 -1
  12. package/dist/web/assets/{KnowledgeOrganizerPage-CV4F54R_.js → KnowledgeOrganizerPage-CyvRflXM.js} +1 -1
  13. package/dist/web/assets/KnowledgePage-59eczTqe.js +1 -0
  14. package/dist/web/assets/{KnowledgeWorkspace-z4u5mXOG.js → KnowledgeWorkspace-CfIjhOog.js} +1 -1
  15. package/dist/web/assets/{OverviewPage-BWOfeS6k.js → OverviewPage-Xyfm5xkZ.js} +1 -1
  16. package/dist/web/assets/{PersonalProfilePage-c3w4-h7a.js → PersonalProfilePage-DB9mByB3.js} +1 -1
  17. package/dist/web/assets/PersonalProjectsPage-_-YXVi5O.js +1 -0
  18. package/dist/web/assets/ProjectAgentsPage-C3vW6wmk.css +1 -0
  19. package/dist/web/assets/ProjectAgentsPage-DeeQByPX.js +11 -0
  20. package/dist/web/assets/{PublicProjectsPage-DdH0oGB3.js → PublicProjectsPage-BEZ1YPs3.js} +1 -1
  21. package/dist/web/assets/{ReviewPage-BYIpcIHG.js → ReviewPage-CQYGwFBH.js} +1 -1
  22. package/dist/web/assets/SearchableSelect-D27H0MoP.js +1 -0
  23. package/dist/web/assets/{SettingsPage-2VkbXgZi.js → SettingsPage-BlEZ03Yi.js} +1 -1
  24. package/dist/web/assets/{WritingTastePage-BOPQZuva.js → WritingTastePage-DvRt_nmP.js} +1 -1
  25. package/dist/web/assets/catalog-DFvIaHVS.js +1 -0
  26. package/dist/web/assets/{console-BlAhujsH.js → console-tOxOl8Vq.js} +1 -1
  27. package/dist/web/assets/conversation-launcher-settings-DuFSpPAo.js +1 -0
  28. package/dist/web/assets/{i18n-Ci3heuiL.js → i18n-CgBHZthI.js} +3 -3
  29. package/dist/web/assets/{identity-4_-v6cB7.js → identity-CFo_Uw1H.js} +1 -1
  30. package/dist/web/assets/index-CJc5ppXH.css +1 -0
  31. package/dist/web/assets/index-yCi3cz2l.js +2 -0
  32. package/dist/web/assets/{model-yG0YqUyZ.js → model-BtnVr7fX.js} +1 -1
  33. package/dist/web/assets/{useApi-CROJJdhE-twhdZArA.js → useApi-CROJJdhE-B6MHm_Av.js} +1 -1
  34. package/dist/web/assets/{vue-router-C-wot_Di.js → vue-router-CzHm-y-x.js} +1 -1
  35. package/dist/web/index.html +7 -6
  36. package/docs/employee-agents.md +99 -0
  37. package/docs/project-agent-memory.md +479 -0
  38. package/graph-provider/fuli_graph/knowledge_audit_models.py +171 -0
  39. package/graph-provider/fuli_graph/model_base.py +5 -0
  40. package/graph-provider/fuli_graph/model_validation.py +32 -0
  41. package/graph-provider/fuli_graph/models.py +26 -170
  42. package/graph-provider/fuli_graph/project_agent_context_models.py +29 -0
  43. package/graph-provider/fuli_graph/project_agent_coordination_models.py +22 -0
  44. package/graph-provider/fuli_graph/project_agent_executor_models.py +35 -7
  45. package/graph-provider/fuli_graph/project_agent_memory_models.py +79 -0
  46. package/graph-provider/fuli_graph/project_agent_memory_routes.py +46 -0
  47. package/graph-provider/fuli_graph/project_agent_models.py +9 -0
  48. package/graph-provider/fuli_graph/project_agent_recruitment_values.py +48 -0
  49. package/graph-provider/fuli_graph/project_agent_routes.py +50 -1
  50. package/graph-provider/fuli_graph/project_agent_task_models.py +220 -29
  51. package/graph-provider/fuli_graph/runtime.py +37 -1
  52. package/graph-provider/fuli_graph/store.py +6 -0
  53. package/graph-provider/fuli_graph/store_project_agent_context.py +65 -0
  54. package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +122 -0
  55. package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +175 -11
  56. package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +33 -10
  57. package/graph-provider/fuli_graph/store_project_agent_executors.py +181 -14
  58. package/graph-provider/fuli_graph/store_project_agent_memory.py +174 -0
  59. package/graph-provider/fuli_graph/store_project_agent_task_activity.py +423 -0
  60. package/graph-provider/fuli_graph/store_project_agent_task_persistence.py +152 -0
  61. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +187 -21
  62. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +756 -164
  63. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +396 -7
  64. package/graph-provider/fuli_graph/store_project_agent_tasks.py +265 -465
  65. package/graph-provider/fuli_graph/store_project_agents.py +214 -46
  66. package/graph-provider/fuli_graph/store_task_contexts.py +217 -0
  67. package/graph-provider/fuli_graph/store_transactions.py +40 -0
  68. package/graph-provider/fuli_graph/task_context_models.py +50 -0
  69. package/graph-provider/fuli_graph/task_context_routes.py +31 -0
  70. package/graph-provider/fuli_graph/workflow_observation_models.py +1 -1
  71. package/graph-provider/pyproject.toml +1 -1
  72. package/npm-shrinkwrap.json +8 -8
  73. package/package.json +5 -2
  74. package/skills/capturing-session-knowledge/SKILL.md +18 -6
  75. package/src/adaptive-runtime/executor-pool.js +46 -5
  76. package/src/adaptive-runtime/graph-services.js +10 -3
  77. package/src/adaptive-runtime/lease-client.js +66 -14
  78. package/src/adaptive-runtime/runtime-broker.js +24 -9
  79. package/src/agent-tools/employee-definitions.js +43 -0
  80. package/src/agent-tools/graph-definitions.js +48 -6
  81. package/src/agent-tools/graph-handlers.js +15 -0
  82. package/src/agent-tools/project-agent-definitions.js +35 -4
  83. package/src/agent-tools/project-agent-memory-definitions.js +36 -0
  84. package/src/agent-tools.js +6 -2
  85. package/src/agents/codex/lifecycle-hook.js +107 -0
  86. package/src/agents/codex/lifecycle-hooks.js +231 -0
  87. package/src/agents/cursor/lifecycle-hook.js +133 -0
  88. package/src/agents/cursor/lifecycle-hooks.js +50 -0
  89. package/src/agents/lifecycle-message.js +19 -0
  90. package/src/app/agent-request-context.js +23 -0
  91. package/src/cli/command-registry.js +2 -0
  92. package/src/cli/employee-command.js +17 -0
  93. package/src/cli/remote-mcp-command.js +282 -0
  94. package/src/cli/setup-command.js +5 -0
  95. package/src/cli.js +10 -0
  96. package/src/employees/catalog/jefa.json +21 -0
  97. package/src/employees/install-package.js +77 -0
  98. package/src/employees/management-policy.js +88 -0
  99. package/src/employees/manifest.js +53 -0
  100. package/src/employees/package-registry.js +107 -0
  101. package/src/employees/project-selection.js +50 -0
  102. package/src/employees/recruitment.js +199 -0
  103. package/src/employees/service.js +83 -0
  104. package/src/employees/task-entry.js +39 -0
  105. package/src/graphiti/agent-knowledge-workflows.js +135 -25
  106. package/src/graphiti/federated-application.js +78 -35
  107. package/src/graphiti/project-agent-control-plane.js +32 -3
  108. package/src/graphiti/project-agent-mapping.js +175 -4
  109. package/src/graphiti/project-agent-memory.js +115 -0
  110. package/src/graphiti/project-agent-task-entry.js +126 -0
  111. package/src/graphiti/project-agent-workflows.js +49 -14
  112. package/src/graphiti/provider-client.js +78 -4
  113. package/src/http/api-router.js +25 -5
  114. package/src/http/employee-api-router.js +64 -0
  115. package/src/http/error-mapping.js +4 -0
  116. package/src/http/graph-api-router.js +14 -0
  117. package/src/http/request-policy.js +6 -3
  118. package/src/http/static-handler.js +4 -3
  119. package/src/mcp/create-mcp-server.js +98 -37
  120. package/src/mcp/direct-call.js +38 -0
  121. package/src/mcp/instructions.js +8 -7
  122. package/src/mcp/provider-task-context-registry.js +74 -0
  123. package/src/mcp/remote-http-server.js +761 -0
  124. package/src/mcp/session-id.js +29 -3
  125. package/src/mcp/task-context-registry.js +88 -29
  126. package/src/mcp/tool-annotations.js +12 -2
  127. package/src/mcp/tool-result.js +8 -3
  128. package/src/mcp-server.js +20 -8
  129. package/src/native-runtime/runtime.js +58 -8
  130. package/src/server.js +76 -28
  131. package/src/setup/agent-installation-status.js +38 -6
  132. package/src/setup/agents.js +20 -8
  133. package/src/setup/claude-code-config.js +55 -13
  134. package/src/setup/codex-bootstrap.js +11 -10
  135. package/src/setup/codex-config.js +74 -7
  136. package/src/setup/config-backup.js +18 -2
  137. package/src/setup/cursor-config.js +22 -5
  138. package/src/setup/setup.js +30 -22
  139. package/src/storage/json-file.js +11 -3
  140. package/dist/web/assets/KnowledgeEditDialog-DSe3Gwep.js +0 -1
  141. package/dist/web/assets/KnowledgePage-vYYXipOi.js +0 -1
  142. package/dist/web/assets/PersonalProjectsPage-B7AlLEwf.js +0 -1
  143. package/dist/web/assets/ProjectAgentsPage-CgftpIPB.css +0 -1
  144. package/dist/web/assets/ProjectAgentsPage-DTHcL_ox.js +0 -11
  145. package/dist/web/assets/SearchableSelect-Dz13ClQb.js +0 -1
  146. package/dist/web/assets/conversation-launcher-settings-DTYxiLJt.js +0 -1
  147. package/dist/web/assets/index-BnH5mcZx.css +0 -1
  148. package/dist/web/assets/index-C67Q0JY2.js +0 -2
@@ -7,27 +7,142 @@ import json
7
7
 
8
8
  from .project_agent_models import ProjectAgentModelStrategy, ProjectAgentProfile
9
9
  from .project_agent_task_models import (
10
+ ProjectAgentActivityTask,
10
11
  ProjectAgentRoutingDecisionRecord,
11
12
  ProjectAgentTaskExecutionSummary,
12
13
  ProjectAgentTaskEventRecord,
13
14
  ProjectAgentTaskParticipantRecord,
14
15
  ProjectAgentTaskRecord,
15
16
  ProjectAgentTaskRouteResult,
17
+ ProjectAgentTokenUsage,
18
+ ProjectAgentWorkerRuntime,
16
19
  )
17
20
  from .provider_values import native_datetime
18
21
 
19
22
 
20
23
  TERMINAL_TASK_STATUSES = {'completed', 'failed', 'cancelled'}
24
+ SOURCE_SESSION_FIELDS = {
25
+ 'source_application', 'source_session_id', 'source_session_url',
26
+ }
27
+ WORKER_DISPLAY_FIELDS = {'worker_label', 'worker_occupation_emoji'}
21
28
 
22
29
 
23
30
  class StoreProjectAgentTaskReads:
24
31
  """Read durable task history and derive client-facing route results."""
25
32
 
26
- async def _find_task_row(self, personal_space_id, task_id):
33
+ @classmethod
34
+ def _event_with_latest_execution_evidence(cls, event, event_rows):
35
+ reported = sorted(
36
+ (dict(item) for item in event_rows),
37
+ key=lambda item: (
38
+ str(item.get('created_at') or ''),
39
+ str(item.get('event_id') or ''),
40
+ ),
41
+ )
42
+ merged = dict(event)
43
+ worker_id = event.get('worker_id') or next(
44
+ (item['worker_id'] for item in reversed(reported)
45
+ if item.get('worker_id')), None,
46
+ )
47
+ if worker_id:
48
+ reported = [item for item in reported
49
+ if item.get('worker_id') == worker_id]
50
+ worker_events = reported
51
+ reported = cls._current_worker_run(reported)
52
+ source_events = cls._current_reporter_session(reported)
53
+ for field in (
54
+ 'source_application', 'source_session_id', 'source_session_url',
55
+ 'tools_used', 'actual_model_provider', 'actual_model',
56
+ 'actual_executor_id', 'matched_executor_rule_id',
57
+ 'token_usage_json', 'worker_id', 'worker_label',
58
+ 'worker_occupation_emoji', 'worker_status', 'worker_runtime_json',
59
+ ):
60
+ scope = worker_events if field in WORKER_DISPLAY_FIELDS else (
61
+ source_events if field in SOURCE_SESSION_FIELDS else reported
62
+ )
63
+ if reported and (worker_id or field in SOURCE_SESSION_FIELDS):
64
+ merged[field] = None
65
+ for candidate in reversed(scope):
66
+ value = candidate.get(field)
67
+ if value is not None and value != '':
68
+ merged[field] = value
69
+ break
70
+ return merged
71
+
72
+ @staticmethod
73
+ def _event_json_fields(event):
74
+ fields = {}
75
+ for name, model in (
76
+ ('token_usage', ProjectAgentTokenUsage),
77
+ ('worker_runtime', ProjectAgentWorkerRuntime),
78
+ ):
79
+ value = event.get(f'{name}_json')
80
+ if value:
81
+ try:
82
+ fields[name] = model.model_validate_json(value)
83
+ except (TypeError, ValueError):
84
+ pass
85
+ return fields
86
+
87
+ @classmethod
88
+ def _current_worker_run(cls, events):
89
+ """Never carry usage, tools, or model claims across worker sessions."""
90
+ start, identity = 0, None
91
+ for index, event in enumerate(events):
92
+ runtime = cls._event_json_fields(event).get('worker_runtime') \
93
+ if isinstance(event, dict) else event.worker_runtime
94
+ if runtime is None:
95
+ continue
96
+ reported_identity = (runtime.application, runtime.session_id)
97
+ if reported_identity != identity:
98
+ start, identity = index, reported_identity
99
+ return events[start:]
100
+
101
+ @staticmethod
102
+ def _current_reporter_session(events):
103
+ """Keep a reporting session URL paired with its own host identity."""
104
+ start, identity = 0, None
105
+ for index, event in enumerate(events):
106
+ get = event.get if isinstance(event, dict) else (
107
+ lambda field: getattr(event, field, None)
108
+ )
109
+ reported = (get('source_application'), get('source_session_id'))
110
+ if reported == (None, None):
111
+ continue
112
+ if reported != identity:
113
+ start, identity = index, reported
114
+ return events[start:]
115
+
116
+ @classmethod
117
+ def _activity_executor_fields(cls, event):
118
+ fields = {
119
+ name: event.get(name)
120
+ for name in (
121
+ 'actual_executor_id', 'matched_executor_rule_id',
122
+ 'source_session_id', 'source_session_url', 'tools_used',
123
+ 'worker_id', 'worker_label', 'worker_occupation_emoji',
124
+ 'worker_status',
125
+ )
126
+ }
127
+ fields.update(cls._event_json_fields(event))
128
+ return {key: value for key, value in fields.items()
129
+ if key in ProjectAgentActivityTask.model_fields}
130
+
131
+ async def _find_task_row(
132
+ self,
133
+ personal_space_id,
134
+ task_id,
135
+ personal_project_id=None,
136
+ ):
27
137
  records, _, _ = await self.runtime.driver.execute_query(
28
- self._task_read_query('WHERE task.task_id = $task_id'),
138
+ self._task_read_query(
139
+ '''WHERE task.task_id = $task_id
140
+ AND ($personal_project_id IS NULL
141
+ OR task.personal_project_id = $personal_project_id)'''
142
+ ),
29
143
  personal_space_id=personal_space_id,
30
144
  task_id=task_id,
145
+ personal_project_id=personal_project_id,
31
146
  routing_='r',
32
147
  )
33
148
  return records[0] if records else None
@@ -119,9 +234,12 @@ class StoreProjectAgentTaskReads:
119
234
  'worker_label',
120
235
  'worker_occupation_emoji',
121
236
  'worker_status',
237
+ 'source_session_url',
238
+ 'tools_used',
122
239
  ):
123
240
  if name in ProjectAgentTaskEventRecord.model_fields:
124
241
  fields[name] = event.get(name)
242
+ fields.update(self._event_json_fields(event))
125
243
  events.append(ProjectAgentTaskEventRecord(**fields))
126
244
  events.sort(key=lambda item: item.created_at)
127
245
  fields = {
@@ -132,6 +250,9 @@ class StoreProjectAgentTaskReads:
132
250
  'objective': raw['objective'],
133
251
  'work_kind': raw['work_kind'],
134
252
  'required_capabilities': list(raw.get('required_capabilities') or []),
253
+ 'executor_capability_hints': list(
254
+ raw.get('executor_capability_hints') or []
255
+ ),
135
256
  'duration': raw.get('duration') or 'ongoing',
136
257
  'staffing_intent': raw.get('staffing_intent') or 'reuse_preferred',
137
258
  'status': raw['status'],
@@ -298,14 +419,16 @@ class StoreProjectAgentTaskReads:
298
419
  or event.actual_executor_id
299
420
  or event.actual_model_provider
300
421
  or event.actual_model
422
+ or event.token_usage
301
423
  or event.status in {
302
424
  'running', 'paused', 'awaiting_review', 'blocked',
303
425
  'completed', 'failed', 'cancelled',
304
426
  }
305
427
  )
306
428
 
307
- @staticmethod
429
+ @classmethod
308
430
  def _execution_summary_row(
431
+ cls,
309
432
  participant,
310
433
  agent_name,
311
434
  occupation_emoji,
@@ -316,12 +439,17 @@ class StoreProjectAgentTaskReads:
316
439
  ):
317
440
  task_fallback = task_fallback or {}
318
441
  latest = events[-1] if events else None
442
+ run_events = cls._current_worker_run(events)
443
+ source_events = cls._current_reporter_session(run_events)
319
444
 
320
445
  def latest_value(name):
446
+ scope = events if name in WORKER_DISPLAY_FIELDS else (
447
+ source_events if name in SOURCE_SESSION_FIELDS else run_events
448
+ )
321
449
  return next(
322
450
  (
323
451
  getattr(event, name)
324
- for event in reversed(events)
452
+ for event in reversed(scope)
325
453
  if getattr(event, name, None) is not None
326
454
  ),
327
455
  None,
@@ -347,6 +475,9 @@ class StoreProjectAgentTaskReads:
347
475
  executor=actual_executor_id,
348
476
  executor_id=actual_executor_id,
349
477
  source_application=latest_value('source_application'),
478
+ source_session_id=latest_value('source_session_id'),
479
+ source_session_url=latest_value('source_session_url'),
480
+ tools_used=latest_value('tools_used'),
350
481
  actual_model_provider=(
351
482
  latest_value('actual_model_provider')
352
483
  or task_fallback.get('actual_model_provider')
@@ -355,6 +486,7 @@ class StoreProjectAgentTaskReads:
355
486
  latest_value('actual_model')
356
487
  or task_fallback.get('actual_model')
357
488
  ),
489
+ token_usage=latest_value('token_usage'),
358
490
  work_summary=(
359
491
  latest.summary
360
492
  if latest
@@ -365,6 +497,7 @@ class StoreProjectAgentTaskReads:
365
497
  worker_id=worker_id,
366
498
  worker_label=worker_label,
367
499
  worker_occupation_emoji=worker_occupation_emoji,
500
+ worker_runtime=latest_value('worker_runtime'),
368
501
  )
369
502
 
370
503
  async def _archive_finished_temporary_agent(self, request, raw_task, updated_at):
@@ -381,7 +514,14 @@ class StoreProjectAgentTaskReads:
381
514
  agent_type: 'temporary',
382
515
  temporary_task_id: $task_id
383
516
  })
517
+ SET agent._task_lifecycle_lock = true
518
+ REMOVE agent._task_lifecycle_lock
519
+ WITH space, task, agent
384
520
  SET agent.status = 'archived',
521
+ agent.archive_reason = coalesce(
522
+ agent.archive_reason,
523
+ 'temporary task reached terminal state'),
524
+ agent.archived_at = coalesce(agent.archived_at, $updated_at),
385
525
  agent.updated_at = $updated_at
386
526
  WITH space, agent
387
527
  MATCH (space)-[:CONTAINS_PROJECT]->(:FuliPersonalProject)-
@@ -392,7 +532,7 @@ class StoreProjectAgentTaskReads:
392
532
  assignment.end_reason = 'temporary task reached terminal state',
393
533
  assignment.ended_at = $updated_at,
394
534
  assignment.updated_at = $updated_at,
395
- assignment.revision = assignment.revision + 1
535
+ assignment.revision = coalesce(assignment.revision, 0) + 1
396
536
  ''',
397
537
  personal_space_id=request.personal_space_id,
398
538
  task_id=request.task_id,
@@ -421,10 +561,31 @@ class StoreProjectAgentTaskReads:
421
561
  json.dumps(payload, sort_keys=True, separators=(',', ':')).encode()
422
562
  ).hexdigest()
423
563
 
424
- def _route_result(self, task, recruitment, *, assigned_agent=None):
425
- if recruitment and recruitment.status == 'fulfilled':
564
+ def _route_result(
565
+ self,
566
+ task,
567
+ recruitment,
568
+ *,
569
+ assigned_agent=None,
570
+ recruitments=None,
571
+ ):
572
+ all_recruitments = list(recruitments or [])
573
+ if recruitment and not any(
574
+ item.recruitment_id == recruitment.recruitment_id
575
+ for item in all_recruitments
576
+ ):
577
+ all_recruitments.insert(0, recruitment)
578
+ fulfilled = [
579
+ item for item in all_recruitments if item.status == 'fulfilled'
580
+ ]
581
+ awaiting = [
582
+ item
583
+ for item in all_recruitments
584
+ if item.status == 'awaiting_confirmation'
585
+ ]
586
+ if all_recruitments and len(fulfilled) == len(all_recruitments):
426
587
  decision = 'recruited'
427
- elif recruitment and recruitment.status == 'awaiting_confirmation':
588
+ elif awaiting:
428
589
  decision = 'awaiting_confirmation'
429
590
  elif task.routing_outcome == 'assigned_existing':
430
591
  decision = 'reused'
@@ -433,25 +594,30 @@ class StoreProjectAgentTaskReads:
433
594
  else:
434
595
  decision = 'blocked'
435
596
  notice = None
436
- must_disclose = bool(
437
- recruitment
438
- and recruitment.confirmation_mode == 'automatic'
439
- and recruitment.status == 'fulfilled'
440
- )
597
+ disclosed_recruitments = [
598
+ item
599
+ for item in fulfilled
600
+ if item.confirmation_mode == 'automatic'
601
+ ]
602
+ must_disclose = bool(disclosed_recruitments)
441
603
  if must_disclose:
442
- notice = (
443
- f'HR {recruitment.hr_agent_id} recruited '
444
- f'{recruitment.proposed_profile.name} as a '
445
- f'{recruitment.position_kind} Agent for '
446
- f'{recruitment.proposed_profile.responsibility}. '
447
- f'Reason: {recruitment.reason}. '
448
- f'Trigger: {recruitment.trigger_source_application or "unspecified"}; '
449
- f'time: {recruitment.fulfilled_at.isoformat() if recruitment.fulfilled_at else "pending"}.'
604
+ notice = ' '.join(
605
+ (
606
+ f'HR {item.hr_agent_id} recruited '
607
+ f'{item.proposed_profile.name} as a '
608
+ f'{item.position_kind} {item.participant_role} Agent for '
609
+ f'{item.proposed_profile.responsibility}. '
610
+ f'Reason: {item.reason}. '
611
+ f'Trigger: {item.trigger_source_application or "unspecified"}; '
612
+ f'time: {item.fulfilled_at.isoformat() if item.fulfilled_at else "pending"}.'
613
+ )
614
+ for item in disclosed_recruitments
450
615
  )
451
616
  return ProjectAgentTaskRouteResult(
452
617
  task=task,
453
618
  assigned_agent=assigned_agent,
454
619
  recruitment=recruitment,
620
+ recruitments=all_recruitments,
455
621
  decision=decision,
456
622
  must_disclose_recruitment=must_disclose,
457
623
  client_notice=notice,