fuli-context 0.10.0 → 0.11.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.
- package/README.md +27 -30
- package/README.zh-CN.md +22 -26
- package/dist/web/assets/AboutPage-cW9hDghN.js +1 -0
- package/dist/web/assets/AgentConversations-CGznTWuV.js +2 -0
- package/dist/web/assets/AgentConversations-XqNEBY_2.css +1 -0
- package/dist/web/assets/AgentHand-DkyF4vgk.js +1 -0
- package/dist/web/assets/AgentName-C1mAVr-v.js +1 -0
- package/dist/web/assets/AgentProfilePage-pKUR0rfj.js +1 -0
- package/dist/web/assets/AgentsDirectoryPage-CUlbT2hD.js +1 -0
- package/dist/web/assets/AgentsDirectoryPage-yRWktiin.css +1 -0
- package/dist/web/assets/ConnectionsPage-CM9C-zEI.css +1 -0
- package/dist/web/assets/ConnectionsPage-CuDHIJXu.js +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-CVbqCzHb.css +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-D9QfGipd.js +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-D-c_Jm2z.css +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-DAtvGney.js +3 -0
- package/dist/web/assets/KnowledgeEditDialog-a-5rasG0.js +1 -0
- package/dist/web/assets/KnowledgeEditDialog-hT9znkoe.css +1 -0
- package/dist/web/assets/KnowledgeInspector-BGsqjNY2.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BndgUsHS.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-DeL7Jp34.css +1 -0
- package/dist/web/assets/KnowledgePage-CQweO0YJ.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-BtUjKTLb.js +4 -0
- package/dist/web/assets/KnowledgeWorkspace-D4a7FcAb.css +1 -0
- package/dist/web/assets/NotFoundPage-BmQTTqQn.js +1 -0
- package/dist/web/assets/OverviewPage-By-fUzSG.css +1 -0
- package/dist/web/assets/OverviewPage-C73HyTsM.js +1 -0
- package/dist/web/assets/PersonalProfilePage-BMuOrppx.js +1 -0
- package/dist/web/assets/PersonalProfilePage-DrMDAAdy.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-Cq9_kFvs.js +1 -0
- package/dist/web/assets/ProjectAgentFirstTask-Bi1ZsTC1.css +1 -0
- package/dist/web/assets/ProjectAgentFirstTask-CaHvOjkp.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-DWGkgIsr.css +1 -0
- package/dist/web/assets/ProjectAgentsPage-WVcNdzVi.js +12 -0
- package/dist/web/assets/ProjectScopePicker-zAgiaZKL.js +1 -0
- package/dist/web/assets/PublicProjectsPage-DKamXDV9.css +1 -0
- package/dist/web/assets/PublicProjectsPage-DL093f65.js +1 -0
- package/dist/web/assets/ReviewPage-D_NIhPx8.js +1 -0
- package/dist/web/assets/RoundtablesPage-Cb0R4GaM.css +1 -0
- package/dist/web/assets/RoundtablesPage-CdVVyZE-.js +1 -0
- package/dist/web/assets/SettingsPage-BmYkLul4.css +1 -0
- package/dist/web/assets/SettingsPage-CbLaau8Q.js +1 -0
- package/dist/web/assets/SharedJefaReportPage-aygd6wvb.js +1 -0
- package/dist/web/assets/UiDialog-Q7fsJUoO.js +1 -0
- package/dist/web/assets/UiDisclosure-BS-muK5j.js +1 -0
- package/dist/web/assets/UiSegmented-CT28bxqw.js +1 -0
- package/dist/web/assets/UiSelect-Bv6R0yr1.css +1 -0
- package/dist/web/assets/UiSelect-DcGpPQQo.js +1 -0
- package/dist/web/assets/WritingTastePage-B_zYD1df.css +1 -0
- package/dist/web/assets/WritingTastePage-CYlwAFqV.js +1 -0
- package/dist/web/assets/agent-profile-BZj3ezWe.css +1 -0
- package/dist/web/assets/{attention-store-Dk-_P4Oi.js → attention-store-D-qIFRIb.js} +1 -1
- package/dist/web/assets/{catalog-DN72452f.js → catalog-UDv854JN.js} +1 -1
- package/dist/web/assets/client-DLS5FZBI.js +1 -0
- package/dist/web/assets/{console-BtPqo79j.js → console-CqzwSo7a.js} +1 -1
- package/dist/web/assets/{conversation-launcher-settings-jkHRmWai.js → conversation-launcher-settings-DRoIz42V.js} +1 -1
- package/dist/web/assets/{i18n-D2CJkEYK.js → i18n-b14l2pQA.js} +3 -3
- package/dist/web/assets/{identity-B3zEw-1u.js → identity-BWbPmZZf.js} +1 -1
- package/dist/web/assets/index-D1OHIz4c.js +5 -0
- package/dist/web/assets/index-kPB0Cf8Q.css +1 -0
- package/dist/web/assets/{model-S7ehAjky.js → model-DupZy8Qb.js} +1 -1
- package/dist/web/assets/{personal-profile-graph--GG3Mizo.js → personal-profile-graph-BPTtMJTZ.js} +1 -1
- package/dist/web/assets/shared-report-BYnLhmCZ.js +1 -0
- package/dist/web/assets/{useAgentRoster-BAPKWZ9y.js → useAgentRoster-ep7SpKv1.js} +1 -1
- package/dist/web/assets/{useApi-CROJJdhE-BtFMXFjw.js → useApi-CROJJdhE-CEQAEuug.js} +1 -1
- package/dist/web/assets/{vue-router-DkaTtPn0.js → vue-router-B6ZjK-Br.js} +1 -1
- package/dist/web/index.html +14 -16
- package/docs/agent-conversations-and-collaboration.md +9 -9
- package/docs/agent-roundtable.md +77 -0
- package/docs/employee-agents.md +2 -2
- package/docs/project-agent-memory.md +1 -1
- package/graph-provider/fuli_graph/agent_conversation_models.py +11 -2
- package/graph-provider/fuli_graph/conversation_context.py +40 -9
- package/graph-provider/fuli_graph/default_project_lead.py +88 -0
- package/graph-provider/fuli_graph/project_agent_context_models.py +5 -1
- package/graph-provider/fuli_graph/project_agent_delegations.py +118 -0
- package/graph-provider/fuli_graph/project_agent_employee_numbers.py +41 -0
- package/graph-provider/fuli_graph/project_agent_memory_routes.py +20 -0
- package/graph-provider/fuli_graph/project_agent_models.py +1 -0
- package/graph-provider/fuli_graph/project_agent_task_models.py +3 -0
- package/graph-provider/fuli_graph/project_agent_team_growth.py +2 -2
- package/graph-provider/fuli_graph/runtime.py +10 -76
- package/graph-provider/fuli_graph/store.py +2 -0
- package/graph-provider/fuli_graph/store_agent_conversations.py +60 -8
- package/graph-provider/fuli_graph/store_project_agent_context.py +52 -4
- package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +16 -1
- package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +3 -1
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +3 -2
- package/graph-provider/fuli_graph/store_project_agent_tasks.py +12 -8
- package/graph-provider/fuli_graph/store_project_agents.py +56 -129
- package/graph-provider/fuli_graph/store_task_contexts.py +26 -0
- package/graph-provider/fuli_graph/system_agents.py +130 -0
- package/graph-provider/fuli_graph/system_hr_identity.py +1 -1
- package/graph-provider/fuli_graph/task_context_routes.py +10 -0
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/src/agent-roundtable/http.js +14 -0
- package/src/agent-roundtable/service.js +247 -0
- package/src/agent-roundtable/store.js +86 -0
- package/src/agent-roundtable/tools.js +61 -0
- package/src/agent-roundtable/wake.js +100 -0
- package/src/agent-tools/agent-conversation-definitions.js +3 -3
- package/src/agent-tools/graph-definitions.js +3 -2
- package/src/agent-tools/interface-catalog.js +0 -5
- package/src/agent-tools.js +2 -2
- package/src/agents/claude-code/wake.js +37 -0
- package/src/agents/codex/wake.js +47 -0
- package/src/agents/identity-receipt.js +35 -7
- package/src/agents/session-log.js +34 -0
- package/src/agents/wake-registry.js +14 -0
- package/src/cli/command-registry.js +0 -2
- package/src/cli/setup-command.js +2 -5
- package/src/cli/update-command.js +13 -2
- package/src/cli.js +0 -5
- package/src/employees/management-policy.js +4 -2
- package/src/employees/manifest.js +3 -0
- package/src/employees/recruitment.js +3 -2
- package/src/graphiti/agent-conversations.js +11 -9
- package/src/graphiti/agent-knowledge-workflows.js +34 -7
- package/src/graphiti/federated-application.js +17 -5
- package/src/graphiti/project-agent-mapping.js +2 -0
- package/src/graphiti/project-agent-task-entry.js +38 -8
- package/src/graphiti/project-agent-team-context.js +4 -1
- package/src/graphiti/project-path-context.js +7 -2
- package/src/graphiti/provider-client.js +17 -0
- package/src/graphiti/repository-project-registration.js +17 -0
- package/src/graphiti/task-entry-preferences.js +17 -4
- package/src/graphiti/task-entry-project-scope.js +67 -0
- package/src/http/api-router.js +5 -3
- package/src/http/error-mapping.js +0 -2
- package/src/mcp/instructions.js +5 -5
- package/src/mcp/session-id.js +1 -1
- package/src/mcp/tool-annotations.js +3 -4
- package/src/setup/agents.js +1 -41
- package/src/setup/graph-runtime.js +15 -2
- package/src/setup/session-skill.js +22 -2
- package/dist/web/assets/AboutPage-D-GyJNv1.js +0 -1
- package/dist/web/assets/AgentConversations-DOuZgUeA.js +0 -2
- package/dist/web/assets/AgentConversations-DS8h3yKn.css +0 -1
- package/dist/web/assets/AgentHand-eGsXoGSq.js +0 -1
- package/dist/web/assets/AgentName-Dtmu9Ff5.js +0 -1
- package/dist/web/assets/AgentProfilePage-D8Tvayn7.js +0 -1
- package/dist/web/assets/AgentsDirectoryPage-BTEfKZjF.js +0 -1
- package/dist/web/assets/ConnectionsPage-C1w99yN3.css +0 -1
- package/dist/web/assets/ConnectionsPage-DwOIcvl9.js +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-BrIMRblq.css +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-D11blfwU.js +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-BcEzYt65.css +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-CK2BAoGt.js +0 -3
- package/dist/web/assets/GrowthLoading-KTjE2e50.js +0 -1
- package/dist/web/assets/KnowledgeEditDialog-B3x8vs3c.css +0 -1
- package/dist/web/assets/KnowledgeEditDialog-n9trHQ5d.js +0 -1
- package/dist/web/assets/KnowledgeInspector-BcNjgw9H.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-BzgxP-nG.css +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-DMBpGxLr.js +0 -1
- package/dist/web/assets/KnowledgePage-POkCMW8f.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-2FvQadhB.js +0 -4
- package/dist/web/assets/KnowledgeWorkspace-sb8OJ97I.css +0 -1
- package/dist/web/assets/NotFoundPage-BCMZbeB8.js +0 -1
- package/dist/web/assets/OverviewPage-0CFWCG9-.css +0 -1
- package/dist/web/assets/OverviewPage-0mYifpjp.js +0 -1
- package/dist/web/assets/PersonalProfilePage-Chv2a2pi.css +0 -1
- package/dist/web/assets/PersonalProfilePage-JW6Muix6.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-CRbi64JP.js +0 -1
- package/dist/web/assets/ProjectAgentFirstTask-6afJ8HUe.css +0 -1
- package/dist/web/assets/ProjectAgentFirstTask-DJ0wTbh8.js +0 -1
- package/dist/web/assets/ProjectAgentsPage-CPgKVLgg.js +0 -12
- package/dist/web/assets/ProjectAgentsPage-DeuRkMp1.css +0 -1
- package/dist/web/assets/ProjectScopePicker-BwB7gK-7.js +0 -1
- package/dist/web/assets/PublicProjectsPage-CB272Sq-.js +0 -1
- package/dist/web/assets/ReviewPage-CelTZU4C.js +0 -1
- package/dist/web/assets/RoundtablesPage-C9dXfWCl.js +0 -2
- package/dist/web/assets/RoundtablesPage-DCbIBnBq.css +0 -1
- package/dist/web/assets/SearchableSelect-f7EHbyDL.js +0 -1
- package/dist/web/assets/SettingsPage-5g7mRN2F.css +0 -1
- package/dist/web/assets/SettingsPage-nAijmm5f.js +0 -1
- package/dist/web/assets/SharedJefaReportPage-CjpJ9bkk.js +0 -1
- package/dist/web/assets/UiDisclosure-CWlC91Zt.js +0 -1
- package/dist/web/assets/WritingTastePage-DaHWY61P.js +0 -1
- package/dist/web/assets/WritingTastePage-SMGF39mf.css +0 -1
- package/dist/web/assets/agent-profile-CBvJHGjt.css +0 -1
- package/dist/web/assets/client-B2cRl-7c.js +0 -1
- package/dist/web/assets/copy-D2LqwtPZ.js +0 -1
- package/dist/web/assets/index-CNp0tRNp.css +0 -1
- package/dist/web/assets/index-ulISYH02.js +0 -5
- package/dist/web/assets/shared-report-DEK9wvhl.js +0 -1
- package/dist/web/assets/useModalDialog-D8tiJfbc.js +0 -1
- package/docs/roundtable.md +0 -46
- package/src/agents/a2a/roundtable-participant.js +0 -374
- package/src/agents/claude-code/roundtable-participant.js +0 -24
- package/src/agents/codex/roundtable-config.js +0 -32
- package/src/agents/codex/roundtable-participant.js +0 -34
- package/src/agents/grok/roundtable-participant.js +0 -185
- package/src/agents/pi/roundtable-participant.js +0 -193
- package/src/agents/pi/roundtable-result-format.js +0 -183
- package/src/cli/roundtable-command.js +0 -79
- package/src/roundtables/application.js +0 -69
- package/src/roundtables/domain.js +0 -181
- package/src/roundtables/http-router.js +0 -58
- package/src/roundtables/mcp-server.js +0 -55
- package/src/roundtables/participant-registry.js +0 -12
- package/src/roundtables/process.js +0 -70
- package/src/roundtables/profiles.js +0 -84
- package/src/roundtables/result-contract.js +0 -47
- package/src/roundtables/server.js +0 -52
- package/src/roundtables/service.js +0 -480
- package/src/roundtables/store.js +0 -63
- package/src/roundtables/tool-contract.js +0 -58
- package/src/roundtables/turn-prompt.js +0 -24
- package/src/roundtables/worker.js +0 -85
- /package/dist/web/assets/{GrowthLoading-CIuQ1icy.css → client-CIuQ1icy.css} +0 -0
|
@@ -12,20 +12,48 @@ export function agentContinuation(projectId, agentId, conversationId = null) {
|
|
|
12
12
|
guidance: 'Use a connected client and the same authorized project/Agent. Obtain a fresh task context; never reuse the previous client’s token. This is a FULI prompt, not a native client @ picker registration.' };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
sourceApplication = null, workStatus = null, conversationId = null, persistence = null }) {
|
|
17
|
-
const owner = agentId ? { agent_id: agentId, name,
|
|
18
|
-
profile_url: agentProfileUrl(application, agentId), source_application: sourceApplication } : null;
|
|
15
|
+
function agentLink(application, agentId, name) {
|
|
19
16
|
const label = String(name?.trim() || agentId).replace(/[\r\n\t]+/g, ' ')
|
|
20
17
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
21
18
|
.replace(/[\\`*_{}\[\]()!|]/g, '\\$&');
|
|
19
|
+
return `[${label}](<${agentProfileUrl(application, agentId).replace(/[<>\s]/g, encodeURIComponent)}>)`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// The reply's attribution line: who led the task and who actually helped.
|
|
23
|
+
export function taskAgentReceipt(application, { projectId, agentId, name = null,
|
|
24
|
+
sourceApplication = null, workStatus = null, conversationId = null, persistence = null,
|
|
25
|
+
collaborators = [], assignmentStatus = null, assignmentReason = null }) {
|
|
26
|
+
const owner = agentId ? { agent_id: agentId, name,
|
|
27
|
+
profile_url: agentProfileUrl(application, agentId), source_application: sourceApplication } : null;
|
|
28
|
+
const helpers = owner ? collaborators.filter(item => item.agentId !== agentId) : [];
|
|
22
29
|
const markdown = owner
|
|
23
|
-
? `FULI Agent
|
|
24
|
-
|
|
30
|
+
? `FULI Agent:负责人 ${agentLink(application, agentId, name)}`
|
|
31
|
+
+ (helpers.length ? ` · 协作 ${helpers.map(item => agentLink(application, item.agentId, item.name)).join('、')}` : '')
|
|
32
|
+
: unassignedReceipt(assignmentStatus, assignmentReason);
|
|
25
33
|
return { required: true, markdown, owner,
|
|
34
|
+
collaborators: helpers.map(item => ({ agent_id: item.agentId, name: item.name,
|
|
35
|
+
profile_url: agentProfileUrl(application, item.agentId) })),
|
|
26
36
|
personal_project_id: projectId, work_status: workStatus, persistence,
|
|
27
37
|
continuation: owner ? agentContinuation(projectId, agentId, conversationId) : null,
|
|
28
|
-
guidance: 'MUST
|
|
38
|
+
guidance: 'MUST end every final user-visible reply with agent_receipt.markdown unchanged, exactly once, including greetings, status and errors, even when capture is disabled or nothing is retained. The receipt returned by checkpoint_task_knowledge supersedes the entry receipt because it names actual collaborators. Never invent an owner or a collaborator.' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function unassignedReceipt(status, reason) {
|
|
42
|
+
if (!status) return 'FULI Agent:分配未完成(任务没有可用负责人)';
|
|
43
|
+
if (status === 'project_unresolved') {
|
|
44
|
+
return reason === 'ambiguous' ? 'FULI Agent:分配待确认(项目归属待确认)'
|
|
45
|
+
: reason === 'session_identity_required' ? 'FULI Agent:分配失败(缺少会话标识)'
|
|
46
|
+
: 'FULI Agent:分配失败(项目归属未能恢复)';
|
|
47
|
+
}
|
|
48
|
+
const labels = {
|
|
49
|
+
unavailable: '分配失败(服务或身份恢复不可用)',
|
|
50
|
+
agent_unavailable: '分配失败(指定 FLA 不可用)',
|
|
51
|
+
agent_not_found: '分配失败(指定 FLA 不存在)',
|
|
52
|
+
ambiguous_agent: '分配待确认(存在同名 FLA)',
|
|
53
|
+
agent_selection_conflict: '分配待确认(FLA 选择冲突)',
|
|
54
|
+
manual_selection: '分配待确认(需要选择负责人)'
|
|
55
|
+
};
|
|
56
|
+
return `FULI Agent:${labels[status] ?? '分配未完成(HR 尚未返回负责人)'}`;
|
|
29
57
|
}
|
|
30
58
|
|
|
31
59
|
export function employeeCollaborationReceipt(application, context, { tool, permission, sourceApplication }) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// Read-only helpers for locating a client's JSONL session log on disk.
|
|
5
|
+
|
|
6
|
+
export function listDirectory(directory) {
|
|
7
|
+
try { return readdirSync(directory); } catch { return []; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Newest-first search for a file ending in `suffix`, at most `depth` levels down.
|
|
11
|
+
export function findFile(directory, suffix, depth) {
|
|
12
|
+
for (const entry of listDirectory(directory).sort().reverse()) {
|
|
13
|
+
const path = join(directory, entry);
|
|
14
|
+
if (entry.endsWith(suffix)) return path;
|
|
15
|
+
if (depth > 0 && !entry.includes('.')) {
|
|
16
|
+
const found = findFile(path, suffix, depth - 1);
|
|
17
|
+
if (found) return found;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The first non-empty string `pick` returns for a JSON line near the start of the log.
|
|
24
|
+
export function firstField(file, pick) {
|
|
25
|
+
const handle = openSync(file, 'r');
|
|
26
|
+
try {
|
|
27
|
+
const buffer = Buffer.alloc(256 * 1024);
|
|
28
|
+
const length = readSync(handle, buffer, 0, buffer.length, 0);
|
|
29
|
+
for (const line of buffer.subarray(0, length).toString('utf8').split('\n')) {
|
|
30
|
+
try { const value = pick(JSON.parse(line)); if (typeof value === 'string' && value) return value; } catch { /* partial line */ }
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
} finally { closeSync(handle); }
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { claudeCodeWake } from './claude-code/wake.js';
|
|
2
|
+
import { codexWake } from './codex/wake.js';
|
|
3
|
+
|
|
4
|
+
// Clients that can be woken headlessly to answer another Agent, in preference order.
|
|
5
|
+
export const WAKE_ADAPTERS = Object.freeze({
|
|
6
|
+
[claudeCodeWake.id]: claudeCodeWake,
|
|
7
|
+
[codexWake.id]: codexWake,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const WAKE_CLIENTS = Object.freeze(Object.keys(WAKE_ADAPTERS));
|
|
11
|
+
|
|
12
|
+
export function wakeAdapter(client) {
|
|
13
|
+
return Object.hasOwn(WAKE_ADAPTERS, client) ? WAKE_ADAPTERS[client] : null;
|
|
14
|
+
}
|
|
@@ -12,8 +12,6 @@ General:
|
|
|
12
12
|
--version, -v
|
|
13
13
|
|
|
14
14
|
Local service:
|
|
15
|
-
roundtable serve [--port PORT] [--data-dir DIR] [--public-url HTTPS_URL]
|
|
16
|
-
roundtable worker --url URL --room ID --runtime codex|claude-code|pi|grok|a2a [--workspace DIR] [--allow-write] [--model MODEL]
|
|
17
15
|
start [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME] (checks Agent setup)
|
|
18
16
|
stop [--data-dir DIR]
|
|
19
17
|
restart [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME]
|
package/src/cli/setup-command.js
CHANGED
|
@@ -96,12 +96,9 @@ export function formatSetupResult(result) {
|
|
|
96
96
|
}
|
|
97
97
|
lines.push('Knowledge storage: Graphiti / Neo4j');
|
|
98
98
|
for (const agent of result.agents) {
|
|
99
|
-
if (agent.status === 'partial') {
|
|
100
|
-
lines.push(`${agent.label}: partially configured; retry setup to finish installation`);
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
99
|
if (agent.status !== 'connected') {
|
|
104
|
-
|
|
100
|
+
const state = agent.status === 'partial' ? 'partially configured' : 'connection failed';
|
|
101
|
+
lines.push(`${agent.label}: ${state}${agent.message ? ` (${agent.message})` : ''}; retry fuli setup`);
|
|
105
102
|
continue;
|
|
106
103
|
}
|
|
107
104
|
lines.push(agent.newTaskRequired
|
|
@@ -234,11 +234,21 @@ function describeProcessFailure(result) {
|
|
|
234
234
|
return 'unknown process error';
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
// Node refuses to spawn Windows batch files such as npm.cmd without a shell
|
|
238
|
+
// (CVE-2024-27980). Only fixed, shell-inert arguments may take that path.
|
|
239
|
+
export function batchFileSpawnOptions(command, args) {
|
|
240
|
+
if (!/\.(?:cmd|bat)$/i.test(command)) return {};
|
|
241
|
+
const unsafe = args.find((arg) => !/^[\w@./:=-]+$/.test(arg));
|
|
242
|
+
if (unsafe !== undefined) throw new TypeError(`Refusing to pass a shell-sensitive argument to ${command}`);
|
|
243
|
+
return { shell: true };
|
|
244
|
+
}
|
|
245
|
+
|
|
237
246
|
function spawnInherited(command, args, { env }) {
|
|
238
247
|
return spawnSync(command, args, {
|
|
239
248
|
env,
|
|
240
249
|
stdio: 'inherit',
|
|
241
|
-
windowsHide: true
|
|
250
|
+
windowsHide: true,
|
|
251
|
+
...batchFileSpawnOptions(command, args)
|
|
242
252
|
});
|
|
243
253
|
}
|
|
244
254
|
|
|
@@ -247,7 +257,8 @@ function spawnCaptured(command, args, { env }) {
|
|
|
247
257
|
env,
|
|
248
258
|
encoding: 'utf8',
|
|
249
259
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
250
|
-
windowsHide: true
|
|
260
|
+
windowsHide: true,
|
|
261
|
+
...batchFileSpawnOptions(command, args)
|
|
251
262
|
});
|
|
252
263
|
}
|
|
253
264
|
|
package/src/cli.js
CHANGED
|
@@ -10,7 +10,6 @@ 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';
|
|
14
13
|
import { runSetupCommand } from './cli/setup-command.js';
|
|
15
14
|
import { runUninstallCommand } from './cli/uninstall-command.js';
|
|
16
15
|
import { runUpdateCommand } from './cli/update-command.js';
|
|
@@ -42,10 +41,6 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
|
|
|
42
41
|
assertSupportedNodeVersion();
|
|
43
42
|
return runEmployeeCommand(commandArgs, { env });
|
|
44
43
|
}
|
|
45
|
-
if (command === 'roundtable') {
|
|
46
|
-
assertSupportedNodeVersion();
|
|
47
|
-
return runRoundtableCommand(commandArgs, { env });
|
|
48
|
-
}
|
|
49
44
|
if (command === 'connect-workspace') {
|
|
50
45
|
assertSupportedNodeVersion();
|
|
51
46
|
return runWorkspaceConnectionCommand(commandArgs, { env });
|
|
@@ -2,7 +2,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { DatabaseSync } from 'node:sqlite';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { EmployeeError } from './manifest.js';
|
|
5
|
+
import { EmployeeError, FIXED_EMPLOYEE_AGENT_TYPES } from './manifest.js';
|
|
6
6
|
|
|
7
7
|
const projectId = z.string().min(1).max(256);
|
|
8
8
|
export const employeeManagementSchema = z.object({
|
|
@@ -73,7 +73,9 @@ export function managementFor(manifest, agent, stored) {
|
|
|
73
73
|
}
|
|
74
74
|
// Upgrades preserve existing authorization. Only a new recruitment uses the
|
|
75
75
|
// template default; an existing employee opts in by saving the new all rule.
|
|
76
|
-
|
|
76
|
+
// A fixed role nobody has assigned yet is still in its template default.
|
|
77
|
+
const configured = agent && !(Object.hasOwn(FIXED_EMPLOYEE_AGENT_TYPES, manifest.id) && !agent.assignments?.length);
|
|
78
|
+
return { revision: 0, mode: configured ? 'selected' : manifest.defaultProjectScope ?? 'selected',
|
|
77
79
|
projectIds: [...new Set((agent?.assignments ?? []).filter(a => a.status === 'active').map(a => a.personalProjectId))],
|
|
78
80
|
excludedProjectIds: [], titleMode: manifest.taskEntry ? 'auto' : 'suggest', titleStyle: 'emoji' };
|
|
79
81
|
}
|
|
@@ -44,6 +44,9 @@ export function parseEmployeeManifest(value) {
|
|
|
44
44
|
return parsed.data;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// Built-in roles every personal space has; the Provider provisions their identities.
|
|
48
|
+
export const FIXED_EMPLOYEE_AGENT_TYPES = Object.freeze({ jefa: 'coordinator', bole: 'hr' });
|
|
49
|
+
|
|
47
50
|
export const employeeAgentId = (id) => `employee.${identifier.parse(id)}`;
|
|
48
51
|
export const employeeCapability = (id) => `fuli.employee:${identifier.parse(id)}`;
|
|
49
52
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { EmployeeError, employeeAgentId, employeeCapability } from './manifest.js';
|
|
2
|
+
import { EmployeeError, FIXED_EMPLOYEE_AGENT_TYPES, employeeAgentId, employeeCapability } from './manifest.js';
|
|
3
3
|
import {
|
|
4
4
|
activeEmployeeAssignments, employeeAssignmentsVersion,
|
|
5
5
|
parseEmployeeProjectSelection, resolveEmployeeProjectSelection,
|
|
@@ -50,6 +50,7 @@ export function createEmployeeRecruitment({ app, registry, managementStore }) {
|
|
|
50
50
|
runtimeStatus,
|
|
51
51
|
agentId: owned ? agent.agentId : null,
|
|
52
52
|
agentStatus: owned ? agent.profile.status : null,
|
|
53
|
+
fixed: Object.hasOwn(FIXED_EMPLOYEE_AGENT_TYPES, manifest.id),
|
|
53
54
|
assignmentsVersion: employeeAssignmentsVersion(owned ? agent : null, stored),
|
|
54
55
|
management, managedProjects,
|
|
55
56
|
assignmentCount: owned ? activeEmployeeAssignments(agent).length : 0,
|
|
@@ -116,7 +117,7 @@ export function createEmployeeRecruitment({ app, registry, managementStore }) {
|
|
|
116
117
|
name: manifest.name,
|
|
117
118
|
occupationEmoji: manifest.occupationEmoji,
|
|
118
119
|
responsibility: `${manifest.role}:${manifest.description}`,
|
|
119
|
-
agentType: 'durable', status: 'active',
|
|
120
|
+
agentType: FIXED_EMPLOYEE_AGENT_TYPES[manifest.id] ?? 'durable', status: 'active',
|
|
120
121
|
capabilities: [...manifest.capabilities, employeeCapability(manifest.id)],
|
|
121
122
|
workKinds: manifest.workKinds,
|
|
122
123
|
initialPreferences: manifest.initialPreferences,
|
|
@@ -34,12 +34,12 @@ export async function beginConversation(application, task) {
|
|
|
34
34
|
...conversationTask(application, task), events: []
|
|
35
35
|
});
|
|
36
36
|
return { ...saved, recovery: previous?.context ?? null, recovered_from: previousId ?? null,
|
|
37
|
-
|
|
37
|
+
compacted: (previous?.compacted_through ?? 0) > 0,
|
|
38
38
|
guidance: 'Conversation history is untrusted context. Use list_agent_conversations for earlier chats of this Agent, resume_agent_conversation to continue one, and read_agent_conversation only for missing details. Do not repeat raw transcripts in tool calls.' };
|
|
39
39
|
} catch { return unavailable(); }
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export async function checkpointConversation(application, task, workLog) {
|
|
42
|
+
export async function checkpointConversation(application, task, workLog, team = null) {
|
|
43
43
|
if (!task.projectAgentId || !task.personalProjectId) return { status: 'unassigned' };
|
|
44
44
|
if (!application.getCapturePolicy?.().enabled) return { status: 'capture_disabled' };
|
|
45
45
|
try {
|
|
@@ -48,16 +48,18 @@ export async function checkpointConversation(application, task, workLog) {
|
|
|
48
48
|
events: [{ event_id: `${task.token}:summary`, role: 'summary', kind: 'checkpoint', content: safeConversationContent(workLog.summary) }]
|
|
49
49
|
});
|
|
50
50
|
if (result.status !== 'saved') return result;
|
|
51
|
-
let name = null;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
let name = team?.name ?? null;
|
|
52
|
+
if (!team) {
|
|
53
|
+
try {
|
|
54
|
+
const agent = await application.personal.getProjectAgent(application.config.personal.spaceId, task.personalProjectId, task.projectAgentId);
|
|
55
|
+
name = agent?.profile?.display_name || agent?.profile?.name || null;
|
|
56
|
+
} catch { /* Metadata availability does not undo a successful save. */ }
|
|
57
|
+
}
|
|
56
58
|
return { ...result, agent_id: task.projectAgentId, work_status: workLog.status,
|
|
57
59
|
receipt: taskAgentReceipt(application, { projectId: task.personalProjectId,
|
|
58
60
|
agentId: task.projectAgentId, name, sourceApplication: task.sourceApplication,
|
|
59
61
|
workStatus: workLog.status, conversationId: result.conversation_id,
|
|
60
|
-
persistence: result.status }),
|
|
62
|
+
persistence: result.status, collaborators: team?.collaborators ?? [] }),
|
|
61
63
|
coverage: 'task_summary; visible transcript capture is confirmed separately by the host hook',
|
|
62
64
|
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.' };
|
|
63
65
|
} catch { return unavailable(); }
|
|
@@ -84,6 +86,6 @@ export async function resumeConversation(application, input) {
|
|
|
84
86
|
}
|
|
85
87
|
export async function updateConversationPolicy(application, input) {
|
|
86
88
|
return application.personal.conversation('policy', { ...conversationScope(application, input), policy: {
|
|
87
|
-
|
|
89
|
+
compact_after_kb: input.compactAfterKb, context_budget: input.contextBudget, enabled: input.enabled
|
|
88
90
|
} });
|
|
89
91
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { beginConversation, checkpointConversation } from './agent-conversations.js';
|
|
2
|
+
import { taskAgentReceipt } from '../agents/identity-receipt.js';
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
3
4
|
|
|
4
5
|
import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
|
|
@@ -92,7 +93,8 @@ export async function checkpointTaskKnowledge(application, {
|
|
|
92
93
|
workLog = null,
|
|
93
94
|
sourceApplication = 'other',
|
|
94
95
|
personalProjectId = null,
|
|
95
|
-
remoteSessionId = null
|
|
96
|
+
remoteSessionId = null,
|
|
97
|
+
collaborators = []
|
|
96
98
|
}) {
|
|
97
99
|
if (!['capture_candidates', 'retain_nothing'].includes(disposition)) {
|
|
98
100
|
throw validationError('Unknown task knowledge checkpoint disposition');
|
|
@@ -122,11 +124,21 @@ export async function checkpointTaskKnowledge(application, {
|
|
|
122
124
|
'Task checkpoint already has different input; resume the original review or begin a new task context'
|
|
123
125
|
);
|
|
124
126
|
}
|
|
127
|
+
const team = await taskTeam(application, task, collaborators);
|
|
128
|
+
const finish = async (result) => {
|
|
129
|
+
const effectiveLog = workLog ?? { status: 'reported', summary: reason };
|
|
130
|
+
const conversation = await checkpointConversation(application, task, effectiveLog, team);
|
|
131
|
+
return { ...result, conversation,
|
|
132
|
+
agent_receipt: taskAgentReceipt(application, { projectId: task.personalProjectId,
|
|
133
|
+
agentId: task.projectAgentId ?? null, name: team.name, sourceApplication: task.sourceApplication,
|
|
134
|
+
workStatus: effectiveLog.status, conversationId: conversation?.conversation_id ?? null,
|
|
135
|
+
persistence: conversation?.status ?? null, collaborators: team.collaborators }),
|
|
136
|
+
...(team.ignored.length ? { ignored_collaborators: team.ignored } : {}) };
|
|
137
|
+
};
|
|
125
138
|
if (task.checkpoint?.phase === 'complete') {
|
|
126
|
-
return { status: 'checkpointed', disposition, reason,
|
|
139
|
+
return finish({ status: 'checkpointed', disposition, reason,
|
|
127
140
|
personal_project_id: task.personalProjectId, project_agent_id: task.projectAgentId,
|
|
128
|
-
replayed: true, capture_status: task.checkpoint.captureStatus
|
|
129
|
-
conversation: await checkpointConversation(application, task, workLog ?? { status: 'reported', summary: reason }) };
|
|
141
|
+
replayed: true, capture_status: task.checkpoint.captureStatus });
|
|
130
142
|
}
|
|
131
143
|
const captureInput = capture ? {
|
|
132
144
|
...capture,
|
|
@@ -181,21 +193,36 @@ export async function checkpointTaskKnowledge(application, {
|
|
|
181
193
|
await application.taskContextRegistry.checkpoint(taskContextToken, {
|
|
182
194
|
...checkpoint, captureStatus: captureResult?.status ?? null
|
|
183
195
|
}, sourceApplication);
|
|
184
|
-
return {
|
|
196
|
+
return finish({
|
|
185
197
|
status: 'checkpointed',
|
|
186
198
|
disposition,
|
|
187
199
|
reason,
|
|
188
200
|
personal_project_id: task.personalProjectId,
|
|
189
201
|
project_agent_id: task.projectAgentId ?? null,
|
|
190
202
|
capture: captureResult,
|
|
191
|
-
conversation: await checkpointConversation(application, task, workLog ?? { status: 'reported', summary: reason }),
|
|
192
203
|
agent_memory: memoryResult ? {
|
|
193
204
|
status: memoryResult.status, agentId: memoryResult.agentId,
|
|
194
205
|
checkpointId: memoryResult.checkpointId, revision: memoryResult.revision,
|
|
195
206
|
sourceApplication: memoryResult.sourceApplication,
|
|
196
207
|
sourceSessionId: memoryResult.sourceSessionId, createdAt: memoryResult.createdAt
|
|
197
208
|
} : null
|
|
198
|
-
};
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Names the lead and only collaborators that are real, active Agents of this space.
|
|
213
|
+
async function taskTeam(application, task, collaboratorIds) {
|
|
214
|
+
const requested = [...new Set(collaboratorIds)].filter(id => id !== task.projectAgentId);
|
|
215
|
+
if (!task.projectAgentId) return { name: null, collaborators: [], ignored: requested };
|
|
216
|
+
let agents = [];
|
|
217
|
+
try {
|
|
218
|
+
agents = await application.listProjectAgents({ personalSpaceId: application.config.personal.spaceId });
|
|
219
|
+
} catch { /* Attribution metadata never blocks a checkpoint. */ }
|
|
220
|
+
const byId = new Map(agents.map(agent => [agent.agentId, agent]));
|
|
221
|
+
const label = agent => agent?.profile.displayName || agent?.profile.name || null;
|
|
222
|
+
const active = requested.filter(id => byId.get(id)?.profile.status === 'active');
|
|
223
|
+
return { name: label(byId.get(task.projectAgentId)),
|
|
224
|
+
collaborators: active.map(id => ({ agentId: id, name: label(byId.get(id)) })),
|
|
225
|
+
ignored: requested.filter(id => !active.includes(id)) };
|
|
199
226
|
}
|
|
200
227
|
|
|
201
228
|
function canonicalCheckpoint(value) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { dirname } from 'node:path';
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
2
|
import { createEmployeeService } from '../employees/service.js';
|
|
3
|
-
import {
|
|
3
|
+
import { createAgentRoundtable } from '../agent-roundtable/service.js';
|
|
4
|
+
import { createRoundtableStore } from '../agent-roundtable/store.js';
|
|
4
5
|
|
|
5
6
|
import { GraphitiProviderClient } from './provider-client.js';
|
|
6
7
|
import {
|
|
@@ -79,6 +80,8 @@ import {
|
|
|
79
80
|
} from './related-project-suggestions.js';
|
|
80
81
|
import { buildUserTasteSkill } from './user-taste-skill.js';
|
|
81
82
|
import { taskEntryPreferences } from './task-entry-preferences.js';
|
|
83
|
+
import { TASK_ENTRY_TOOLS, registerRepositoryProject } from './repository-project-registration.js';
|
|
84
|
+
import { resolveTaskEntryProject } from './task-entry-project-scope.js';
|
|
82
85
|
import { getWritingTasteProfile as getWritingTasteProfileWorkflow } from './writing-taste-profile-workflow.js';
|
|
83
86
|
import {
|
|
84
87
|
groupSubscriptions,
|
|
@@ -117,7 +120,8 @@ export function openFederatedGraphApplication({
|
|
|
117
120
|
fetchImpl
|
|
118
121
|
});
|
|
119
122
|
app.employees = createEmployeeService({ app, runtimeConfigPath });
|
|
120
|
-
app.
|
|
123
|
+
app.roundtable = createAgentRoundtable({ app, env,
|
|
124
|
+
openStore: () => createRoundtableStore(join(dirname(runtimeConfigPath), 'agent-roundtable.sqlite')) });
|
|
121
125
|
}
|
|
122
126
|
return app;
|
|
123
127
|
}
|
|
@@ -258,7 +262,15 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
|
|
|
258
262
|
agentInvocation = false,
|
|
259
263
|
agentToolName = 'get_collaboration_preferences'
|
|
260
264
|
} = {}) {
|
|
261
|
-
|
|
265
|
+
let projectResolution = await this.#resolvePreferenceProject({ personalProjectId, projectPath });
|
|
266
|
+
if (projectResolution.repositoryProjectId && agentInvocation && TASK_ENTRY_TOOLS.has(agentToolName)
|
|
267
|
+
&& this.getCapturePolicy().enabled) {
|
|
268
|
+
// Every task gets a FULI Agent, so a task in an unregistered repository registers it.
|
|
269
|
+
projectResolution = await registerRepositoryProject(this, projectResolution);
|
|
270
|
+
}
|
|
271
|
+
projectResolution = await resolveTaskEntryProject(this, projectResolution, {
|
|
272
|
+
sessionId, sourceSessionId, sourceApplication, agentInvocation, agentToolName
|
|
273
|
+
});
|
|
262
274
|
return taskEntryPreferences(this, projectResolution, {
|
|
263
275
|
personalProjectId, projectAgentId, projectPath, taskPrompt, sourceApplication,
|
|
264
276
|
sourceSessionId, sessionId, turnId, workKind, requiredCapabilities,
|
|
@@ -1152,7 +1164,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
|
|
|
1152
1164
|
};
|
|
1153
1165
|
}
|
|
1154
1166
|
|
|
1155
|
-
close() { this.
|
|
1167
|
+
close() { this.roundtable?.close(); return this.employees?.close(); }
|
|
1156
1168
|
|
|
1157
1169
|
async #recordAgentViews(items, toolName) {
|
|
1158
1170
|
const unique = new Map(items.map((item) => [
|
|
@@ -71,6 +71,8 @@ export function projectAgentRecord(value) {
|
|
|
71
71
|
Object.hasOwn(profile, 'display_name'));
|
|
72
72
|
copyOptional(result, 'legacyAgentIds', value.legacy_agent_ids,
|
|
73
73
|
Object.hasOwn(value, 'legacy_agent_ids'));
|
|
74
|
+
copyOptional(result, 'employeeNumber', value.employee_number,
|
|
75
|
+
Object.hasOwn(value, 'employee_number'));
|
|
74
76
|
copyOptional(result.profile, 'workKinds', profile.work_kinds,
|
|
75
77
|
Object.hasOwn(profile, 'work_kinds'));
|
|
76
78
|
copyOptional(
|
|
@@ -8,26 +8,56 @@ const ENTRY_TOOLS = new Set(['begin_task_context', 'get_collaboration_preference
|
|
|
8
8
|
|
|
9
9
|
export async function resolveTaskEntryAgent(application, resolution, input) {
|
|
10
10
|
if (!input.agentInvocation || !ENTRY_TOOLS.has(input.agentToolName)) return null;
|
|
11
|
-
if (!resolution.personalProjectId) return {
|
|
11
|
+
if (!resolution.personalProjectId) return {
|
|
12
|
+
status: resolution.status === 'unavailable' ? 'unavailable' : 'project_unresolved',
|
|
13
|
+
reason: resolution.reason ?? resolution.status,
|
|
14
|
+
worker_started: false
|
|
15
|
+
};
|
|
12
16
|
try {
|
|
13
17
|
const mention = await resolveTaskAgentMention(application, resolution.personalProjectId, input);
|
|
14
18
|
if (mention?.status) return mention;
|
|
15
19
|
const workKind = input.workKind ?? taskWorkKind(input.taskPrompt);
|
|
16
|
-
const
|
|
20
|
+
const scope = {
|
|
17
21
|
personal_space_id: application.config.personal.spaceId,
|
|
18
22
|
personal_project_id: resolution.personalProjectId,
|
|
23
|
+
source_application: input.sourceApplication ?? 'other'
|
|
24
|
+
};
|
|
25
|
+
const contextRequest = {
|
|
26
|
+
...scope,
|
|
19
27
|
agent_id: mention?.agentId ?? input.projectAgentId ?? null,
|
|
20
28
|
session_id: input.sessionId ?? null,
|
|
21
29
|
turn_id: input.turnId ?? null,
|
|
22
30
|
work_kind: workKind,
|
|
23
|
-
required_capabilities: input.requiredCapabilities ?? []
|
|
24
|
-
|
|
31
|
+
required_capabilities: input.requiredCapabilities ?? []
|
|
32
|
+
};
|
|
33
|
+
const delegation = application.roundtable?.taskEntryDelegation?.({
|
|
34
|
+
sessionId: input.receivingSessionId ?? input.sessionId
|
|
25
35
|
});
|
|
36
|
+
if (delegation) {
|
|
37
|
+
contextRequest.delegation_token = delegation.token;
|
|
38
|
+
contextRequest.delegation_session_id = delegation.sessionId;
|
|
39
|
+
}
|
|
40
|
+
let value = await application.personal.resolveProjectAgentContext(contextRequest);
|
|
41
|
+
if (value?.reason === 'project_lead_required') {
|
|
42
|
+
await application.personal.staffDefaultProjectLead(scope);
|
|
43
|
+
// Re-resolve after staffing so a requested specialist is not lost and a
|
|
44
|
+
// concurrent successful hire is handled without selecting another owner.
|
|
45
|
+
value = await application.personal.resolveProjectAgentContext(contextRequest);
|
|
46
|
+
}
|
|
26
47
|
if (!value?.status) throw new Error('Role resolution is unavailable');
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
let agent = value.agent ? projectAgentRecord(value.agent) : null;
|
|
49
|
+
const implicit = !mention?.agentId && !input.projectAgentId;
|
|
50
|
+
const peerOnly = agent && implicit && unrelatedPeer(agent, workKind, input.requiredCapabilities);
|
|
51
|
+
if (implicit && value.reason !== 'project_lead_required' && (peerOnly || value.status === 'unassigned')) {
|
|
52
|
+
// Every task gets a FULI Agent: a project without a lead has HR hire one.
|
|
53
|
+
const staffed = await application.personal.staffDefaultProjectLead(scope).catch(() => null);
|
|
54
|
+
if (staffed?.agent) {
|
|
55
|
+
value = staffed;
|
|
56
|
+
agent = projectAgentRecord(staffed.agent);
|
|
57
|
+
} else if (peerOnly) {
|
|
58
|
+
return { status: 'unassigned', reason: 'specialist_required', worker_started: false,
|
|
59
|
+
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.' };
|
|
60
|
+
}
|
|
31
61
|
}
|
|
32
62
|
const employeeId = /^employee\.([a-z][a-z0-9-]{0,63})$/.exec(agent?.agentId ?? '')?.[1];
|
|
33
63
|
if (employeeId && application.employees) {
|
|
@@ -15,7 +15,10 @@ export async function loadProjectTeamContext(application, personalSpaceId, proje
|
|
|
15
15
|
&& assignment.status === 'active')?.responsibility || agent.profile?.responsibility,
|
|
16
16
|
role: agent.agent_id === policy.team_lead_agent_id ? 'lead' : 'member' }));
|
|
17
17
|
return { lead_agent_id: policy.team_lead_agent_id, roster,
|
|
18
|
+
user_facing_agent_id: policy.team_lead_agent_id,
|
|
19
|
+
reporting_lines: roster.filter(agent => agent.role === 'member')
|
|
20
|
+
.map(agent => ({ agent_id: agent.agent_id, reports_to_agent_id: policy.team_lead_agent_id })),
|
|
18
21
|
unavailable_agent_ids: ids.filter(id => !roster.some(agent => agent.agent_id === id)),
|
|
19
22
|
peer_roles: ['hr', 'project_manager'], worker_started: false,
|
|
20
|
-
guidance: 'The
|
|
23
|
+
guidance: 'The project lead is the sole user-facing owner. Members report results and blockers to this lead; do not skip reporting levels or replace the lead when a member is named. The lead integrates and verifies member reports before reporting to the user. Split bounded work through an authorized coordination plan; report real worker evidence. HR and project managers remain peers. Team membership grants no new tool, data, executor, or publication permissions.' };
|
|
21
24
|
}
|
|
@@ -47,6 +47,8 @@ export function resolvePersonalProjectPath(projectPath, projects, {
|
|
|
47
47
|
fileExists,
|
|
48
48
|
pathApi
|
|
49
49
|
});
|
|
50
|
+
// An unregistered repository can be registered under this exact ID.
|
|
51
|
+
let repositoryProjectId = null;
|
|
50
52
|
if (repositoryRoot) {
|
|
51
53
|
const worktreeId = worktreeOriginProjectId(
|
|
52
54
|
pathApi.join(repositoryRoot, '.git'),
|
|
@@ -56,10 +58,13 @@ export function resolvePersonalProjectPath(projectPath, projects, {
|
|
|
56
58
|
// A worktree's folder name is arbitrary; only its original repository
|
|
57
59
|
// identifies the project, even when the folder names another project.
|
|
58
60
|
return resolveProjectId(worktreeId, projectIds, 'git_worktree_origin')
|
|
59
|
-
?? unresolved('unmatched');
|
|
61
|
+
?? unresolved('unmatched', safeProjectId(worktreeId, pathApi) ? { repositoryProjectId: worktreeId } : {});
|
|
60
62
|
}
|
|
61
63
|
const repository = resolveProjectId(pathApi.basename(repositoryRoot), projectIds, 'repository_root');
|
|
62
64
|
if (repository) return repository;
|
|
65
|
+
if (safeProjectId(pathApi.basename(repositoryRoot), pathApi)) {
|
|
66
|
+
repositoryProjectId = pathApi.basename(repositoryRoot);
|
|
67
|
+
}
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
const directory = resolveProjectId(pathApi.basename(currentPath), projectIds, 'directory_name');
|
|
@@ -76,7 +81,7 @@ export function resolvePersonalProjectPath(projectPath, projects, {
|
|
|
76
81
|
if (childMatches.length > 1) {
|
|
77
82
|
return unresolved('ambiguous', { candidateCount: childMatches.length });
|
|
78
83
|
}
|
|
79
|
-
return unresolved('unmatched');
|
|
84
|
+
return unresolved('unmatched', repositoryProjectId ? { repositoryProjectId } : {});
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
function resolveProjectId(candidate, projectIds, basis) {
|
|
@@ -153,6 +153,23 @@ export class GraphitiProviderClient {
|
|
|
153
153
|
resolveProjectAgentContext(input) {
|
|
154
154
|
return this.#request('/v1/project-agent-context/resolve', { method: 'POST', body: input });
|
|
155
155
|
}
|
|
156
|
+
issueAgentDelegation(input) {
|
|
157
|
+
return this.#request('/v1/project-agent-context/delegations', { method: 'POST', body: input });
|
|
158
|
+
}
|
|
159
|
+
verifyAgentDelegation(input) {
|
|
160
|
+
return this.#request('/v1/project-agent-context/delegations/verify', { method: 'POST', body: input });
|
|
161
|
+
}
|
|
162
|
+
revokeAgentDelegation(input) {
|
|
163
|
+
return this.#request('/v1/project-agent-context/delegations/revoke', { method: 'POST', body: input });
|
|
164
|
+
}
|
|
165
|
+
recentAgentSessions({ personalSpaceId, agentId, personalProjectId = null, limit = 5 }) {
|
|
166
|
+
const query = new URLSearchParams({ personal_space_id: personalSpaceId, project_agent_id: agentId, limit: String(limit) });
|
|
167
|
+
if (personalProjectId) query.set('personal_project_id', personalProjectId);
|
|
168
|
+
return this.#request(`/v1/task-context-sessions/recent?${query}`);
|
|
169
|
+
}
|
|
170
|
+
staffDefaultProjectLead(input) {
|
|
171
|
+
return this.#request('/v1/project-agent-context/default-lead', { method: 'POST', body: input });
|
|
172
|
+
}
|
|
156
173
|
beginTaskContext(input) {
|
|
157
174
|
return this.#request('/v1/task-contexts', { method: 'PUT', body: input });
|
|
158
175
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const TASK_ENTRY_TOOLS = new Set(['begin_task_context', 'get_collaboration_preferences']);
|
|
2
|
+
|
|
3
|
+
// Registers the exact repository ID the path resolver would match next time.
|
|
4
|
+
// A failed registration leaves the task unresolved rather than guessing.
|
|
5
|
+
export async function registerRepositoryProject(application, resolution) {
|
|
6
|
+
const projectId = resolution.repositoryProjectId;
|
|
7
|
+
try {
|
|
8
|
+
await application.upsertPersonalProject({
|
|
9
|
+
personalSpaceId: application.config.personal.spaceId,
|
|
10
|
+
projectId,
|
|
11
|
+
profile: { name: projectId, lifecycle: 'active' }
|
|
12
|
+
});
|
|
13
|
+
} catch {
|
|
14
|
+
return resolution;
|
|
15
|
+
}
|
|
16
|
+
return { status: 'matched', basis: 'registered_repository', personalProjectId: projectId };
|
|
17
|
+
}
|
|
@@ -7,12 +7,11 @@ export async function taskEntryPreferences(application, projectResolution, {
|
|
|
7
7
|
sourceSessionId, sessionId, turnId, workKind, requiredCapabilities,
|
|
8
8
|
limit, agentInvocation, agentToolName
|
|
9
9
|
}, recordAgentViews) {
|
|
10
|
-
const selection =
|
|
11
|
-
? null
|
|
12
|
-
: await resolveTaskEntryAgent(
|
|
10
|
+
const selection = await resolveTaskEntryAgent(
|
|
13
11
|
application, projectResolution, { projectAgentId, taskPrompt, sourceApplication,
|
|
14
12
|
sessionId: agentToolName === 'get_collaboration_preferences'
|
|
15
13
|
? sessionId ?? sourceSessionId : turnId ? sessionId : null,
|
|
14
|
+
receivingSessionId: sessionId ?? sourceSessionId,
|
|
16
15
|
turnId: agentToolName === 'begin_task_context' ? turnId : null,
|
|
17
16
|
workKind, requiredCapabilities, agentInvocation, agentToolName }
|
|
18
17
|
);
|
|
@@ -41,9 +40,23 @@ export async function taskEntryPreferences(application, projectResolution, {
|
|
|
41
40
|
sourceApplication, taskPrompt, selectionReason: selection.reason,
|
|
42
41
|
matchBasis: selection.match_basis
|
|
43
42
|
}) : selection;
|
|
43
|
+
if (context && selection?.requested_agent_id) {
|
|
44
|
+
context.requested_agent_id = selection.requested_agent_id;
|
|
45
|
+
context.requested_agent_guidance = 'The user requested this project specialist. Keep the project lead as accountable owner; coordinate a scoped workstream for the specialist and receive its report before replying. This request is not evidence that a worker ran. Do not load the specialist’s private memory into the lead context.';
|
|
46
|
+
}
|
|
47
|
+
if (context && selection?.reporting_lead_agent_id) {
|
|
48
|
+
context.reporting_lead_agent_id = selection.reporting_lead_agent_id;
|
|
49
|
+
context.reporting_guidance = 'This is a delegated member turn. Keep this member identity and private memory isolated. Return results and blockers to the project lead; the lead integrates the final user report.';
|
|
50
|
+
}
|
|
51
|
+
// Questions other Agents left for this Agent while it was not running.
|
|
52
|
+
const waiting = selection?.agent && application.roundtable
|
|
53
|
+
? application.roundtable.pending(selection.agent.agentId) : [];
|
|
44
54
|
return { ...managedPreferences, ...(context ? { project_agent_context: context } : {}),
|
|
55
|
+
...(waiting.length ? { agent_messages: waiting,
|
|
56
|
+
agent_messages_guidance: 'Other Agents are waiting for your answers. Reply to each with reply_agent_message.' } : {}),
|
|
45
57
|
agent_receipt: taskAgentReceipt(application, {
|
|
46
58
|
projectId: projectResolution.personalProjectId, agentId: selection?.agent?.agentId ?? null,
|
|
47
|
-
name: context?.role?.name, sourceApplication
|
|
59
|
+
name: context?.role?.name, sourceApplication,
|
|
60
|
+
assignmentStatus: selection?.status, assignmentReason: selection?.reason
|
|
48
61
|
}) };
|
|
49
62
|
}
|