fuli-context 0.8.2 → 0.9.0-beta.1

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 (127) hide show
  1. package/README.md +115 -12
  2. package/README.zh-CN.md +99 -12
  3. package/dist/web/assets/{AboutPage-fBq5VFoQ.js → AboutPage-u2Bv6cO-.js} +1 -1
  4. package/dist/web/assets/{AgentHand-CMZk5GjY.js → AgentHand-CsMcNXUm.js} +1 -1
  5. package/dist/web/assets/{AgentName-DRZHAIB2.js → AgentName-BboCA-Fd.js} +1 -1
  6. package/dist/web/assets/{AgentProfilePage-BPQEfMhB.js → AgentProfilePage-Dyux6qE3.js} +1 -1
  7. package/dist/web/assets/AgentsDirectoryPage-BEJdvmX7.js +1 -0
  8. package/dist/web/assets/{ConnectionsPage-COb56qnk.js → ConnectionsPage-Dw_hK_EM.js} +1 -1
  9. package/dist/web/assets/{EmployeeRecruitDialog-BLTpjCcK.js → EmployeeRecruitDialog-DP_ktv3w.js} +1 -1
  10. package/dist/web/assets/{EmployeeWorkbenchPage-HUd-gne2.js → EmployeeWorkbenchPage-CAt8lVnr.js} +1 -1
  11. package/dist/web/assets/{GrowthLoading-Bu_kH-I8.js → GrowthLoading-DIwqGFSc.js} +1 -1
  12. package/dist/web/assets/{KnowledgeEditDialog-BTwXXRCD.js → KnowledgeEditDialog-Degd9AGD.js} +1 -1
  13. package/dist/web/assets/{KnowledgeInspector-Ct5hl2--.js → KnowledgeInspector-yms1ysZ4.js} +1 -1
  14. package/dist/web/assets/{KnowledgeOrganizerPage-DMC8Hh43.js → KnowledgeOrganizerPage-C64wNn8a.js} +1 -1
  15. package/dist/web/assets/KnowledgePage-BTFEXzgi.js +1 -0
  16. package/dist/web/assets/{KnowledgeWorkspace-DmHIc0jb.js → KnowledgeWorkspace-BsF1X4Aq.js} +1 -1
  17. package/dist/web/assets/{NotFoundPage-C0yOR9T9.js → NotFoundPage-DfaNTiD1.js} +1 -1
  18. package/dist/web/assets/{OverviewPage-BreX_kb4.js → OverviewPage-VXMfcWFI.js} +1 -1
  19. package/dist/web/assets/{PersonalProfilePage-CX1a6_E3.js → PersonalProfilePage-C1LBhcYu.js} +1 -1
  20. package/dist/web/assets/PersonalProjectsPage-B5lV4ZJW.js +1 -0
  21. package/dist/web/assets/ProjectAgentFirstTask-6afJ8HUe.css +1 -0
  22. package/dist/web/assets/ProjectAgentFirstTask-DF-IvEqp.js +1 -0
  23. package/dist/web/assets/ProjectAgentsPage-CgL92gng.js +13 -0
  24. package/dist/web/assets/ProjectAgentsPage-CoJ4qQlA.css +1 -0
  25. package/dist/web/assets/{ProjectScopePicker-DVHfXX1f.js → ProjectScopePicker-CZUMdyYz.js} +1 -1
  26. package/dist/web/assets/{PublicProjectsPage-CtctKml1.js → PublicProjectsPage-e4CpxHk9.js} +1 -1
  27. package/dist/web/assets/{ReviewPage-DUqaK-do.js → ReviewPage-DugEaFvI.js} +1 -1
  28. package/dist/web/assets/RoundtablesPage-642YtZBC.js +2 -0
  29. package/dist/web/assets/RoundtablesPage-DCbIBnBq.css +1 -0
  30. package/dist/web/assets/{SearchableSelect-BZtyYmZN.js → SearchableSelect-Do6X3fXi.js} +1 -1
  31. package/dist/web/assets/{SettingsPage-CB4ymLr8.js → SettingsPage-wqYxDPyu.js} +1 -1
  32. package/dist/web/assets/{SharedJefaReportPage-CbFtrDCY.js → SharedJefaReportPage-CNQDezJr.js} +1 -1
  33. package/dist/web/assets/{WritingTastePage-B7lSzs2z.js → WritingTastePage-CAUvbDlL.js} +1 -1
  34. package/dist/web/assets/{agent-profile.css_vue_type_style_index_0_src_true_lang-C22UdfU0.js → agent-profile.css_vue_type_style_index_0_src_true_lang-DHOcWSe_.js} +1 -1
  35. package/dist/web/assets/{catalog-DU4Kfd-N.js → catalog-JmKyJnKL.js} +1 -1
  36. package/dist/web/assets/{client-CEHkJJ-5.js → client-DTQ_tUr2.js} +1 -1
  37. package/dist/web/assets/{console--CZxc3PW.js → console-CrhIK67Y.js} +1 -1
  38. package/dist/web/assets/{conversation-launcher-settings-Co1RlTFH.js → conversation-launcher-settings-fUWCt3XG.js} +1 -1
  39. package/dist/web/assets/copy-CX6pyzAG.js +1 -0
  40. package/dist/web/assets/i18n-CIIxzhPZ.js +10 -0
  41. package/dist/web/assets/{identity-CsowvtIm.js → identity-CdFITIOu.js} +1 -1
  42. package/dist/web/assets/{index-CWMqDq8g.css → index-CYZi7E1e.css} +1 -1
  43. package/dist/web/assets/index-DMoxVzDU.js +2 -0
  44. package/dist/web/assets/{model-CGpllrds.js → model-CJsrDUNb.js} +1 -1
  45. package/dist/web/assets/{personal-profile-graph-BNTG4PSY.js → personal-profile-graph-CQLOait6.js} +1 -1
  46. package/dist/web/assets/{shared-report-BFUJqqQe.js → shared-report-Cll2Yj8W.js} +1 -1
  47. package/dist/web/assets/{task-evidence-BUu7yYIn.js → task-evidence-DTiPCs7r.js} +1 -1
  48. package/dist/web/assets/{useApi-CROJJdhE-raNlHRDl.js → useApi-CROJJdhE-fcdVGInY.js} +1 -1
  49. package/dist/web/assets/{useModalDialog-CroGk2Cs.js → useModalDialog-B-45idX9.js} +1 -1
  50. package/dist/web/assets/{vue-router-JFYBLXB1.js → vue-router-BUX6njIv.js} +1 -1
  51. package/dist/web/index.html +12 -11
  52. package/docs/agent-conversations-and-collaboration.md +18 -1
  53. package/docs/employee-agents.md +20 -1
  54. package/docs/roundtable-beta-testing.md +102 -0
  55. package/docs/roundtable-beta-validation.md +125 -0
  56. package/docs/roundtable-research.md +142 -0
  57. package/graph-provider/fuli_graph/app.py +5 -0
  58. package/graph-provider/fuli_graph/models.py +1 -0
  59. package/graph-provider/fuli_graph/project_agent_task_models.py +1 -0
  60. package/graph-provider/fuli_graph/store_project_agent_context.py +22 -1
  61. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +23 -8
  62. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +86 -11
  63. package/graph-provider/fuli_graph/store_project_agent_tasks.py +1 -0
  64. package/graph-provider/fuli_graph/store_records.py +1 -0
  65. package/graph-provider/fuli_graph/store_task_contexts.py +2 -1
  66. package/graph-provider/fuli_graph/task_context_adoption.py +9 -4
  67. package/graph-provider/fuli_graph/task_context_temporary_owner.py +25 -0
  68. package/graph-provider/fuli_graph/temporary_project_scope.py +88 -0
  69. package/graph-provider/pyproject.toml +1 -1
  70. package/npm-shrinkwrap.json +131 -79
  71. package/package.json +8 -4
  72. package/skills/capturing-session-knowledge/SKILL.md +20 -5
  73. package/skills/operating-fuli/SKILL.md +8 -0
  74. package/src/agent-tools/graph-definitions.js +1 -1
  75. package/src/agent-tools/interface-catalog.js +5 -0
  76. package/src/agent-tools/project-agent-definitions.js +2 -2
  77. package/src/agent-tools.js +9 -2
  78. package/src/agents/a2a/roundtable-participant.js +374 -0
  79. package/src/agents/claude-code/roundtable-participant.js +24 -0
  80. package/src/agents/codex/roundtable-config.js +29 -0
  81. package/src/agents/codex/roundtable-participant.js +34 -0
  82. package/src/agents/grok/roundtable-participant.js +185 -0
  83. package/src/agents/identity-receipt.js +29 -0
  84. package/src/agents/pi/roundtable-participant.js +193 -0
  85. package/src/agents/pi/roundtable-result-format.js +183 -0
  86. package/src/cli/command-registry.js +2 -0
  87. package/src/cli/roundtable-command.js +59 -0
  88. package/src/cli.js +5 -0
  89. package/src/conversations/task-context-view.js +3 -1
  90. package/src/employees/service.js +9 -2
  91. package/src/employees/task-entry.js +5 -1
  92. package/src/graphiti/agent-conversations.js +15 -1
  93. package/src/graphiti/agent-knowledge-workflows.js +14 -5
  94. package/src/graphiti/federated-application.js +9 -41
  95. package/src/graphiti/project-agent-mapping.js +1 -0
  96. package/src/graphiti/project-agent-mention.js +35 -0
  97. package/src/graphiti/project-agent-task-entry.js +22 -3
  98. package/src/graphiti/project-agent-workflows.js +31 -12
  99. package/src/graphiti/project-path-context.js +1 -0
  100. package/src/graphiti/provider-client.js +3 -0
  101. package/src/graphiti/task-entry-preferences.js +50 -0
  102. package/src/graphiti/temporary-project-scope.js +46 -0
  103. package/src/http/api-router.js +2 -0
  104. package/src/http/error-mapping.js +2 -0
  105. package/src/mcp/provider-task-context-registry.js +10 -0
  106. package/src/mcp/session-id.js +1 -0
  107. package/src/mcp/tool-annotations.js +2 -0
  108. package/src/roundtables/application.js +69 -0
  109. package/src/roundtables/domain.js +181 -0
  110. package/src/roundtables/http-router.js +56 -0
  111. package/src/roundtables/mcp-server.js +48 -0
  112. package/src/roundtables/participant-registry.js +12 -0
  113. package/src/roundtables/process.js +70 -0
  114. package/src/roundtables/result-contract.js +47 -0
  115. package/src/roundtables/server.js +52 -0
  116. package/src/roundtables/service.js +396 -0
  117. package/src/roundtables/store.js +59 -0
  118. package/src/roundtables/tool-contract.js +41 -0
  119. package/src/roundtables/turn-prompt.js +21 -0
  120. package/src/roundtables/worker.js +85 -0
  121. package/dist/web/assets/AgentsDirectoryPage-CKZfrcuD.js +0 -1
  122. package/dist/web/assets/KnowledgePage-uXW1Eovz.js +0 -1
  123. package/dist/web/assets/PersonalProjectsPage-Br1S8ETM.js +0 -1
  124. package/dist/web/assets/ProjectAgentsPage-CA04h1Uf.js +0 -13
  125. package/dist/web/assets/ProjectAgentsPage-CZWWV4Kw.css +0 -1
  126. package/dist/web/assets/i18n-BtOOoyJd.js +0 -9
  127. package/dist/web/assets/index-7X3LGByg.js +0 -2
@@ -0,0 +1,59 @@
1
+ import { resolve } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { openFederatedGraphApplication } from '../graphiti/federated-application.js';
4
+ import { resolveSetupPaths } from '../setup/paths.js';
5
+ import { createRoundtableServer } from '../roundtables/server.js';
6
+ import { runRoundtableWorker } from '../roundtables/worker.js';
7
+
8
+ export async function runRoundtableCommand(args, { env = process.env } = {}) {
9
+ const [action, ...options] = args;
10
+ if (!action || ['--help', '-h'].includes(action)) {
11
+ console.log('fl roundtable serve [--data-dir DIR] [--port 3738] [--host 127.0.0.1] [--public-url HTTPS_URL]\nfl roundtable worker --url URL --room ID --runtime codex|claude-code|pi|grok|a2a [--workspace DIR] [--allow-write] [--model MODEL] [--a2a-url HTTPS_URL]\nSeat secret: FULI_ROUNDTABLE_TOKEN environment variable. Pi uses local Ollama; select an installed model with --model. Owner console remains local; expose only /roundtable-peer/ through your HTTPS proxy.'); return;
12
+ }
13
+ const allowed = new Set(['--data-dir', '--port', '--host', '--public-url', '--url', '--room', '--runtime', '--workspace', '--model', '--a2a-url', '--allow-write', '--once', '--allow-http']);
14
+ const flags = new Set(['--allow-write', '--once', '--allow-http']);
15
+ const values = {};
16
+ for (let index = 0; index < options.length; index++) {
17
+ const name = options[index];
18
+ if (!allowed.has(name) || Object.hasOwn(values, name)) throw new TypeError(`Unknown or duplicate Roundtable option ${name}`);
19
+ if (flags.has(name)) values[name] = true;
20
+ else { const value = options[++index]; if (!value || value.startsWith('--')) throw new TypeError(`Missing ${name}`); values[name] = value; }
21
+ }
22
+ if (action === 'serve') {
23
+ const port = Number(values['--port'] ?? 3738);
24
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65535) throw new TypeError('Invalid Roundtable port');
25
+ const allowedHosts = [];
26
+ if (values['--public-url']) {
27
+ const publicUrl = new URL(values['--public-url']);
28
+ if (publicUrl.protocol !== 'https:' || publicUrl.username || publicUrl.password) throw new TypeError('Public URL must use HTTPS');
29
+ allowedHosts.push(publicUrl.host);
30
+ }
31
+ const paths = resolveSetupPaths({ env, dataDir: values['--data-dir'] ? resolve(values['--data-dir']) : undefined });
32
+ const app = existsSync(paths.graphRuntimeConfigPath)
33
+ ? openFederatedGraphApplication({ runtimeConfigPath: paths.graphRuntimeConfigPath, env }) : null;
34
+ let host;
35
+ try { host = await createRoundtableServer({ dataDir: paths.dataDir, service: app?.roundtables, port, host: values['--host'] ?? '127.0.0.1', allowedHosts }); }
36
+ catch (error) { await app?.close(); throw error; }
37
+ const closeServer = host.close;
38
+ let closing;
39
+ host.close = () => closing ??= closeServer().finally(() => app?.close());
40
+ const stop = () => { void host.close().catch(() => { process.exitCode = 1; }); };
41
+ process.once('SIGINT', stop); process.once('SIGTERM', stop);
42
+ host.server.once('close', () => { process.off('SIGINT', stop); process.off('SIGTERM', stop); });
43
+ console.log(`Agent 圆桌 / Agent Roundtable: ${host.url}/roundtables`);
44
+ return host;
45
+ }
46
+ if (action === 'worker') {
47
+ if (!values['--url'] || !values['--room'] || !values['--runtime']) throw new TypeError('Worker requires --url --room --runtime');
48
+ const controller = new AbortController();
49
+ const stop = () => controller.abort(); process.once('SIGINT', stop); process.once('SIGTERM', stop);
50
+ try {
51
+ return await runRoundtableWorker({ url: values['--url'], roomId: values['--room'], runtime: values['--runtime'],
52
+ workspace: resolve(values['--workspace'] ?? process.cwd()), allowWrite: Boolean(values['--allow-write']),
53
+ model: values['--model'], a2aUrl: values['--a2a-url'], once: Boolean(values['--once']),
54
+ allowHttp: Boolean(values['--allow-http']), env, signal: controller.signal,
55
+ onEvent: (event) => console.log(JSON.stringify(event)) });
56
+ } finally { process.off('SIGINT', stop); process.off('SIGTERM', stop); }
57
+ }
58
+ throw new TypeError('Roundtable action must be serve or worker');
59
+ }
package/src/cli.js CHANGED
@@ -10,6 +10,7 @@ import { runLocalRuntimeCommand } from './cli/local-runtime-command.js';
10
10
  import { runRemoteMcpCommand } from './cli/remote-mcp-command.js';
11
11
  import { runGraphDataCommand } from './cli/graph-data-command.js';
12
12
  import { runEmployeeCommand } from './cli/employee-command.js';
13
+ import { runRoundtableCommand } from './cli/roundtable-command.js';
13
14
  import { runSetupCommand } from './cli/setup-command.js';
14
15
  import { runUninstallCommand } from './cli/uninstall-command.js';
15
16
  import { runUpdateCommand } from './cli/update-command.js';
@@ -41,6 +42,10 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
41
42
  assertSupportedNodeVersion();
42
43
  return runEmployeeCommand(commandArgs, { env });
43
44
  }
45
+ if (command === 'roundtable') {
46
+ assertSupportedNodeVersion();
47
+ return runRoundtableCommand(commandArgs, { env });
48
+ }
44
49
  if (command === 'connect-workspace') {
45
50
  assertSupportedNodeVersion();
46
51
  return runWorkspaceConnectionCommand(commandArgs, { env });
@@ -5,7 +5,9 @@ export function compactTaskContext(value) {
5
5
  const result = { ...value };
6
6
  if (Array.isArray(value.effective_preferences)) result.effective_preferences = value.effective_preferences.map(item => ({
7
7
  instruction: item.instruction, preference_key: item.preference_key,
8
- preference_scope: item.preference_scope, confirmation_status: item.confirmation_status
8
+ preference_scope: item.preference_scope, confirmation_status: item.confirmation_status,
9
+ profile_aspect: item.profile_aspect,
10
+ ...(item.attributes?.tasteDomain ? { attributes: { tasteDomain: item.attributes.tasteDomain } } : {})
9
11
  }));
10
12
  const continuity = value.project_agent_context;
11
13
  if (continuity?.memory) {
@@ -4,6 +4,7 @@ import { createEmployeeRecruitment } from './recruitment.js';
4
4
  import { join } from 'node:path';
5
5
  import { EmployeeManagementStore } from './management-policy.js';
6
6
  import { employeeTaskEntry } from './task-entry.js';
7
+ import { employeeCollaborationReceipt } from '../agents/identity-receipt.js';
7
8
 
8
9
  export function createEmployeeService({ app, runtimeConfigPath, registry: configuredRegistry, managementStore: configuredManagementStore }) {
9
10
  const registry = configuredRegistry ?? createEmployeePackageRegistry(employeeDirectories(runtimeConfigPath));
@@ -65,7 +66,13 @@ export function createEmployeeService({ app, runtimeConfigPath, registry: config
65
66
  sourceApplication: input.sourceApplication ?? 'other', id: input.sourceSessionId,
66
67
  verified: input.sourceSessionVerified === true,
67
68
  } : null;
68
- try { return await runtime.callTool(input.tool, input.arguments ?? {}, { ...context, session }); }
69
+ try {
70
+ const result = await runtime.callTool(input.tool, input.arguments ?? {}, { ...context, session });
71
+ if (!result || typeof result !== 'object' || Array.isArray(result)) return result;
72
+ return { ...result, collaboration_receipt: employeeCollaborationReceipt(app, context, {
73
+ tool: input.tool, permission: tool.permission, sourceApplication: input.sourceApplication
74
+ }) };
75
+ }
69
76
  catch (error) {
70
77
  if (Number.isInteger(error?.status) && error.status >= 400 && error.status < 500) {
71
78
  throw new EmployeeError(error.message, error.status, error.code ?? 'tool_error');
@@ -79,7 +86,7 @@ export function createEmployeeService({ app, runtimeConfigPath, registry: config
79
86
  recruit: async (input) => recruitment.recruit(await resolveInput(input)),
80
87
  workspace, describeTools, callTool,
81
88
  authorize: recruitment.authorize,
82
- taskEntry: (input) => employeeTaskEntry({ registry, recruitment, workspace }, input)
89
+ taskEntry: (input) => employeeTaskEntry({ app, registry, recruitment, workspace }, input)
83
90
  .catch(() => ({ status: 'unavailable', managers: [], worker_started: false })),
84
91
  async handleHttp(request, response, input) {
85
92
  const context = await workspace(input);
@@ -1,6 +1,7 @@
1
1
  // Management is a task-entry capability, not a replacement for the specialist
2
2
  // role/executor chosen by FULI. Never persist the user's prompt or start a model.
3
- export async function employeeTaskEntry({ registry, recruitment, workspace }, input) {
3
+ import { employeeCollaborationReceipt } from '../agents/identity-receipt.js';
4
+ export async function employeeTaskEntry({ app, registry, recruitment, workspace }, input) {
4
5
  if (!input.personalProjectId) return { status: 'project_unresolved', managers: [], worker_started: false };
5
6
  const managers = [];
6
7
  let unavailable = false;
@@ -18,6 +19,9 @@ export async function employeeTaskEntry({ registry, recruitment, workspace }, in
18
19
  const board = await runtime.callTool(manifest.taskEntry.boardTool, { limit: 8 }, resolved);
19
20
  managers.push({
20
21
  template_id: manifest.id, agent_id: context.agentId, name: manifest.name,
22
+ collaboration_receipt: employeeCollaborationReceipt(app, resolved, {
23
+ tool: manifest.taskEntry.boardTool, permission: 'board.read', sourceApplication: input.sourceApplication
24
+ }),
21
25
  personal_project_id: context.project.id, scope_mode: context.management.mode,
22
26
  title_mode: context.management.titleMode, title_style: context.management.titleStyle,
23
27
  board: { version: board.version, total: board.total, truncated: board.truncated,
@@ -1,4 +1,5 @@
1
1
  import { detectSensitiveContent } from '../security/sensitive-content.js';
2
+ import { agentContinuation, taskAgentReceipt } from '../agents/identity-receipt.js';
2
3
 
3
4
  export function conversationScope(application, input) {
4
5
  return { personal_space_id: application.config.personal.spaceId,
@@ -46,16 +47,29 @@ export async function checkpointConversation(application, task, workLog) {
46
47
  ...conversationTask(application, task), summary: safeConversationContent(workLog.summary), status: workLog.status,
47
48
  events: [{ event_id: `${task.token}:summary`, role: 'summary', kind: 'checkpoint', content: safeConversationContent(workLog.summary) }]
48
49
  });
50
+ if (result.status !== 'saved') return result;
51
+ let name = null;
52
+ try {
53
+ const agent = await application.personal.getProjectAgent(application.config.personal.spaceId, task.personalProjectId, task.projectAgentId);
54
+ name = agent?.profile?.display_name || agent?.profile?.name || null;
55
+ } catch { /* Metadata availability does not undo a successful save. */ }
49
56
  return { ...result, agent_id: task.projectAgentId, work_status: workLog.status,
57
+ receipt: taskAgentReceipt(application, { projectId: task.personalProjectId,
58
+ agentId: task.projectAgentId, name, sourceApplication: task.sourceApplication,
59
+ workStatus: workLog.status, conversationId: result.conversation_id,
60
+ persistence: result.status }),
50
61
  coverage: 'task_summary; visible transcript capture is confirmed separately by the host hook',
51
62
  guidance: 'Include the Agent name, truthful work status and conversation ID in the final receipt. In another client select this Agent, then call resume_agent_conversation with its current taskContextToken and this conversation ID.' };
52
63
  } catch { return unavailable(); }
53
64
  }
54
65
 
55
66
  export async function queryConversations(application, input) {
56
- return application.personal.conversation('query', { ...conversationScope(application, input),
67
+ const result = await application.personal.conversation('query', { ...conversationScope(application, input),
57
68
  mode: input.mode ?? 'list', ...(input.conversationId ? { conversation_id: input.conversationId } : {}),
58
69
  after: input.after ?? 0, limit: input.limit ?? (input.mode === 'events' ? 3 : 10) });
70
+ if (!Array.isArray(result.conversations)) return result;
71
+ return { ...result, conversations: result.conversations.map(conversation => ({ ...conversation,
72
+ continuation_prompt: agentContinuation(input.personalProjectId, input.agentId, conversation.id).prompt })) };
59
73
  }
60
74
  export async function resumeConversation(application, input) {
61
75
  const task = await application.taskContextRegistry.context(input.taskContextToken, input.sourceApplication);
@@ -23,18 +23,26 @@ export async function beginTaskContext(application, {
23
23
  requiredCapabilities = []
24
24
  }) {
25
25
  const continuationToken = /^FULI_CHECKPOINT_REQUIRED: (fuli-task-[a-zA-Z0-9-]{8,128})\b/.exec(taskPrompt ?? '')?.[1];
26
+ const current = !continuationToken && turnId && application.taskContextRegistry.current
27
+ ? await application.taskContextRegistry.current(sessionId, sourceApplication) : null;
26
28
  const resumed = continuationToken
27
29
  ? await application.taskContextRegistry.context(continuationToken, sourceApplication) : null;
30
+ const scopeRetry = current?.turnId === turnId && current?.projectScope?.type === 'temporary'
31
+ ? current : null;
28
32
  if (resumed && resumed.sessionId !== sessionId) {
29
33
  throw validationError('Checkpoint continuation belongs to another host session');
30
34
  }
35
+ const existingTask = resumed ?? scopeRetry;
36
+ if (existingTask && personalProjectId && existingTask.personalProjectId !== personalProjectId) {
37
+ throw validationError('Task context belongs to another project');
38
+ }
31
39
  const preferences = await application.getCollaborationPreferences({
32
- personalProjectId,
40
+ personalProjectId: resumed?.personalProjectId ?? scopeRetry?.personalProjectId ?? personalProjectId,
33
41
  projectPath,
34
42
  sessionId,
35
- turnId,
43
+ turnId: existingTask?.turnId ?? turnId,
36
44
  taskPrompt,
37
- projectAgentId: resumed?.projectAgentId ?? projectAgentId,
45
+ projectAgentId: resumed?.projectAgentId ?? scopeRetry?.projectAgentId ?? projectAgentId,
38
46
  sourceApplication,
39
47
  sourceSessionId,
40
48
  workKind,
@@ -45,7 +53,7 @@ export async function beginTaskContext(application, {
45
53
  if (resumed && resumed.personalProjectId !== preferences.context.personal_project_id) {
46
54
  throw validationError('Checkpoint continuation belongs to another project');
47
55
  }
48
- const task = resumed ?? await application.taskContextRegistry.begin({
56
+ const task = resumed ?? scopeRetry ?? await application.taskContextRegistry.begin({
49
57
  sessionId,
50
58
  turnId,
51
59
  personalProjectId: preferences.context.personal_project_id,
@@ -64,6 +72,7 @@ export async function beginTaskContext(application, {
64
72
  resumed_checkpoint: Boolean(resumed),
65
73
  previous_checkpoint_missing: task.previousCheckpointMissing,
66
74
  ...preferences,
75
+ ...(task.projectScope ? { project_scope: task.projectScope } : {}),
67
76
  task_guidance: {
68
77
  collaboration: 'Before implementation assess work complexity and required capabilities using plan_agent_collaboration. The stable project lead coordinates qualified specialists and source-lead-approved loans. Use only available authorized clients/models. Ask optional verification-client preference once; after 15 seconds use the recommended authorized task-local default. Persist project/global preferences only after explicit choice. Verify artifacts, record_agent_verification with evidence; after two failures escalate capability. Never claim a model change or worker run from a routing recommendation alone.',
69
78
  profile_capture: 'Evaluate durable artifact taste (including writing voice, wording, structure and formatting), stable collaboration personality, and decision preferences separately. Do not default every preference to judgment_preference. Use tasteDomain: writing for writing evidence; retain exact session/turn/time provenance. Agent-inferred personality remains pending. Report actual missing evidence rather than promising automatic progress from use alone.',
@@ -124,7 +133,7 @@ export async function checkpointTaskKnowledge(application, {
124
133
  targetKind: 'personal',
125
134
  spaceId: application.config.personal.spaceId,
126
135
  personalProjectId: task.personalProjectId,
127
- projectAgentId: task.projectAgentId ?? null,
136
+ projectAgentId: task.agentMemoryScope === 'task_only' ? null : task.projectAgentId ?? null,
128
137
  sessionId: task.sessionId,
129
138
  sourceApplication,
130
139
  idempotencyKey: `${task.token}:knowledge`
@@ -1,5 +1,6 @@
1
1
  import { dirname } from 'node:path';
2
2
  import { createEmployeeService } from '../employees/service.js';
3
+ import { createFuliRoundtableService } from '../roundtables/application.js';
3
4
 
4
5
  import { GraphitiProviderClient } from './provider-client.js';
5
6
  import {
@@ -76,11 +77,8 @@ import {
76
77
  import {
77
78
  relatedProjectGuidance
78
79
  } from './related-project-suggestions.js';
79
- import {
80
- getCollaborationPreferences as getCollaborationPreferencesWorkflow
81
- } from './collaboration-preference-workflow.js';
82
80
  import { buildUserTasteSkill } from './user-taste-skill.js';
83
- import { resolveTaskEntryAgent, loadProjectAgentContinuity } from './project-agent-task-entry.js';
81
+ import { taskEntryPreferences } from './task-entry-preferences.js';
84
82
  import { getWritingTasteProfile as getWritingTasteProfileWorkflow } from './writing-taste-profile-workflow.js';
85
83
  import {
86
84
  groupSubscriptions,
@@ -117,6 +115,7 @@ export function openFederatedGraphApplication({
117
115
  fetchImpl
118
116
  });
119
117
  app.employees = createEmployeeService({ app, runtimeConfigPath });
118
+ app.roundtables = createFuliRoundtableService({ app, dataDir: dirname(runtimeConfigPath) });
120
119
  }
121
120
  return app;
122
121
  }
@@ -256,42 +255,11 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
256
255
  agentToolName = 'get_collaboration_preferences'
257
256
  } = {}) {
258
257
  const projectResolution = await this.#resolvePreferenceProject({ personalProjectId, projectPath });
259
- const selection = projectPath === null && !personalProjectId && !projectAgentId
260
- ? null
261
- : await resolveTaskEntryAgent(
262
- this, projectResolution, { projectAgentId, taskPrompt, sourceApplication,
263
- sessionId: agentToolName === 'get_collaboration_preferences'
264
- ? sessionId ?? sourceSessionId : turnId ? sessionId : null,
265
- turnId: agentToolName === 'begin_task_context' ? turnId : null,
266
- workKind, requiredCapabilities, agentInvocation, agentToolName }
267
- );
268
- // A rejected explicit role must not leak its private preferences via fallback.
269
- const selectedAgentId = selection ? selection.agent?.agentId ?? null : projectAgentId;
270
- const preferences = await getCollaborationPreferencesWorkflow(
271
- this,
272
- projectResolution,
273
- {
274
- projectAgentId: selectedAgentId,
275
- taskPrompt,
276
- limit,
277
- agentInvocation,
278
- agentToolName
279
- },
280
- (items, toolName) => this.#recordAgentViews(items, toolName)
281
- );
282
- const management = this.employees && agentInvocation &&
283
- ['begin_task_context', 'get_collaboration_preferences'].includes(agentToolName)
284
- ? await this.employees.taskEntry({ personalProjectId: projectResolution.personalProjectId,
285
- sourceApplication, sourceSessionId, sessionId })
286
- : null;
287
- const managedPreferences = management ? { ...preferences, project_management_context: management } : preferences;
288
- if (!selection) return managedPreferences;
289
- const context = selection.agent ? await loadProjectAgentContinuity(this, {
290
- projectId: projectResolution.personalProjectId, agent: selection.agent,
291
- sourceApplication, taskPrompt, selectionReason: selection.reason,
292
- matchBasis: selection.match_basis
293
- }) : selection;
294
- return { ...managedPreferences, project_agent_context: context };
258
+ return taskEntryPreferences(this, projectResolution, {
259
+ personalProjectId, projectAgentId, projectPath, taskPrompt, sourceApplication,
260
+ sourceSessionId, sessionId, turnId, workKind, requiredCapabilities,
261
+ limit, agentInvocation, agentToolName
262
+ }, (items, toolName) => this.#recordAgentViews(items, toolName));
295
263
  }
296
264
 
297
265
  async getUserTasteSkill({
@@ -1179,7 +1147,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
1179
1147
  };
1180
1148
  }
1181
1149
 
1182
- close() { return this.employees?.close(); }
1150
+ close() { this.roundtables?.close(); return this.employees?.close(); }
1183
1151
 
1184
1152
  async #recordAgentViews(items, toolName) {
1185
1153
  const unique = new Map(items.map((item) => [
@@ -411,6 +411,7 @@ export function projectAgentTaskRecord(value = {}) {
411
411
  taskId: value.task_id,
412
412
  personalSpaceId: value.personal_space_id,
413
413
  personalProjectId: value.personal_project_id,
414
+ ...(value.project_scope ? { projectScope: value.project_scope } : {}),
414
415
  title: value.title,
415
416
  objective: value.objective,
416
417
  workKind: value.work_kind,
@@ -0,0 +1,35 @@
1
+ // Only an explicit leading mention selects a role. Matching uses this project's
2
+ // directory, never fuzzy search or another Agent's memory. Provider authorization
3
+ // still runs after matching, including for inactive or client-restricted names.
4
+ export async function resolveTaskAgentMention(application, projectId, input) {
5
+ const prompt = String(input.taskPrompt ?? '').trimStart();
6
+ if (!prompt.startsWith('@')) return null;
7
+ const directory = await application.personal.listProjectAgents(
8
+ application.config.personal.spaceId, projectId);
9
+ const agents = Array.isArray(directory) ? directory : directory.agents ?? directory.items ?? [];
10
+ const raw = prompt.slice(1, 2049);
11
+ const text = normalize(raw);
12
+ const stable = /^\{([^{}\s]+)\}(?=$|[\s,,::])/u.exec(raw);
13
+ let stableId;
14
+ try { stableId = stable ? decodeURIComponent(stable[1]) : null; }
15
+ catch { stableId = null; }
16
+ const matches = agents.flatMap(agent => {
17
+ const aliases = stable ? [agent.agent_id] : [agent.profile?.display_name, agent.profile?.name, agent.agent_id];
18
+ const lengths = aliases.filter(Boolean).map(name => stable ? name : normalize(name)).filter(name => stable
19
+ ? name === stableId : text.startsWith(name) && /^(?:$|[\s,,::!?!?])/u.test(text.slice(name.length))).map(name => name.length);
20
+ return lengths.length ? [{ agent, length: Math.max(...lengths) }] : [];
21
+ });
22
+ const longest = Math.max(0, ...matches.map(match => match.length));
23
+ const exact = matches.filter(match => match.length === longest);
24
+ const failure = status => ({ status, worker_started: false,
25
+ required_action: 'Select an exact current-project Agent with @{agentId}; use list_project_agents to resolve names. Do not load a different Agent as fallback.' });
26
+ if (!exact.length) return failure('agent_not_found');
27
+ if (exact.length > 1) return { ...failure('ambiguous_agent'),
28
+ candidates: exact.slice(0, 8).map(({ agent }) => ({ agent_id: agent.agent_id,
29
+ name: agent.profile?.display_name || agent.profile?.name })) };
30
+ const agentId = exact[0].agent.agent_id;
31
+ if (input.projectAgentId && input.projectAgentId !== agentId) return failure('agent_selection_conflict');
32
+ return { agentId };
33
+ }
34
+
35
+ function normalize(value) { return String(value).normalize('NFKC').toLowerCase(); }
@@ -2,6 +2,7 @@ import { projectAgentRecord } from './project-agent-mapping.js';
2
2
  import { agentMemoryView } from './project-agent-memory.js';
3
3
  import { planTaskKnowledgeRecall } from './task-knowledge-recall.js';
4
4
  import { loadProjectTeamContext } from './project-agent-team-context.js';
5
+ import { resolveTaskAgentMention } from './project-agent-mention.js';
5
6
 
6
7
  const ENTRY_TOOLS = new Set(['begin_task_context', 'get_collaboration_preferences']);
7
8
 
@@ -9,18 +10,25 @@ export async function resolveTaskEntryAgent(application, resolution, input) {
9
10
  if (!input.agentInvocation || !ENTRY_TOOLS.has(input.agentToolName)) return null;
10
11
  if (!resolution.personalProjectId) return { status: 'project_unresolved', worker_started: false };
11
12
  try {
13
+ const mention = await resolveTaskAgentMention(application, resolution.personalProjectId, input);
14
+ if (mention?.status) return mention;
15
+ const workKind = input.workKind ?? taskWorkKind(input.taskPrompt);
12
16
  const value = await application.personal.resolveProjectAgentContext({
13
17
  personal_space_id: application.config.personal.spaceId,
14
18
  personal_project_id: resolution.personalProjectId,
15
- agent_id: input.projectAgentId ?? null,
19
+ agent_id: mention?.agentId ?? input.projectAgentId ?? null,
16
20
  session_id: input.sessionId ?? null,
17
21
  turn_id: input.turnId ?? null,
18
- work_kind: input.workKind ?? taskWorkKind(input.taskPrompt),
22
+ work_kind: workKind,
19
23
  required_capabilities: input.requiredCapabilities ?? [],
20
24
  source_application: input.sourceApplication ?? 'other'
21
25
  });
22
26
  if (!value?.status) throw new Error('Role resolution is unavailable');
23
27
  const agent = value.agent ? projectAgentRecord(value.agent) : null;
28
+ if (agent && !mention?.agentId && !input.projectAgentId && unrelatedPeer(agent, workKind, input.requiredCapabilities)) {
29
+ return { status: 'unassigned', reason: 'specialist_required', worker_started: false,
30
+ required_action: 'Use coordinate_project_agent_task to select or recruit a qualified durable owner before implementation. HR and project managers collaborate in their own roles; do not attribute development to them by fallback.' };
31
+ }
24
32
  const employeeId = /^employee\.([a-z][a-z0-9-]{0,63})$/.exec(agent?.agentId ?? '')?.[1];
25
33
  if (employeeId && application.employees) {
26
34
  try { await application.employees.authorize({ templateId: employeeId, personalProjectId: resolution.personalProjectId }); }
@@ -118,6 +126,15 @@ function boundedRoleText(value, limit) {
118
126
  return typeof value === 'string' ? value.slice(0, limit) : '';
119
127
  }
120
128
 
129
+ function unrelatedPeer(agent, workKind, required = []) {
130
+ const profile = agent.profile;
131
+ const capabilities = (profile.capabilities ?? []).map(value => value.toLowerCase());
132
+ const peer = ['employee.jefa', 'employee.bole', 'fuli-project-hr'].includes(agent.agentId)
133
+ || profile.agentType === 'hr' || capabilities.some(value => ['fuli.employee:jefa', 'fuli.employee:bole'].includes(value));
134
+ return peer && !(profile.workKinds ?? []).some(kind => kind.toLowerCase() === workKind.toLowerCase())
135
+ && !(required.length && required.every(value => capabilities.includes(value.toLowerCase())));
136
+ }
137
+
121
138
  function scopedItems(items, agentId) {
122
139
  return (items ?? []).filter(item => !item.project_agent_id || item.project_agent_id === agentId)
123
140
  .slice(0, 4).map(item => ({
@@ -133,7 +150,9 @@ function scopedItems(items, agentId) {
133
150
  function taskWorkKind(prompt) {
134
151
  const text = typeof prompt === 'string' ? prompt.slice(0, 8192) : '';
135
152
  if (/代码审查|代码评审|code review|review code/iu.test(text)) return 'code_review';
136
- if (/修复|实现|开发|编写代码|\b(?:implement|fix|bug|coding)\b/iu.test(text)) return 'implementation';
153
+ if (/架构|\barchitecture\b/iu.test(text)) return 'architecture';
154
+ if (/修复|实现|开发|编写代码|优化|改为|接入|\b(?:implement|fix|bug|coding)\b/iu.test(text)) return 'implementation';
137
155
  if (/测试|验证|\b(?:test|verify|validation)\b/iu.test(text)) return 'test_validation';
156
+ if (/看板|排期|规划|\bplanning\b/iu.test(text)) return 'project_management';
138
157
  return 'project_context';
139
158
  }
@@ -1,4 +1,5 @@
1
1
  import { loadProjectAgentContinuity } from './project-agent-task-entry.js';
2
+ import { resolveCoordinationScope, scopedTaskIdempotencyKey } from './temporary-project-scope.js';
2
3
  import {
3
4
  executorActualReportRecord,
4
5
  executorRecord,
@@ -152,6 +153,9 @@ export async function coordinateProjectAgentTask(
152
153
  projectResolution,
153
154
  input
154
155
  ) {
156
+ let entryTask = input.taskContextToken
157
+ ? await application.taskContextRegistry.context(input.taskContextToken, input.sourceApplication) : null;
158
+ projectResolution = await resolveCoordinationScope(application, projectResolution, input, entryTask);
155
159
  const projectId = projectResolution.personalProjectId ??
156
160
  projectResolution.personal_project_id ?? null;
157
161
  if (!projectId) {
@@ -168,23 +172,28 @@ export async function coordinateProjectAgentTask(
168
172
  };
169
173
  }
170
174
 
171
- const entryTask = input.taskContextToken
172
- ? await application.taskContextRegistry.context(input.taskContextToken, input.sourceApplication) : null;
175
+ if (entryTask && projectResolution.scope?.type === 'temporary') {
176
+ entryTask = await application.taskContextRegistry.context(input.taskContextToken, input.sourceApplication);
177
+ }
173
178
  if (entryTask && entryTask.personalProjectId !== projectId) {
174
179
  throw new TypeError('Coordination task context belongs to another project');
175
180
  }
176
181
  const route = await submitProjectAgentTask(application, {
177
182
  personalSpaceId: application.config.personal.spaceId,
178
183
  personalProjectId: projectId,
179
- idempotencyKey: input.idempotencyKey,
184
+ idempotencyKey: projectResolution.scope?.type === 'temporary'
185
+ ? scopedTaskIdempotencyKey(projectId, input) : input.idempotencyKey,
180
186
  title: input.title,
181
187
  verificationRequired: true,
182
188
  objective: input.objective,
183
189
  workKind: input.workKind,
184
190
  requiredCapabilities: input.requiredCapabilities,
185
191
  executorCapabilityHints: input.executorCapabilityHints,
186
- duration: input.duration,
187
- staffingIntent: input.staffingIntent,
192
+ duration: projectResolution.scope?.type === 'temporary' ? 'one_off' : input.duration,
193
+ staffingIntent: projectResolution.scope?.type === 'temporary'
194
+ && !input.leadAgentId
195
+ && (!input.staffingIntent || input.staffingIntent === 'reuse_preferred')
196
+ ? 'temporary' : input.staffingIntent,
188
197
  leadAgentId: input.leadAgentId,
189
198
  collaboratorAgentIds: input.collaboratorAgentIds,
190
199
  coordinatorAgentId: input.coordinatorAgentId,
@@ -193,7 +202,8 @@ export async function coordinateProjectAgentTask(
193
202
  modelStrategyOverride: input.modelStrategyOverride,
194
203
  executorPolicyOverride: input.executorPolicyOverride,
195
204
  sourceApplication: input.sourceApplication,
196
- sourceSessionId: input.sourceSessionId,
205
+ sourceSessionId: projectResolution.scope?.type === 'temporary' && entryTask
206
+ ? entryTask.sessionId : input.sourceSessionId,
197
207
  routingReason: input.routingReason,
198
208
  recruitmentProfile: input.recruitmentProfile
199
209
  });
@@ -227,10 +237,11 @@ export async function coordinateProjectAgentTask(
227
237
  const hostExecutionRequired = route.task.status === 'queued' &&
228
238
  workerPlan.length > 0 && contextsReady;
229
239
  let adoptedTask = null;
240
+ const leadMemoryScope = workerPlan.find(worker => worker.agent_id === route.task.leadAgentId)
241
+ ?.context?.agent?.memoryScope;
230
242
  if (entryTask && !entryTask.projectAgentId && contextsReady
231
243
  && ['queued', 'running', 'paused', 'blocked', 'awaiting_recruitment'].includes(route.task.status)
232
- && workerPlan.find(worker => worker.agent_id === route.task.leadAgentId)
233
- ?.context?.agent?.memoryScope === 'reviewed_agent') {
244
+ && ['reviewed_agent', 'task_only'].includes(leadMemoryScope)) {
234
245
  adoptedTask = await application.taskContextRegistry.adoptAgent(input.taskContextToken, {
235
246
  personalProjectId: projectId, taskId: route.task.taskId, agentId: route.task.leadAgentId
236
247
  }, input.sourceApplication);
@@ -245,10 +256,16 @@ export async function coordinateProjectAgentTask(
245
256
  personal_space_id: application.config.personal.spaceId,
246
257
  personal_project_id: projectId,
247
258
  project_resolution: projectResolution,
259
+ ...(projectResolution.scope ? { project_scope: projectResolution.scope } : {}),
260
+ ...(route.task.status === 'awaiting_recruitment' ? {
261
+ required_action: 'Review the returned recruitment proposal and use decide_project_agent_recruitment with its current revision before starting a worker.'
262
+ } : {}),
248
263
  route,
249
- ...(adoptedTask ? { task_context: {
250
- task_context_token: adoptedTask.token, project_agent_id: adoptedTask.projectAgentId,
251
- memory_revision: adoptedTask.memoryRevision, work_log_required: adoptedTask.workLogRequired
264
+ ...(adoptedTask || (entryTask && projectResolution.scope) ? { task_context: {
265
+ task_context_token: (adoptedTask ?? entryTask).token,
266
+ personal_project_id: projectId, project_agent_id: (adoptedTask ?? entryTask).projectAgentId,
267
+ memory_revision: (adoptedTask ?? entryTask).memoryRevision,
268
+ work_log_required: (adoptedTask ?? entryTask).workLogRequired
252
269
  } } : {}),
253
270
  host_execution_required: hostExecutionRequired,
254
271
  host_execution_policy: {
@@ -581,7 +598,9 @@ export async function getProjectAgentContext(
581
598
  executionSummary: agent.executionSummary ?? [],
582
599
  role_instructions: {
583
600
  responsibility: agent.profile.responsibility,
584
- initial_preferences: agent.profile.initialPreferences
601
+ initial_preferences: agent.profile.initialPreferences,
602
+ character: continuity.role.character,
603
+ expectations: continuity.role.expectations
585
604
  },
586
605
  effective_preferences: preferences.effective_preferences,
587
606
  deferred_conflicts: preferences.deferred_conflicts,
@@ -39,6 +39,7 @@ export function resolvePersonalProjectPath(projectPath, projects, {
39
39
 
40
40
  const projectIds = new Set(
41
41
  projects
42
+ .filter(project => project.scope_type !== 'temporary' && project.scopeType !== 'temporary')
42
43
  .map(({ project_id: projectId }) => projectId)
43
44
  .filter((projectId) => safeProjectId(projectId, pathApi))
44
45
  );
@@ -87,6 +87,9 @@ export class GraphitiProviderClient {
87
87
  upsertPersonalProject(input) {
88
88
  return this.#request('/v1/personal-projects', { method: 'PUT', body: input });
89
89
  }
90
+ ensureTemporaryProject(input) {
91
+ return this.#request('/v1/temporary-projects/ensure', { method: 'POST', body: input });
92
+ }
90
93
  listPersonalProjects(personalSpaceId) {
91
94
  const query = new URLSearchParams({ personal_space_id: personalSpaceId });
92
95
  return this.#request(`/v1/personal-projects?${query}`);
@@ -0,0 +1,50 @@
1
+ import { taskAgentReceipt } from '../agents/identity-receipt.js';
2
+ import { getCollaborationPreferences as getCollaborationPreferencesWorkflow } from './collaboration-preference-workflow.js';
3
+ import { resolveTaskEntryAgent, loadProjectAgentContinuity } from './project-agent-task-entry.js';
4
+
5
+ export async function taskEntryPreferences(application, projectResolution, {
6
+ personalProjectId, projectAgentId, projectPath, taskPrompt, sourceApplication,
7
+ sourceSessionId, sessionId, turnId, workKind, requiredCapabilities,
8
+ limit, agentInvocation, agentToolName
9
+ }, recordAgentViews) {
10
+ const selection = projectPath === null && !personalProjectId && !projectAgentId
11
+ ? null
12
+ : await resolveTaskEntryAgent(
13
+ application, projectResolution, { projectAgentId, taskPrompt, sourceApplication,
14
+ sessionId: agentToolName === 'get_collaboration_preferences'
15
+ ? sessionId ?? sourceSessionId : turnId ? sessionId : null,
16
+ turnId: agentToolName === 'begin_task_context' ? turnId : null,
17
+ workKind, requiredCapabilities, agentInvocation, agentToolName }
18
+ );
19
+ // A rejected explicit role must not leak its private preferences via fallback.
20
+ const selectedAgentId = selection ? selection.agent?.agentId ?? null : projectAgentId;
21
+ const preferences = await getCollaborationPreferencesWorkflow(
22
+ application,
23
+ projectResolution,
24
+ {
25
+ projectAgentId: selectedAgentId,
26
+ taskPrompt,
27
+ limit,
28
+ agentInvocation,
29
+ agentToolName
30
+ },
31
+ (items, toolName) => recordAgentViews(items, toolName)
32
+ );
33
+ const management = application.employees && agentInvocation &&
34
+ ['begin_task_context', 'get_collaboration_preferences'].includes(agentToolName)
35
+ ? await application.employees.taskEntry({ personalProjectId: projectResolution.personalProjectId,
36
+ sourceApplication, sourceSessionId, sessionId })
37
+ : null;
38
+ const managedPreferences = management ? { ...preferences, project_management_context: management } : preferences;
39
+ if (!selection) return managedPreferences;
40
+ const context = selection.agent ? await loadProjectAgentContinuity(application, {
41
+ projectId: projectResolution.personalProjectId, agent: selection.agent,
42
+ sourceApplication, taskPrompt, selectionReason: selection.reason,
43
+ matchBasis: selection.match_basis
44
+ }) : selection;
45
+ return { ...managedPreferences, project_agent_context: context,
46
+ ...(selection.agent ? { agent_receipt: taskAgentReceipt(application, {
47
+ projectId: projectResolution.personalProjectId, agentId: selection.agent.agentId,
48
+ name: context.role?.name, sourceApplication
49
+ }) } : {}) };
50
+ }