fuli-context 0.8.0 → 0.8.2

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 (132) hide show
  1. package/README.md +4 -0
  2. package/README.zh-CN.md +4 -1
  3. package/dist/web/assets/{AboutPage-BjMmpwJB.js → AboutPage-fBq5VFoQ.js} +1 -1
  4. package/dist/web/assets/AgentHand-CMZk5GjY.js +1 -0
  5. package/dist/web/assets/AgentName-Cydd0UI4.css +1 -0
  6. package/dist/web/assets/AgentName-DRZHAIB2.js +1 -0
  7. package/dist/web/assets/AgentProfilePage-BPQEfMhB.js +1 -0
  8. package/dist/web/assets/AgentsDirectoryPage-CKZfrcuD.js +1 -0
  9. package/dist/web/assets/{ConnectionsPage-BKGxFDos.js → ConnectionsPage-COb56qnk.js} +1 -1
  10. package/dist/web/assets/EmployeeRecruitDialog-BLTpjCcK.js +1 -0
  11. package/dist/web/assets/EmployeeWorkbenchPage-CQwZnTA0.css +1 -0
  12. package/dist/web/assets/EmployeeWorkbenchPage-HUd-gne2.js +3 -0
  13. package/dist/web/assets/{GrowthLoading-WXCj8b0r.js → GrowthLoading-Bu_kH-I8.js} +1 -1
  14. package/dist/web/assets/{KnowledgeEditDialog-Bkie08ZA.js → KnowledgeEditDialog-BTwXXRCD.js} +1 -1
  15. package/dist/web/assets/{KnowledgeInspector-Cez-Z_95.js → KnowledgeInspector-Ct5hl2--.js} +1 -1
  16. package/dist/web/assets/{KnowledgeOrganizerPage-BYmTPA_8.js → KnowledgeOrganizerPage-DMC8Hh43.js} +1 -1
  17. package/dist/web/assets/KnowledgePage-uXW1Eovz.js +1 -0
  18. package/dist/web/assets/{KnowledgeWorkspace-DKij2_B4.js → KnowledgeWorkspace-DmHIc0jb.js} +1 -1
  19. package/dist/web/assets/{NotFoundPage-HGV-q-bh.js → NotFoundPage-C0yOR9T9.js} +1 -1
  20. package/dist/web/assets/{OverviewPage-DE29_F_Y.js → OverviewPage-BreX_kb4.js} +1 -1
  21. package/dist/web/assets/{PersonalProfilePage-B95DFc7x.js → PersonalProfilePage-CX1a6_E3.js} +1 -1
  22. package/dist/web/assets/PersonalProjectsPage-Br1S8ETM.js +1 -0
  23. package/dist/web/assets/ProjectAgentsPage-CA04h1Uf.js +13 -0
  24. package/dist/web/assets/ProjectAgentsPage-CZWWV4Kw.css +1 -0
  25. package/dist/web/assets/{ProjectScopePicker-Cu5PdxF6.js → ProjectScopePicker-DVHfXX1f.js} +1 -1
  26. package/dist/web/assets/{PublicProjectsPage-BY4B3_t3.js → PublicProjectsPage-CtctKml1.js} +1 -1
  27. package/dist/web/assets/{ReviewPage-C9vCesNu.js → ReviewPage-DUqaK-do.js} +1 -1
  28. package/dist/web/assets/{SearchableSelect-214TvfYg.js → SearchableSelect-BZtyYmZN.js} +1 -1
  29. package/dist/web/assets/{SettingsPage-D9TnzcFc.js → SettingsPage-CB4ymLr8.js} +1 -1
  30. package/dist/web/assets/{SharedJefaReportPage-CdSGI_OE.js → SharedJefaReportPage-CbFtrDCY.js} +1 -1
  31. package/dist/web/assets/{WritingTastePage-CUE1dU8f.js → WritingTastePage-B7lSzs2z.js} +1 -1
  32. package/dist/web/assets/agent-profile-CdKOh5xw.css +1 -0
  33. package/dist/web/assets/agent-profile.css_vue_type_style_index_0_src_true_lang-C22UdfU0.js +1 -0
  34. package/dist/web/assets/catalog-DU4Kfd-N.js +1 -0
  35. package/dist/web/assets/{client-Bl4oiW3E.js → client-CEHkJJ-5.js} +1 -1
  36. package/dist/web/assets/{console-BAIVNC-N.js → console--CZxc3PW.js} +1 -1
  37. package/dist/web/assets/{conversation-launcher-settings-DpRI_Jp2.js → conversation-launcher-settings-Co1RlTFH.js} +1 -1
  38. package/dist/web/assets/{i18n-CVFADb_M.js → i18n-BtOOoyJd.js} +3 -3
  39. package/dist/web/assets/{identity-Dt0WZgqo.js → identity-CsowvtIm.js} +1 -1
  40. package/dist/web/assets/index-7X3LGByg.js +2 -0
  41. package/dist/web/assets/index-CWMqDq8g.css +1 -0
  42. package/dist/web/assets/{model-B8sOxih0.js → model-CGpllrds.js} +1 -1
  43. package/dist/web/assets/{personal-profile-graph-aAqex7ty.js → personal-profile-graph-BNTG4PSY.js} +1 -1
  44. package/dist/web/assets/profile-model-BQO4zNs9.js +1 -0
  45. package/dist/web/assets/{shared-report-BQD6l_cS.js → shared-report-BFUJqqQe.js} +1 -1
  46. package/dist/web/assets/task-evidence-BUu7yYIn.js +1 -0
  47. package/dist/web/assets/{useApi-CROJJdhE-CuJ16v2S.js → useApi-CROJJdhE-raNlHRDl.js} +1 -1
  48. package/dist/web/assets/{useModalDialog-Ddv2XJQi.js → useModalDialog-CroGk2Cs.js} +1 -1
  49. package/dist/web/assets/{vue-router-C8KuEZ6a.js → vue-router-JFYBLXB1.js} +1 -1
  50. package/dist/web/index.html +12 -10
  51. package/docs/agent-conversations-and-collaboration.md +115 -0
  52. package/docs/agent-interface-architecture.md +17 -0
  53. package/docs/employee-agents.md +14 -1
  54. package/docs/project-agent-memory.md +34 -1
  55. package/graph-provider/fuli_graph/agent_conversation_models.py +72 -0
  56. package/graph-provider/fuli_graph/agent_conversation_routes.py +47 -0
  57. package/graph-provider/fuli_graph/agent_loan_models.py +28 -0
  58. package/graph-provider/fuli_graph/agent_quality_policy.py +151 -0
  59. package/graph-provider/fuli_graph/agent_verification_models.py +22 -0
  60. package/graph-provider/fuli_graph/conversation_context.py +30 -0
  61. package/graph-provider/fuli_graph/knowledge_search.py +8 -16
  62. package/graph-provider/fuli_graph/project_agent_executor_models.py +3 -0
  63. package/graph-provider/fuli_graph/project_agent_memory_routes.py +2 -0
  64. package/graph-provider/fuli_graph/project_agent_models.py +10 -0
  65. package/graph-provider/fuli_graph/project_agent_routes.py +5 -1
  66. package/graph-provider/fuli_graph/project_agent_task_models.py +4 -0
  67. package/graph-provider/fuli_graph/runtime.py +14 -0
  68. package/graph-provider/fuli_graph/store.py +6 -0
  69. package/graph-provider/fuli_graph/store_agent_conversations.py +206 -0
  70. package/graph-provider/fuli_graph/store_agent_loans.py +146 -0
  71. package/graph-provider/fuli_graph/store_agent_verification.py +111 -0
  72. package/graph-provider/fuli_graph/store_project_agent_executors.py +2 -0
  73. package/graph-provider/fuli_graph/store_project_agent_task_activity.py +6 -0
  74. package/graph-provider/fuli_graph/store_project_agent_tasks.py +20 -8
  75. package/graph-provider/fuli_graph/store_task_contexts.py +9 -0
  76. package/graph-provider/fuli_graph/task_context_routes.py +7 -0
  77. package/graph-provider/pyproject.toml +1 -1
  78. package/npm-shrinkwrap.json +15 -15
  79. package/package.json +4 -3
  80. package/skills/capturing-session-knowledge/SKILL.md +20 -2
  81. package/skills/operating-fuli/SKILL.md +4 -0
  82. package/src/agent-tools/agent-collaboration-definitions.js +30 -0
  83. package/src/agent-tools/agent-conversation-definitions.js +21 -0
  84. package/src/agent-tools/employee-definitions.js +6 -5
  85. package/src/agent-tools/graph-definitions.js +19 -35
  86. package/src/agent-tools/graph-handlers.js +17 -1
  87. package/src/agent-tools/interface-catalog.js +2 -0
  88. package/src/agent-tools/project-agent-definitions.js +21 -0
  89. package/src/agent-tools/project-relation-definitions.js +36 -0
  90. package/src/agents/claude-code/conversation-transcript.js +20 -0
  91. package/src/agents/claude-code/lifecycle-hook.js +154 -0
  92. package/src/agents/claude-code/lifecycle-hooks.js +20 -0
  93. package/src/agents/codex/conversation-transcript.js +22 -0
  94. package/src/agents/codex/lifecycle-hook.js +47 -8
  95. package/src/agents/codex/lifecycle-hooks.js +6 -6
  96. package/src/agents/cursor/lifecycle-hook.js +5 -4
  97. package/src/app/agent-request-context.js +6 -1
  98. package/src/app/test-tools.js +26 -0
  99. package/src/conversations/sync-transcript.js +144 -0
  100. package/src/conversations/task-context-view.js +28 -0
  101. package/src/conversations/transcript-guard.js +54 -0
  102. package/src/conversations/transcript-reader.js +113 -0
  103. package/src/employees/service.js +8 -1
  104. package/src/graphiti/agent-collaboration.js +62 -0
  105. package/src/graphiti/agent-conversations.js +75 -0
  106. package/src/graphiti/agent-knowledge-workflows.js +8 -2
  107. package/src/graphiti/project-agent-control-plane.js +22 -2
  108. package/src/graphiti/project-agent-mapping.js +19 -1
  109. package/src/graphiti/project-agent-task-entry.js +11 -1
  110. package/src/graphiti/project-agent-workflows.js +11 -1
  111. package/src/graphiti/project-path-context.js +17 -10
  112. package/src/graphiti/provider-client.js +17 -0
  113. package/src/graphiti/verification-preference.js +24 -0
  114. package/src/http/graph-api-router.js +17 -0
  115. package/src/mcp/create-mcp-server.js +19 -5
  116. package/src/mcp/employee-tool-catalog.js +22 -0
  117. package/src/mcp/session-id.js +4 -0
  118. package/src/mcp/tool-annotations.js +8 -1
  119. package/src/mcp/tool-result.js +3 -1
  120. package/src/setup/agent-installation-status.js +1 -0
  121. package/src/setup/claude-code-config.js +13 -19
  122. package/dist/web/assets/AgentHand-BfLYPGjg.js +0 -1
  123. package/dist/web/assets/EmployeeRecruitDialog-D-W043v4.js +0 -1
  124. package/dist/web/assets/EmployeeWorkbenchPage-Cz7y-IFD.css +0 -1
  125. package/dist/web/assets/EmployeeWorkbenchPage-HHQPxdqI.js +0 -3
  126. package/dist/web/assets/KnowledgePage-Ck7tIBDn.js +0 -1
  127. package/dist/web/assets/PersonalProjectsPage-Bt7tFF5Z.js +0 -1
  128. package/dist/web/assets/ProjectAgentsPage-BioDR9OW.css +0 -1
  129. package/dist/web/assets/ProjectAgentsPage-CiiPNe_G.js +0 -12
  130. package/dist/web/assets/index-BUMq5zNh.js +0 -2
  131. package/dist/web/assets/index-C0ci8dnU.css +0 -1
  132. package/src/mcp/task-context-registry.js +0 -165
@@ -1,3 +1,5 @@
1
+ import { planAgentCollaboration, agentLoan, agentVerification } from './agent-collaboration.js';
2
+ import { queryConversations, resumeConversation, updateConversationPolicy } from './agent-conversations.js';
1
3
  import { agentProjectResolution } from './agent-knowledge-workflows.js';
2
4
  import { listAgentAttention, changeAgentAttention } from './agent-attention.js';
3
5
  import {
@@ -54,6 +56,24 @@ export const projectAgentControlPlaneHooks = Object.freeze({
54
56
  });
55
57
 
56
58
  export class ProjectAgentControlPlaneApplication {
59
+ async agentVerification(operation, input) {
60
+ this.#assertSpace(input.personalSpaceId);
61
+ return agentVerification(this, operation, input);
62
+ }
63
+ async planAgentCollaboration(input) { return planAgentCollaboration(this, input); }
64
+ async agentLoan(operation, input) {
65
+ if (input.personalSpaceId) this.#assertSpace(input.personalSpaceId);
66
+ return agentLoan(this, operation, input);
67
+ }
68
+ async queryAgentConversations(input) {
69
+ this.#assertSpace(input.personalSpaceId);
70
+ return queryConversations(this, input);
71
+ }
72
+ async resumeAgentConversation(input) { return resumeConversation(this, input); }
73
+ async updateAgentConversationPolicy(input) {
74
+ this.#assertSpace(input.personalSpaceId);
75
+ return updateConversationPolicy(this, input);
76
+ }
57
77
  async listAgentAttention(input) {
58
78
  this.#assertSpace(input.personalSpaceId);
59
79
  return listAgentAttention(this, input);
@@ -103,7 +123,7 @@ export class ProjectAgentControlPlaneApplication {
103
123
  }
104
124
 
105
125
  async listCurrentProjectAgents(input) {
106
- const resolution = await this.#resolveProject(input.projectPath);
126
+ const resolution = await this.#resolveProject(input.projectPath, input.personalProjectId ?? null);
107
127
  return listCurrentProjectAgentsWorkflow(
108
128
  this,
109
129
  agentProjectResolution(resolution),
@@ -152,7 +172,7 @@ export class ProjectAgentControlPlaneApplication {
152
172
  }
153
173
 
154
174
  async coordinateProjectAgentTask(input) {
155
- const resolution = await this.#resolveProject(input.projectPath);
175
+ const resolution = await this.#resolveProject(input.projectPath, input.personalProjectId ?? null);
156
176
  return coordinateProjectAgentTaskWorkflow(
157
177
  this,
158
178
  agentProjectResolution(resolution),
@@ -9,6 +9,8 @@ export function providerProjectAgentProfile(profile) {
9
9
  initial_preferences: profile.initialPreferences ?? profile.initial_preferences ?? [],
10
10
  status: profile.status ?? 'active'
11
11
  };
12
+ copyOptional(result, 'character', profile.character, Object.hasOwn(profile, 'character'));
13
+ copyOptional(result, 'expectations', profile.expectations, Object.hasOwn(profile, 'expectations'));
12
14
  copyOptional(result, 'occupation_emoji', profile.occupationEmoji !== undefined
13
15
  ? profile.occupationEmoji : profile.occupation_emoji,
14
16
  hasAny(profile, ['occupationEmoji', 'occupation_emoji']));
@@ -55,6 +57,8 @@ export function projectAgentRecord(value) {
55
57
  createdAt: value.created_at,
56
58
  updatedAt: value.updated_at
57
59
  };
60
+ copyOptional(result.profile, 'character', profile.character, Object.hasOwn(profile, 'character'));
61
+ copyOptional(result.profile, 'expectations', profile.expectations, Object.hasOwn(profile, 'expectations'));
58
62
  copyOptional(
59
63
  result.profile,
60
64
  'occupationEmoji',
@@ -254,6 +258,9 @@ export function providerProjectAgentTaskSubmit(input) {
254
258
  required_capabilities: input.requiredCapabilities ?? input.required_capabilities ?? [],
255
259
  executor_capability_hints:
256
260
  input.executorCapabilityHints ?? input.executor_capability_hints ?? [],
261
+ // Preserve an explicit opt-out so Provider validation rejects it instead
262
+ // of silently accepting a different contract from the caller's request.
263
+ verification_required: input.verificationRequired ?? input.verification_required ?? true,
257
264
  duration: input.duration ?? 'ongoing',
258
265
  staffing_intent: input.staffingIntent ?? input.staffing_intent ?? 'reuse_preferred',
259
266
  lead_agent_id: input.leadAgentId ?? input.lead_agent_id ?? null,
@@ -333,6 +340,7 @@ export function projectAgentTokenUsage(value) {
333
340
 
334
341
  export function providerProjectAgentTaskActivity(input) {
335
342
  const result = withoutUndefined({
343
+ artifact_revision: input.artifactRevision ?? input.artifact_revision,
336
344
  personal_space_id: input.personalSpaceId ?? input.personal_space_id,
337
345
  personal_project_id: input.personalProjectId ?? input.personal_project_id,
338
346
  task_id: input.taskId ?? input.task_id,
@@ -612,7 +620,7 @@ export function projectAgentRecruitmentRecord(value = {}) {
612
620
  }
613
621
 
614
622
  export function projectAgentProfileRecord(profile = {}) {
615
- return {
623
+ const result = {
616
624
  name: profile.name,
617
625
  responsibility: profile.responsibility,
618
626
  occupationEmoji: profile.occupation_emoji !== undefined
@@ -628,6 +636,11 @@ export function projectAgentProfileRecord(profile = {}) {
628
636
  cleanupEligible: profile.cleanup_eligible ?? false,
629
637
  status: profile.status ?? 'active'
630
638
  };
639
+ copyOptional(result, 'displayName', profile.display_name ?? profile.displayName,
640
+ hasAny(profile, ['display_name', 'displayName']));
641
+ copyOptional(result, 'character', profile.character, Object.hasOwn(profile, 'character'));
642
+ copyOptional(result, 'expectations', profile.expectations, Object.hasOwn(profile, 'expectations'));
643
+ return result;
631
644
  }
632
645
 
633
646
  export function projectAgentExecutionSummary(value) {
@@ -812,6 +825,8 @@ export function providerProjectAgentRecruitmentDecision(input) {
812
825
 
813
826
  function providerExecutorModel(value = {}) {
814
827
  return withoutUndefined({
828
+ capability_tier: value.capabilityTier ?? value.capability_tier,
829
+ cost_rank: value.costRank ?? value.cost_rank,
815
830
  provider: value.provider,
816
831
  model: value.model,
817
832
  capabilities: value.capabilities ?? [],
@@ -826,6 +841,8 @@ function providerExecutorModel(value = {}) {
826
841
 
827
842
  function executorModelRecord(value = {}) {
828
843
  return withoutUndefined({
844
+ capabilityTier: value.capability_tier ?? value.capabilityTier,
845
+ costRank: value.cost_rank ?? value.costRank,
829
846
  provider: value.provider,
830
847
  model: value.model,
831
848
  capabilities: value.capabilities ?? [],
@@ -1034,6 +1051,7 @@ export function executorPreflightRecord(value = {}) {
1034
1051
 
1035
1052
  export function providerExecutorActualReport(input) {
1036
1053
  return withoutUndefined({
1054
+ artifact_revision: input.artifactRevision ?? input.artifact_revision,
1037
1055
  personal_space_id: input.personalSpaceId ?? input.personal_space_id,
1038
1056
  personal_project_id: input.personalProjectId ?? input.personal_project_id,
1039
1057
  task_id: input.taskId ?? input.task_id,
@@ -89,7 +89,13 @@ export async function loadProjectAgentContinuity(application, {
89
89
  selection_reason: selectionReason,
90
90
  match_basis: [...matchBasis],
91
91
  role: { name: agent.profile.displayName || agent.profile.name, responsibility: agent.profile.responsibility,
92
- initial_preferences: [...(agent.profile.initialPreferences ?? [])] },
92
+ initial_preferences: [...(agent.profile.initialPreferences ?? [])],
93
+ character: {
94
+ judgment: boundedRoleText(agent.profile.character?.judgment, 2048),
95
+ taste: boundedRoleText(agent.profile.character?.taste, 2048),
96
+ personality: boundedRoleText(agent.profile.character?.personality, 2048)
97
+ },
98
+ expectations: boundedRoleText(agent.profile.expectations, 4096) },
93
99
  project_brief: profile ? { name: profile.name, purpose: profile.purpose,
94
100
  scope: profile.scope, technical_summary: profile.technical_summary,
95
101
  boundaries: profile.boundaries ?? [] } : null,
@@ -108,6 +114,10 @@ export async function loadProjectAgentContinuity(application, {
108
114
  };
109
115
  }
110
116
 
117
+ function boundedRoleText(value, limit) {
118
+ return typeof value === 'string' ? value.slice(0, limit) : '';
119
+ }
120
+
111
121
  function scopedItems(items, agentId) {
112
122
  return (items ?? []).filter(item => !item.project_agent_id || item.project_agent_id === agentId)
113
123
  .slice(0, 4).map(item => ({
@@ -178,6 +178,7 @@ export async function coordinateProjectAgentTask(
178
178
  personalProjectId: projectId,
179
179
  idempotencyKey: input.idempotencyKey,
180
180
  title: input.title,
181
+ verificationRequired: true,
181
182
  objective: input.objective,
182
183
  workKind: input.workKind,
183
184
  requiredCapabilities: input.requiredCapabilities,
@@ -213,7 +214,14 @@ export async function coordinateProjectAgentTask(
213
214
  assignment_summary: participant.assignmentSummary ?? null,
214
215
  workstream_boundary: boundaries[index] ?? null,
215
216
  context_status: contexts[index]?.status ?? 'unavailable',
216
- context: contexts[index]
217
+ context: {
218
+ ...contexts[index],
219
+ lifecycle: {
220
+ task_checkpoint_owner: 'coordinator',
221
+ completion_tool: 'record_project_agent_task_activity',
222
+ instruction: 'Return the bounded result and evidence to the coordinator. Report only worker activity. Do not begin or checkpoint an inherited parent task context, or write its Agent memory. An independently created host task/session owns its own lifecycle.'
223
+ }
224
+ }
217
225
  }));
218
226
  const contextsReady = workerPlan.every(({ context_status: status }) => status === 'ready');
219
227
  const hostExecutionRequired = route.task.status === 'queued' &&
@@ -248,6 +256,8 @@ export async function coordinateProjectAgentTask(
248
256
  start_only_returned_workers: true,
249
257
  report_worker_start_and_terminal_events: true,
250
258
  report_actual_executor_only_after_real_use: true,
259
+ task_checkpoint_owner: 'coordinator',
260
+ forward_parent_task_context_token: false,
251
261
  release_runtime_lease_in_finally: true
252
262
  },
253
263
  worker_plan: workerPlan,
@@ -47,23 +47,22 @@ export function resolvePersonalProjectPath(projectPath, projects, {
47
47
  pathApi
48
48
  });
49
49
  if (repositoryRoot) {
50
- const repositoryId = pathApi.basename(repositoryRoot);
51
- if (projectIds.has(repositoryId)) {
52
- return matched(repositoryId, 'repository_root');
53
- }
54
50
  const worktreeId = worktreeOriginProjectId(
55
51
  pathApi.join(repositoryRoot, '.git'),
56
52
  { readText, pathApi }
57
53
  );
58
- if (worktreeId && projectIds.has(worktreeId)) {
59
- return matched(worktreeId, 'git_worktree_origin');
54
+ if (worktreeId) {
55
+ // A worktree's folder name is arbitrary; only its original repository
56
+ // identifies the project, even when the folder names another project.
57
+ return resolveProjectId(worktreeId, projectIds, 'git_worktree_origin')
58
+ ?? unresolved('unmatched');
60
59
  }
60
+ const repository = resolveProjectId(pathApi.basename(repositoryRoot), projectIds, 'repository_root');
61
+ if (repository) return repository;
61
62
  }
62
63
 
63
- const directoryId = pathApi.basename(currentPath);
64
- if (projectIds.has(directoryId)) {
65
- return matched(directoryId, 'directory_name');
66
- }
64
+ const directory = resolveProjectId(pathApi.basename(currentPath), projectIds, 'directory_name');
65
+ if (directory) return directory;
67
66
 
68
67
  const childMatches = [...projectIds].filter((projectId) => (
69
68
  CHILD_PROJECT_MARKERS.some((marker) =>
@@ -79,6 +78,14 @@ export function resolvePersonalProjectPath(projectPath, projects, {
79
78
  return unresolved('unmatched');
80
79
  }
81
80
 
81
+ function resolveProjectId(candidate, projectIds, basis) {
82
+ if (projectIds.has(candidate)) return matched(candidate, basis);
83
+ const matches = [...projectIds].filter((id) => id.toLowerCase() === candidate.toLowerCase());
84
+ if (matches.length === 1) return matched(matches[0], `${basis}_case_insensitive`);
85
+ if (matches.length > 1) return unresolved('ambiguous', { candidateCount: matches.length });
86
+ return null;
87
+ }
88
+
82
89
  export function findRepositoryRoot(projectPath, {
83
90
  fileExists = existsSync,
84
91
  pathApi = path
@@ -58,6 +58,23 @@ export class GraphitiProviderClient {
58
58
  this.requestTimeoutMs = requestTimeoutMs;
59
59
  }
60
60
 
61
+ agentVerification(operation, input) {
62
+ if (!['query', 'record'].includes(operation)) throw new TypeError('Unknown verification operation');
63
+ return this.#request(`/v1/agent-verification/${operation}`, { method: 'POST', body: input });
64
+ }
65
+ agentLoan(operation, input) {
66
+ if (!['query', 'request', 'decide'].includes(operation)) throw new TypeError('Unknown loan operation');
67
+ return this.#request(`/v1/agent-loans/${operation}`, { method: 'POST', body: input });
68
+ }
69
+ conversation(operation, input) {
70
+ if (!['query', 'append', 'resume', 'policy', 'boundary'].includes(operation)) throw new TypeError('Unknown conversation operation');
71
+ return this.#request(`/v1/agent-conversations/${operation}`, {
72
+ method: operation === 'policy' ? 'PUT' : 'POST', body: input
73
+ });
74
+ }
75
+ currentTaskContext(input) {
76
+ return this.#request(`/v1/task-context-sessions/current?${new URLSearchParams(input)}`);
77
+ }
61
78
  health() { return this.#request('/health', { authenticated: false }); }
62
79
  listSpaces() { return this.#request('/v1/spaces'); }
63
80
  createSpace(input) { return this.#request('/v1/spaces', { method: 'POST', body: input }); }
@@ -0,0 +1,24 @@
1
+ // Preference selection never authorizes an executor. Existing user routing rules
2
+ // win; otherwise the default applies only to the current task.
3
+ export function verificationPreference({ executors, rules = [], projectId, lockedExecutorIds = null, now = Date.now() }) {
4
+ const eligible = executors.filter(executor => executor.registrationStatus === 'registered'
5
+ && executor.permissionStatus === 'authorized' && executor.preflightStatus === 'passed'
6
+ && executor.workspacePermission && executor.healthStatus !== 'unhealthy'
7
+ && (!executor.healthRequired || executor.healthStatus === 'healthy')
8
+ && executor.availableModels?.some(model => model.available)
9
+ && (!lockedExecutorIds || lockedExecutorIds.includes(executor.executorId)));
10
+ const applicable = rules.filter(rule => rule.status === 'active' && rule.workKind === 'test_validation'
11
+ && (rule.scope === 'global' || rule.scope === 'space' || rule.scope === 'project' && rule.personalProjectId === projectId))
12
+ .sort((a, b) => ({ project: 3, space: 2, global: 1 }[b.scope] - { project: 3, space: 2, global: 1 }[a.scope])
13
+ || (a.priority ?? 100) - (b.priority ?? 100));
14
+ const preference = applicable[0];
15
+ const preferred = (preference?.executorIds ?? []).map(id => eligible.find(item => item.executorId === id)).find(Boolean);
16
+ eligible.sort((a, b) => (a.globalPriority ?? 100) - (b.globalPriority ?? 100) || a.executorId.localeCompare(b.executorId));
17
+ return { status: eligible.length ? 'available' : 'blocked', recommendedExecutorId: preferred?.executorId ?? eligible[0]?.executorId ?? null,
18
+ choiceSource: preferred ? 'user_rule' : 'task_default', scope: preferred ? preference.scope : 'task',
19
+ shouldAsk: !preference && eligible.length > 1,
20
+ expiresAt: new Date(now + 15000).toISOString(), timeoutSeconds: 15,
21
+ eligibleExecutors: eligible.map(item => ({ executorId: item.executorId, name: item.displayName })),
22
+ persistChoiceWith: 'upsert_executor_routing_rule', workKind: 'test_validation',
23
+ guidance: 'If shouldAsk, offer the optional choice once. If unanswered at expiresAt, use recommendedExecutorId for this task only. Ask project vs global scope only for a user-selected preference; silence creates no durable rule. Recheck availability before dispatch. Never grant permissions, authenticate or spend newly authorized money from a timeout.' };
24
+ }
@@ -1,4 +1,5 @@
1
1
  import { readJson, sendJson } from './response.js';
2
+ import { testToolsEnabled } from '../app/test-tools.js';
2
3
  import { handleAgentAttentionRequest } from './agent-attention-api-router.js';
3
4
 
4
5
  export async function handleGraphApiRequest({
@@ -8,6 +9,14 @@ export async function handleGraphApiRequest({
8
9
  app
9
10
  }) {
10
11
  if (await handleAgentAttentionRequest({ request, response, url, app })) return true;
12
+ if (url.pathname === '/api/agent-conversations/query' && request.method === 'POST') {
13
+ sendJson(response, 200, await app.queryAgentConversations({ ...(await readJson(request)), ownerInspection: true }));
14
+ return true;
15
+ }
16
+ if (url.pathname === '/api/agent-conversations/policy' && request.method === 'PUT') {
17
+ sendJson(response, 200, await app.updateAgentConversationPolicy({ ...(await readJson(request)), ownerInspection: true }));
18
+ return true;
19
+ }
11
20
  if (url.pathname === '/api/state' && request.method === 'GET') {
12
21
  sendJson(response, 200, await app.state());
13
22
  return true;
@@ -224,6 +233,14 @@ export async function handleGraphApiRequest({
224
233
  return true;
225
234
  }
226
235
  if (url.pathname === '/api/project-agents/test-cleanup' && request.method === 'POST') {
236
+ if (!testToolsEnabled()) {
237
+ sendJson(response, 404, {
238
+ error: 'Not found',
239
+ code: 'not_found',
240
+ detail: 'Test cleanup requires FULI_ENABLE_TEST_TOOLS=1'
241
+ });
242
+ return true;
243
+ }
227
244
  sendJson(response, 200, await app.cleanupProjectAgentTestRoles({
228
245
  personalSpaceId: url.searchParams.get('personalSpaceId'),
229
246
  testSource: url.searchParams.get('testSource')
@@ -1,3 +1,5 @@
1
+ import { compactTaskContext } from '../conversations/task-context-view.js';
2
+ import { employeeToolCatalogResult } from './employee-tool-catalog.js';
1
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
4
  import {
3
5
  CallToolRequestSchema,
@@ -13,6 +15,7 @@ import { createCommonKnowledgePreviewTokens } from './common-knowledge-preview-t
13
15
  import { auditLifecycleTool } from './lifecycle-audit.js';
14
16
  import { registerFuliContextResources } from './context-resources.js';
15
17
  import { annotationsFor } from './tool-annotations.js';
18
+ import { isTestOnlyToolName, testToolsEnabled } from '../app/test-tools.js';
16
19
  import {
17
20
  errorToolResult,
18
21
  hookAdditionalContextToolResult,
@@ -28,6 +31,9 @@ import {
28
31
  } from './session-id.js';
29
32
 
30
33
  const TOOL_RESULT_LIMIT_BYTES = Object.freeze({
34
+ read_agent_conversation: 600 * 1024,
35
+ list_agent_conversations: 32 * 1024,
36
+ resume_agent_conversation: 24 * 1024,
31
37
  list_project_agent_tasks: 128 * 1024,
32
38
  list_preference_conflicts: 128 * 1024,
33
39
  list_agent_interfaces: 64 * 1024,
@@ -107,7 +113,8 @@ export function createMcpServer(
107
113
  authoritativeSourceApplication,
108
114
  withRuntimeLease,
109
115
  toolNames,
110
- prepareToolInput
116
+ prepareToolInput,
117
+ env
111
118
  );
112
119
  for (const tool of tools.values()) registerTool(server, tool);
113
120
  if (!tools.size) registerEmptyToolList(server);
@@ -130,7 +137,8 @@ function createToolMap(
130
137
  sourceApplication,
131
138
  withRuntimeLease,
132
139
  toolNames,
133
- prepareToolInput
140
+ prepareToolInput,
141
+ env = process.env
134
142
  ) {
135
143
  const commonKnowledgePreviews = createCommonKnowledgePreviewTokens();
136
144
  if (toolNames !== null && !Array.isArray(toolNames)) {
@@ -147,8 +155,10 @@ function createToolMap(
147
155
  );
148
156
  }
149
157
  }
158
+ const includeTestTools = testToolsEnabled(env);
150
159
  return new Map(definitions
151
160
  .filter((definition) => !allowedTools || allowedTools.has(definition.name))
161
+ .filter((definition) => includeTestTools || !isTestOnlyToolName(definition.name))
152
162
  .map((definition) => [definition.name, {
153
163
  definition,
154
164
  schema: jsonSchemaToZod(definition.inputSchema),
@@ -169,7 +179,7 @@ function createToolMap(
169
179
  ),
170
180
  projectActionPreviews,
171
181
  commonKnowledgePreviews,
172
- requestContext
182
+ { ...requestContext, testToolsEnabled: includeTestTools }
173
183
  ),
174
184
  requestContext
175
185
  );
@@ -216,8 +226,11 @@ async function invokeTool(tool, input, requestContext = null) {
216
226
  const value = await tool.invoke(input, requestContext);
217
227
  auditLifecycleTool(tool.definition.name);
218
228
  const limitBytes = TOOL_RESULT_LIMIT_BYTES[tool.definition.name];
229
+ if (tool.definition.name === 'list_employee_tools') {
230
+ return employeeToolCatalogResult(value, { limitBytes });
231
+ }
219
232
  if (tool.definition.name === 'begin_task_context') {
220
- return hookAdditionalContextToolResult(value, {
233
+ return hookAdditionalContextToolResult(compactTaskContext(value), {
221
234
  hookEventName: 'UserPromptSubmit',
222
235
  label: 'Fuli task context. Apply effective_preferences and use taskContextToken for the final checkpoint.',
223
236
  limitBytes,
@@ -227,7 +240,8 @@ async function invokeTool(tool, input, requestContext = null) {
227
240
  const itemLimit = ({ list_agent_interfaces: 200, list_project_agent_tasks: 200,
228
241
  list_preference_conflicts: 1000, list_external_knowledge_bindings: 200,
229
242
  get_collaboration_preferences: 1000 })[tool.definition.name];
230
- return successToolResult(value, { limitBytes, itemLimit });
243
+ return successToolResult(tool.definition.name === 'get_collaboration_preferences'
244
+ ? compactTaskContext(value) : value, { limitBytes, itemLimit });
231
245
  } catch (error) {
232
246
  return errorToolResult(error);
233
247
  }
@@ -0,0 +1,22 @@
1
+ import { ApplicationError } from '../app/application-error.js';
2
+
3
+ // Schemas are executable contracts. Summary truncation can change their types,
4
+ // required fields or enum values, so return a complete bounded catalog or fail.
5
+ export function employeeToolCatalogResult(value, { limitBytes = 64 * 1024 } = {}) {
6
+ const catalog = pick(value, ['personalSpaceId', 'agentId', 'templateId', 'name',
7
+ 'role', 'runtimeStatus', 'basePath', 'workbenchUrl', 'agentCardUrl']);
8
+ if (value.project) catalog.project = pick(value.project, ['id', 'name', 'description']);
9
+ catalog.tools = (value.tools ?? []).map(tool => pick(tool,
10
+ ['name', 'title', 'description', 'permission', 'inputSchema']));
11
+ const json = JSON.stringify(catalog);
12
+ if (Buffer.byteLength(json, 'utf8') > limitBytes) {
13
+ throw new ApplicationError('tool_catalog_too_large',
14
+ 'Employee tool catalog is too large. Use includeSchemas=false to list names, then toolName to read one complete schema.');
15
+ }
16
+ return { content: [{ type: 'text', text: json }], structuredContent: JSON.parse(json) };
17
+ }
18
+
19
+ function pick(value, keys) {
20
+ return Object.fromEntries(keys.filter(key => value[key] !== undefined)
21
+ .map(key => [key, value[key]]));
22
+ }
@@ -3,6 +3,10 @@ import { randomUUID } from 'node:crypto';
3
3
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
4
  const WORKFLOW_OBSERVATION_TOOL = 'record_workflow_transition_observation';
5
5
  const PROJECT_AGENT_SOURCE_TOOLS = new Set([
6
+ 'get_agent_quality_gate', 'record_agent_verification',
7
+ 'plan_agent_collaboration', 'request_agent_loan', 'decide_agent_loan', 'list_agent_loans',
8
+ 'list_agent_conversations', 'read_agent_conversation', 'resume_agent_conversation',
9
+ 'get_agent_conversation_policy', 'update_agent_conversation_policy',
6
10
  'call_employee_tool',
7
11
  'begin_task_context',
8
12
  'verify_task_checkpoint',
@@ -1,4 +1,7 @@
1
1
  const READ_TOOLS = new Set([
2
+ 'get_agent_quality_gate',
3
+ 'plan_agent_collaboration', 'list_agent_loans',
4
+ 'list_agent_conversations', 'read_agent_conversation', 'get_agent_conversation_policy',
2
5
  'list_agent_attention',
3
6
  'list_project_agent_tasks',
4
7
  'list_preference_conflicts',
@@ -46,6 +49,9 @@ const READ_TOOLS = new Set([
46
49
  ]);
47
50
 
48
51
  const WRITE_TOOLS = new Set([
52
+ 'record_agent_verification',
53
+ 'request_agent_loan', 'decide_agent_loan',
54
+ 'resume_agent_conversation', 'update_agent_conversation_policy',
49
55
  'request_agent_attention',
50
56
  'cancel_agent_attention',
51
57
  'update_capture_policy',
@@ -128,7 +134,8 @@ const DESTRUCTIVE_TOOLS = new Set([
128
134
  'review_project_proposal',
129
135
  'review_project_relation',
130
136
  'review_personal_draft',
131
- 'unsubscribe_public_project'
137
+ 'unsubscribe_public_project',
138
+ 'cleanup_test_project_agents'
132
139
  ]);
133
140
 
134
141
  export function annotationsFor(name) {
@@ -178,7 +178,9 @@ function projectString(value, budget) {
178
178
  function projectArray(value, budget, depth) {
179
179
  const projected = [];
180
180
  let used = 2;
181
- for (const item of value.slice(0, RESULT_ITEM_LIMIT)) {
181
+ // sanitize already applied the caller's item limit. A second default limit
182
+ // here silently drops catalog entries and preferences when bytes are capped.
183
+ for (const item of value) {
182
184
  const separatorBytes = projected.length ? 1 : 0;
183
185
  const child = projectValue(item, budget - used - separatorBytes, depth + 1);
184
186
  if (child === undefined) continue;
@@ -39,6 +39,7 @@ export function inspectAgentInstallations(agents, context, {
39
39
  );
40
40
  const claudeLifecycleCurrent = agent.id !== 'claude-code' ||
41
41
  hasCurrentClaudeCodeHooks(readJson(agent.settingsPath), {
42
+ ...context,
42
43
  hookTimeoutSec: context.hookTimeoutSec ?? 30
43
44
  });
44
45
  const codexConfig = agent.id === 'codex' && agent.hooksPath
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { isDeepStrictEqual } from 'node:util';
3
+ import { claudeLifecycleCommand, isClaudeLifecycleCommand } from '../agents/claude-code/lifecycle-hooks.js';
3
4
 
4
5
  import {
5
6
  readJsonFile,
@@ -48,9 +49,7 @@ export function connectClaudeCode(agent, context, {
48
49
  }
49
50
  }
50
51
  };
51
- const nextSettings = withManagedHooks(settings, {
52
- hookTimeoutSec: context.hookTimeoutSec
53
- });
52
+ const nextSettings = withManagedHooks(settings, context);
54
53
  const registrationChanged = !sameJson(current, next);
55
54
  const hooksChanged = !sameJson(settings, nextSettings);
56
55
  if (registrationChanged) writeConfig(agent.configPath, next);
@@ -104,13 +103,13 @@ export function disconnectClaudeCode(agent, {
104
103
  };
105
104
  }
106
105
 
107
- export function hasCurrentClaudeCodeHooks(settings, { hookTimeoutSec = 30 } = {}) {
106
+ export function hasCurrentClaudeCodeHooks(settings, context = {}) {
108
107
  try {
109
108
  assertObject(settings, 'Claude Code settings');
110
109
  assertHookSettings(settings.hooks ?? {});
111
110
  assertPermissionSettings(settings.permissions ?? {});
112
111
  return sameJson(
113
- withManagedHooks(settings, { hookTimeoutSec }),
112
+ withManagedHooks(settings, context),
114
113
  settings
115
114
  );
116
115
  } catch {
@@ -118,23 +117,20 @@ export function hasCurrentClaudeCodeHooks(settings, { hookTimeoutSec = 30 } = {}
118
117
  }
119
118
  }
120
119
 
121
- function withManagedHooks(settings, { hookTimeoutSec = 30 } = {}) {
120
+ function withManagedHooks(settings, context) {
121
+ const { hookTimeoutSec = 30 } = context;
122
122
  const normalizedHookTimeoutSec = positiveHookTimeout(hookTimeoutSec);
123
123
  const clean = withoutManagedSettings(settings);
124
124
  const hooks = { ...(clean.hooks ?? {}) };
125
125
  hooks.UserPromptSubmit = [
126
126
  ...(hooks.UserPromptSubmit ?? []),
127
- managedHookGroup('begin_task_context', {
128
- sessionId: '${session_id}',
129
- projectPath: '${cwd}',
130
- taskPrompt: '${prompt}'
131
- }, 'Loading Fuli task context', normalizedHookTimeoutSec)
127
+ managedHookGroup(context, 'UserPromptSubmit', 'Loading Fuli task context', normalizedHookTimeoutSec)
132
128
  ];
133
129
  hooks.Stop = [
134
130
  ...(hooks.Stop ?? []),
135
131
  managedHookGroup(
136
- 'verify_task_checkpoint',
137
- { sessionId: '${session_id}' },
132
+ context,
133
+ 'Stop',
138
134
  'Checking Fuli task checkpoint',
139
135
  normalizedHookTimeoutSec
140
136
  )
@@ -186,13 +182,11 @@ function withoutManagedHooks(settings) {
186
182
  return { ...settings, hooks };
187
183
  }
188
184
 
189
- function managedHookGroup(tool, input, statusMessage, timeout) {
185
+ function managedHookGroup(context, event, statusMessage, timeout) {
190
186
  return {
191
187
  hooks: [{
192
- type: 'mcp_tool',
193
- server: FULI_SERVER,
194
- tool,
195
- input,
188
+ type: 'command',
189
+ command: claudeLifecycleCommand(context, event, timeout),
196
190
  timeout,
197
191
  statusMessage
198
192
  }]
@@ -207,7 +201,7 @@ function positiveHookTimeout(value) {
207
201
  }
208
202
 
209
203
  function isManagedHook(hook, tool = null) {
210
- return hook?.type === 'mcp_tool'
204
+ return isClaudeLifecycleCommand(hook) || hook?.type === 'mcp_tool'
211
205
  && hook.server === FULI_SERVER
212
206
  && MANAGED_HOOK_TOOLS.has(hook.tool)
213
207
  && (tool === null || hook.tool === tool);
@@ -1 +0,0 @@
1
- import{L as e,N as t,T as n,V as r,Z as i,_ as a,a as o,at as s,b as c,nt as l,v as u,y as d}from"./i18n-CVFADb_M.js";import{r as f}from"./console-BAIVNC-N.js";import{t as p}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{i as m,n as h}from"./client-Bl4oiW3E.js";function g(e){let t=/^employee\.([a-z][a-z0-9-]{0,63})$/.exec(e.agentId)?.[1];return t&&e.profile.capabilities.includes(`fuli.employee:${t}`)?t:null}var _=i([]),v=i(!1),y=i(``),b=``,x=null,S=0;async function C(e){if(!e)return[];let t=await h(`/api/employee-templates?${new URLSearchParams({personalSpaceId:e})}`);return Array.isArray(t.templates)?t.templates:[]}function w(e){if(e===b&&x)return x;let t=++S;return e!==b&&(_.value=[]),b=e,y.value=``,e?(v.value=!0,x=C(e).then(e=>{t===S&&(_.value=e)}).catch(e=>{t===S&&(_.value=[],y.value=T(e))}).finally(()=>{t===S&&(v.value=!1,x=null)}),x):(_.value=[],v.value=!1,Promise.resolve())}function T(e){let t=e instanceof Error?e.message:``;try{let e=JSON.parse(t);return e.code&&[`runtime_unavailable`,`assignment_required`,`project_not_found`,`identity_conflict`,`reactivation_required`,`assignment_scope_conflict`,`assignment_update_incomplete`].includes(e.code)?o(`employees.errors.${e.code}`):e.error||o(`employees.loadError`)}catch{return t||o(`employees.loadError`)}}var E={jefa:`/assets/jefa-avatar-Dew42td5.webp`,bole:`/assets/bole-avatar-CVIr-bU_.webp`};function D(e){return E[e]}var O=f(`agent-attention`,()=>{let e=i(``),t=i({}),n=i(0),r=i([]),a=i(0),o=i(``),s=i(!1),c=i(!1),l=i(``),u=0;async function d(){let i=++u;if(e.value){c.value=!0;try{let c=new URLSearchParams({personalSpaceId:e.value,status:`open`}),d=await h(`/api/agent-attention?${c}&limit=1`);if(i!==u)return;if(t.value=d.counts,n.value=d.total,s.value){o.value&&c.set(`agentId`,o.value);let e=await h(`/api/agent-attention?${c}&limit=100`);if(i!==u)return;r.value=e.items,a.value=e.total}l.value=``}catch(e){i===u&&(l.value=e instanceof Error?e.message:String(e))}finally{i===u&&(c.value=!1)}}}function f(i){e.value!==i&&(u++,e.value=i,t.value={},r.value=[],n.value=0,a.value=0,l.value=``,s.value=!1,c.value=!1,o.value=``,i&&d())}function p(e=``){o.value=e,r.value=[],a.value=0,s.value=!0,d()}async function g(){if(c.value)return;let t=++u;c.value=!0;try{let n=new URLSearchParams({personalSpaceId:e.value,status:`open`,limit:`100`,offset:String(r.value.length)});o.value&&n.set(`agentId`,o.value);let i=await h(`/api/agent-attention?${n}`);if(t!==u)return;let s=new Set(r.value.map(e=>e.requestId));r.value.push(...i.items.filter(e=>!s.has(e.requestId))),a.value=i.total}catch(e){t===u&&(l.value=String(e))}finally{t===u&&(c.value=!1)}}async function _(t,n){let r=e.value;if(!r||t.personalSpaceId!==r)throw Error(`This request belongs to a different space. Reopen it before replying.`);await m(`/api/agent-attention/respond`,{personalSpaceId:r,personalProjectId:t.personalProjectId,requestId:t.requestId,expectedRevision:t.revision,response:n}),e.value===r&&await d()}return{spaceId:e,counts:t,total:n,items:r,filteredTotal:a,agentId:o,open:s,loading:c,error:l,setSpace:f,show:p,refresh:d,more:g,respond:_}});function k(e){let t=new URLSearchParams({agent:e.agentId,project:e.personalProjectId});return e.taskId&&t.set(`task`,e.taskId),`/project-agents?${t}${e.taskId?`#task-${encodeURIComponent(e.taskId)}`:``}`}var A={"aria-hidden":`true`},j=p(n({__name:`AgentHand`,props:{agentId:{},passive:{type:Boolean}},setup(n){let i=n,f=O(),p=a(()=>i.agentId?f.counts[i.agentId]??0:f.total);function m(e){i.passive||(e.preventDefault(),e.stopPropagation(),f.show(i.agentId))}return(i,a)=>p.value?(t(),d(e(n.passive?`span`:`button`),{key:0,type:n.passive?void 0:`button`,class:`agent-hand`,"aria-label":l(o)(`attention.count`,{count:p.value}),title:l(o)(`attention.count`,{count:p.value}),onClick:m},{default:r(()=>[a[0]||=u(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.7`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[u(`path`,{d:`M8 13V5a1.5 1.5 0 0 1 3 0v6-8a1.5 1.5 0 0 1 3 0v8-6a1.5 1.5 0 0 1 3 0v7-3a1.5 1.5 0 0 1 3 0v6c0 4-2 7-6 7h-1c-3 0-4-1-6-4l-3-4a1.6 1.6 0 0 1 2.5-2z`})],-1),u(`span`,A,s(p.value),1)]),_:1},8,[`type`,`aria-label`,`title`])):c(``,!0)}}),[[`__scopeId`,`data-v-0ed1321f`]]);export{y as a,g as c,w as d,D as i,_ as l,k as n,v as o,O as r,T as s,j as t,C as u};
@@ -1 +0,0 @@
1
- import{C as e,F as t,N as n,T as r,V as i,Z as a,_ as o,a as s,at as c,b as l,h as u,m as ee,nt as d,rt as te,v as f,w as p,x as m,y as h,z as g}from"./i18n-CVFADb_M.js";import{t as _}from"./GrowthLoading-WXCj8b0r.js";import{t as v}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as y}from"./vue-router-C8KuEZ6a.js";import{i as ne}from"./client-Bl4oiW3E.js";import{d as re,i as b,l as ie,s as ae,u as oe}from"./AgentHand-BfLYPGjg.js";import{t as se}from"./useModalDialog-Ddv2XJQi.js";import{n as ce}from"./paths-CDREouhi.js";import{t as x}from"./SearchableSelect-214TvfYg.js";import{t as le}from"./ProjectScopePicker-Cu5PdxF6.js";var ue={class:`employee-recruit-heading`},de={id:`employee-recruit-title`},fe=[`disabled`],pe={key:1,role:`alert`,class:`employee-message`},me={key:2},he={class:`employee-recruit-body`},ge={key:0,class:`employee-picker-field`},_e={class:`employee-profile`},ve={class:`employee-avatar`,"aria-hidden":`true`},ye=[`src`],be={class:`employee-specialties`},xe=[`aria-label`],Se=[`data-scope`,`aria-checked`,`tabindex`,`disabled`,`onClick`],Ce={key:3,class:`employee-scope-rule`,role:`status`},we={key:4,class:`employee-muted`},Te={key:5,class:`employee-muted`},Ee={key:6,class:`employee-muted`},De=[`aria-label`],Oe={class:`employee-title-controls`},ke={class:`employee-picker-field`},Ae={class:`employee-picker-field`},je={class:`employee-title-preview`},Me={class:`employee-muted`},Ne=[`aria-label`],Pe={key:9,class:`employee-error`,role:`alert`},Fe=[`disabled`],Ie={key:11,class:`employee-success`,role:`status`},Le={class:`employee-recruit-actions`},Re=[`disabled`],ze=[`disabled`],S=v(r({__name:`EmployeeRecruitDialog`,props:{open:{type:Boolean},personalSpaceId:{},projects:{},defaultProjectId:{},defaultProjectIds:{},templateId:{}},emits:[`close`,`recruited`],setup(r,{emit:v}){let S=r,C=v,w=a([]),T=a([]),E=a(`selected`),D=a([]),O=a(`auto`),k=a(`emoji`),A=a(``),j=a(``),M=a(!1),N=a(!1),P=a(``),F=a(!1),I=a(``),L=a(null),R=a([]),z=a(``),{dialogRef:Be,initialFocusRef:Ve,onCancel:He,onKeydown:Ue}=se(()=>S.open,Q),B=o(()=>R.value),V=o(()=>S.templateId?B.value.find(e=>e.id===S.templateId):B.value.find(e=>e.id===P.value)??B.value[0]),We=o(()=>V.value?.agentId?s(`employees.manageProjectsTitle`,{name:V.value.name}):s(`employees.recruit`)),H=o(()=>S.projects.filter(e=>e.profile.lifecycle!==`archived`).map(e=>({id:e.project_id,name:e.profile.name}))),U=o(()=>V.value?.management!==void 0),W=o({get:()=>E.value===`all`?H.value.filter(e=>!D.value.includes(e.id)).map(e=>e.id):w.value,set:e=>{if(E.value===`all`){let t=new Set(H.value.map(e=>e.id));D.value=[...D.value.filter(e=>!t.has(e)),...H.value.filter(t=>!e.includes(t.id)).map(e=>e.id)]}else w.value=e}}),G=o(()=>({mode:E.value,projectIds:E.value===`selected`?[...w.value].sort():[],excludedProjectIds:E.value===`all`?[...D.value].sort():[],titleMode:O.value,titleStyle:k.value})),Ge=o(()=>[`auto`,`suggest`,`off`].map(e=>({value:e,label:s(`employees.titles.${e}`)}))),Ke=o(()=>[`emoji`,`text`].map(e=>({value:e,label:s(`employees.titles.${e}`)}))),K=o(()=>new Set(T.value)),qe=o(()=>[...K.value].filter(e=>!W.value.includes(e))),q=o(()=>!!(V.value?.agentStatus&&V.value.agentStatus!==`active`)),J=o(()=>V.value?.agentStatus===`active`&&(U.value?JSON.stringify(G.value)===A.value&&(E.value===`all`||w.value.length===K.value.size&&w.value.every(e=>K.value.has(e))):w.value.length===K.value.size&&w.value.every(e=>K.value.has(e)))),Je=o(()=>F.value?s(`employees.recruiting`):q.value?s(`employees.rehire`,{name:V.value?.name??``}):J.value||V.value?.agentId?s(`employees.assign`):s(`employees.recruitName`,{name:V.value?.name??``})),Ye=o(()=>V.value&&W.value.length?`/employees/${encodeURIComponent(V.value.id)}?project=${encodeURIComponent(W.value[0])}`:``),Y=0;g([()=>S.open,()=>S.personalSpaceId,()=>S.templateId],([e])=>{Y+=1,e&&Z(!0)},{immediate:!0}),g(P,()=>{S.open&&!M.value&&X(!0)}),g(G,()=>{I.value=N.value?I.value:``,L.value=null},{flush:`sync`});function X(e=!1){let t=new Set(H.value.map(e=>e.id));T.value=[...new Set((V.value?.assignments??[]).filter(e=>e.status===`active`&&t.has(e.personalProjectId)).map(e=>e.personalProjectId))];let n=e&&!V.value?.agentId?S.defaultProjectIds??(S.defaultProjectId?[S.defaultProjectId]:[]):[];w.value=[...new Set([...T.value,...n.filter(e=>t.has(e))])];let r=V.value?.management;E.value=r?.mode??`selected`,D.value=[...r?.excludedProjectIds??[]],O.value=r?.titleMode??`auto`,k.value=r?.titleStyle??`emoji`,r?.mode===`selected`&&V.value?.agentId&&(w.value=r.projectIds.filter(e=>t.has(e))),r?.mode===`all`&&V.value?.agentId&&(T.value=[...W.value]),A.value=JSON.stringify(G.value),j.value=V.value?.assignmentsVersion??``,N.value=!1,I.value=``,L.value=null}async function Z(e=!1){let t=++Y;M.value=!0,R.value=[],z.value=``;try{let n=await oe(S.personalSpaceId);if(t!==Y||!S.open)return;R.value=n,X(e)}catch(e){t===Y&&S.open&&(R.value=[],z.value=ae(e))}finally{t===Y&&(M.value=!1)}}function Q(){F.value||C(`close`)}async function Xe(){if(!V.value||V.value.identityConflict||!S.personalSpaceId||F.value||M.value||N.value||!j.value||J.value)return;F.value=!0,I.value=``;let e=V.value.id;try{let t=await ne(`/api/employee-templates/${encodeURIComponent(e)}/recruit`,{personalSpaceId:S.personalSpaceId,...U.value?{management:G.value}:{personalProjectIds:[...w.value]},replaceAssignments:!0,expectedAssignmentsVersion:j.value,...q.value?{reactivate:!0}:{}});await re(S.personalSpaceId),R.value=[...ie.value],X(),L.value=t,C(`recruited`,t)}catch(e){I.value=ae(e);try{let t=JSON.parse(e instanceof Error?e.message:``);N.value=t.code===`assignment_scope_conflict`||t.code===`assignment_update_incomplete`}catch{}}finally{F.value=!1}}function $(e){e===`selected`&&(w.value=[...W.value]),E.value=e}function Ze(e){if(!(F.value||M.value||N.value)&&[`ArrowLeft`,`ArrowRight`,`Home`,`End`].includes(e.key)){e.preventDefault();let t=e.key===`Home`?`all`:e.key===`End`||E.value===`all`?`selected`:`all`;$(t),e.currentTarget.querySelector(`[data-scope="${t}"]`)?.focus()}}return(a,o)=>r.open?(n(),m(`dialog`,{key:0,ref_key:`dialogRef`,ref:Be,class:`employee-recruit-dialog`,"aria-modal":`true`,"aria-labelledby":`employee-recruit-title`,onCancel:o[6]||=(...e)=>d(He)&&d(He)(...e),onKeydown:o[7]||=(...e)=>d(Ue)&&d(Ue)(...e)},[f(`header`,ue,[f(`h2`,de,c(We.value),1),f(`button`,{ref_key:`initialFocusRef`,ref:Ve,type:`button`,class:`quiet-button`,disabled:F.value,onClick:Q},c(d(s)(`employees.close`)),9,fe)]),M.value&&!B.value.length?(n(),h(_,{key:0,variant:`compact`,label:d(s)(`employees.loading`)},null,8,[`label`])):z.value?(n(),m(`div`,pe,[f(`p`,null,c(z.value),1),f(`button`,{class:`quiet-button`,type:`button`,onClick:o[0]||=e=>Z(!0)},c(d(s)(`employees.retry`)),1)])):V.value?(n(),m(`form`,{key:3,onSubmit:ee(Xe,[`prevent`])},[f(`div`,he,[B.value.length>1&&!S.templateId?(n(),m(`div`,ge,[f(`span`,null,c(d(s)(`employees.choose`)),1),p(x,{modelValue:P.value,"onUpdate:modelValue":o[1]||=e=>P.value=e,"control-id":`employee-template`,label:d(s)(`employees.choose`),options:B.value.map(e=>({value:e.id,label:e.name,meta:e.role})),disabled:F.value||M.value},null,8,[`modelValue`,`label`,`options`,`disabled`])])):l(``,!0),f(`div`,_e,[f(`span`,ve,[d(b)(V.value.id)?(n(),m(`img`,{key:0,src:d(b)(V.value.id),alt:``},null,8,ye)):(n(),m(u,{key:1},[e(c(V.value.name.slice(0,1)),1)],64))]),f(`div`,null,[f(`h3`,null,[e(c(V.value.name)+` `,1),f(`span`,null,c(V.value.role),1)]),f(`p`,null,c(V.value.description),1)])]),f(`p`,be,c(V.value.capabilities.join(` · `)),1),M.value?(n(),h(_,{key:1,variant:`compact`,label:d(s)(`employees.loadingScope`)},null,8,[`label`])):l(``,!0),U.value?(n(),m(`div`,{key:2,class:`employee-scope-mode`,role:`radiogroup`,"aria-label":d(s)(`employees.scope.rule`),onKeydown:Ze},[(n(),m(u,null,t([`all`,`selected`],e=>f(`button`,{key:e,type:`button`,role:`radio`,"data-scope":e,"aria-checked":E.value===e,tabindex:E.value===e?0:-1,disabled:F.value||M.value||N.value,onClick:t=>$(e)},c(d(s)(`employees.scope.${e===`all`?`continuousAll`:`onlySelected`}`)),9,Se)),64))],40,xe)):l(``,!0),U.value?(n(),m(`p`,Ce,c(E.value===`all`?d(s)(`employees.scope.allHint`,{name:V.value.name}):d(s)(`employees.scope.selectedHint`)),1)):l(``,!0),p(le,{modelValue:W.value,"onUpdate:modelValue":o[2]||=e=>W.value=e,projects:H.value,inline:``,hint:E.value===`all`?d(s)(`employees.scope.excludeHint`):void 0,disabled:F.value||M.value||N.value},null,8,[`modelValue`,`projects`,`hint`,`disabled`]),E.value===`all`&&D.value.length?(n(),m(`p`,we,c(d(s)(`employees.scope.excludedCount`,{count:D.value.length})),1)):l(``,!0),H.value.length?l(``,!0):(n(),m(`p`,Te,[e(c(d(s)(`employees.noProjects`))+` `,1),p(d(y),{to:d(ce)(r.personalSpaceId,`directory`),onClick:Q},{default:i(()=>[e(c(d(s)(`employees.createProject`)),1)]),_:1},8,[`to`])])),qe.value.length&&!L.value?(n(),m(`p`,Ee,c(d(s)(`employees.scopeRemoved`,{count:qe.value.length})),1)):l(``,!0),U.value&&V.value.permissions.includes(`session.title`)?(n(),m(`section`,{key:7,class:`employee-title-settings`,"aria-label":d(s)(`employees.titles.heading`)},[f(`h3`,null,c(d(s)(`employees.titles.heading`)),1),f(`div`,Oe,[f(`div`,ke,[f(`span`,null,c(d(s)(`employees.titles.mode`)),1),p(x,{modelValue:O.value,"onUpdate:modelValue":o[3]||=e=>O.value=e,"control-id":`employee-title-mode`,label:d(s)(`employees.titles.mode`),options:Ge.value,disabled:F.value||M.value||N.value},null,8,[`modelValue`,`label`,`options`,`disabled`])]),f(`div`,Ae,[f(`span`,null,c(d(s)(`employees.titles.style`)),1),p(x,{modelValue:k.value,"onUpdate:modelValue":o[4]||=e=>k.value=e,"control-id":`employee-title-style`,label:d(s)(`employees.titles.style`),options:Ke.value,disabled:F.value||M.value||N.value||O.value===`off`},null,8,[`modelValue`,`label`,`options`,`disabled`])])]),f(`p`,je,[e(c(d(s)(`employees.titles.example`))+` `,1),f(`span`,null,`【P1|`+c(k.value===`emoji`?`🔧 `:``)+`FIX|`+c(d(s)(`employees.titles.exampleTask`))+`】`,1)]),f(`p`,Me,c(d(s)(`employees.titles.boundary`)),1)],8,De)):l(``,!0),f(`section`,{class:`employee-permissions`,"aria-label":d(s)(`employees.permissions`)},[f(`strong`,null,c(d(s)(`employees.permissions`)),1),f(`ul`,null,[(n(!0),m(u,null,t(V.value.permissions,e=>(n(),m(`li`,{key:e},c(e===`board.read`?d(s)(`employees.boardRead`):e===`board.write`?d(s)(`employees.boardWrite`):e===`session.title`?d(s)(`employees.titles.heading`):e),1))),128))]),f(`p`,null,c(d(s)(`employees.noExecutor`)),1)],8,Ne),V.value.runtime?(n(),m(`p`,{key:8,class:te([`employee-runtime`,{"is-warning":V.value.runtimeStatus!==`ready`}])},c(V.value.runtimeStatus===`ready`?d(s)(`employees.hostReady`):d(s)(`employees.installRequired`)),3)):l(``,!0),I.value||V.value.identityConflict?(n(),m(`p`,Pe,c(I.value||d(s)(`employees.errors.identity_conflict`)),1)):l(``,!0),N.value?(n(),m(`button`,{key:10,type:`button`,class:`quiet-button`,disabled:M.value,onClick:o[5]||=e=>Z()},c(d(s)(`employees.reloadScope`)),9,Fe)):l(``,!0),L.value?(n(),m(`div`,Ie,[f(`strong`,null,c(d(s)(`employees.scopeSaved`)),1),f(`p`,null,c(E.value===`all`?d(s)(`employees.scope.successAll`,{count:W.value.length}):W.value.length?d(s)(`employees.successMultiple`,{count:W.value.length}):d(s)(`employees.successUnassigned`)),1)])):l(``,!0)]),f(`footer`,Le,[f(`button`,{class:`quiet-button`,type:`button`,disabled:F.value,onClick:Q},c(d(s)(`employees.cancel`)),9,Re),J.value&&Ye.value&&!F.value&&!M.value&&!N.value&&V.value.runtimeStatus===`ready`?(n(),h(d(y),{key:0,class:`employee-primary`,to:Ye.value,onClick:Q},{default:i(()=>[e(c(d(s)(`employees.open`)),1)]),_:1},8,[`to`])):(n(),m(`button`,{key:1,class:`employee-primary`,type:`submit`,disabled:F.value||M.value||N.value||!j.value||J.value||V.value.identityConflict||!r.personalSpaceId},[F.value?(n(),h(_,{key:0,variant:`inline`,label:Je.value},null,8,[`label`])):(n(),m(u,{key:1},[e(c(Je.value),1)],64))],8,ze))])],32)):(n(),m(`p`,me,c(d(s)(`employees.noTemplates`)),1))],544)):l(``,!0)}}),[[`__scopeId`,`data-v-53eaa264`]]);export{S as t};