fuli-context 0.7.6 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -13
- package/README.zh-CN.md +60 -14
- package/dist/web/assets/{AboutPage-sLV9WChi.js → AboutPage-BcZeFcHe.js} +1 -1
- package/dist/web/assets/{ConnectionsPage-BWn4ywGJ.js → ConnectionsPage-xAQYl1SQ.js} +1 -1
- package/dist/web/assets/EmployeeRecruitDialog-CbPAmhy7.js +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-CfFlyKSh.css +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-BQvTLoc8.css +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-DF3EL0Ui.js +1 -0
- package/dist/web/assets/KnowledgeEditDialog-UnnBDt0I.js +1 -0
- package/dist/web/assets/{KnowledgeEditDialog-DWUV_NPp.css → KnowledgeEditDialog-iv-dgUeD.css} +1 -1
- package/dist/web/assets/{KnowledgeInspector-CRQWOaos.js → KnowledgeInspector-BqPJH5Rr.js} +1 -1
- package/dist/web/assets/{KnowledgeOrganizerPage-Dfj3vIpU.js → KnowledgeOrganizerPage-CyvRflXM.js} +1 -1
- package/dist/web/assets/KnowledgePage-59eczTqe.js +1 -0
- package/dist/web/assets/{KnowledgeWorkspace-ChEB5Bwk.js → KnowledgeWorkspace-CfIjhOog.js} +1 -1
- package/dist/web/assets/{OverviewPage-Cd66M0RT.js → OverviewPage-Xyfm5xkZ.js} +1 -1
- package/dist/web/assets/{PersonalProfilePage-RCFHL0Wz.js → PersonalProfilePage-DB9mByB3.js} +1 -1
- package/dist/web/assets/PersonalProjectsPage-_-YXVi5O.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-C3vW6wmk.css +1 -0
- package/dist/web/assets/ProjectAgentsPage-DeeQByPX.js +11 -0
- package/dist/web/assets/{PublicProjectsPage-mstPEjHe.js → PublicProjectsPage-BEZ1YPs3.js} +1 -1
- package/dist/web/assets/{ReviewPage-6Y1SmfZy.js → ReviewPage-CQYGwFBH.js} +1 -1
- package/dist/web/assets/SearchableSelect-D27H0MoP.js +1 -0
- package/dist/web/assets/{SettingsPage--LdTMUm7.js → SettingsPage-BlEZ03Yi.js} +1 -1
- package/dist/web/assets/{WritingTastePage-CIpKswvs.js → WritingTastePage-DvRt_nmP.js} +1 -1
- package/dist/web/assets/catalog-DFvIaHVS.js +1 -0
- package/dist/web/assets/{console-BT1P2Db4.js → console-tOxOl8Vq.js} +1 -1
- package/dist/web/assets/conversation-launcher-settings-DuFSpPAo.js +1 -0
- package/dist/web/assets/{i18n-js6gfc7j.js → i18n-CgBHZthI.js} +3 -3
- package/dist/web/assets/{identity-CfDfanpa.js → identity-CFo_Uw1H.js} +1 -1
- package/dist/web/assets/index-CJc5ppXH.css +1 -0
- package/dist/web/assets/index-yCi3cz2l.js +2 -0
- package/dist/web/assets/{model-DJkLAJsg.js → model-BtnVr7fX.js} +1 -1
- package/dist/web/assets/{useApi-CROJJdhE-BdSti7Y2.js → useApi-CROJJdhE-B6MHm_Av.js} +1 -1
- package/dist/web/assets/{vue-router-Kk67EaCP.js → vue-router-CzHm-y-x.js} +1 -1
- package/dist/web/index.html +7 -6
- package/docs/employee-agents.md +99 -0
- package/docs/project-agent-memory.md +479 -0
- package/graph-provider/fuli_graph/knowledge_audit_models.py +171 -0
- package/graph-provider/fuli_graph/model_base.py +5 -0
- package/graph-provider/fuli_graph/model_validation.py +32 -0
- package/graph-provider/fuli_graph/models.py +26 -170
- package/graph-provider/fuli_graph/project_agent_context_models.py +29 -0
- package/graph-provider/fuli_graph/project_agent_executor_models.py +35 -7
- package/graph-provider/fuli_graph/project_agent_memory_models.py +79 -0
- package/graph-provider/fuli_graph/project_agent_memory_routes.py +46 -0
- package/graph-provider/fuli_graph/project_agent_models.py +9 -0
- package/graph-provider/fuli_graph/project_agent_recruitment_values.py +48 -0
- package/graph-provider/fuli_graph/project_agent_routes.py +21 -1
- package/graph-provider/fuli_graph/project_agent_task_models.py +217 -29
- package/graph-provider/fuli_graph/runtime.py +37 -1
- package/graph-provider/fuli_graph/store.py +6 -0
- package/graph-provider/fuli_graph/store_project_agent_context.py +65 -0
- package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +175 -11
- package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +33 -10
- package/graph-provider/fuli_graph/store_project_agent_executors.py +181 -14
- package/graph-provider/fuli_graph/store_project_agent_memory.py +174 -0
- package/graph-provider/fuli_graph/store_project_agent_task_activity.py +423 -0
- package/graph-provider/fuli_graph/store_project_agent_task_persistence.py +152 -0
- package/graph-provider/fuli_graph/store_project_agent_task_reads.py +187 -21
- package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +748 -162
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +230 -18
- package/graph-provider/fuli_graph/store_project_agent_tasks.py +261 -468
- package/graph-provider/fuli_graph/store_project_agents.py +214 -46
- package/graph-provider/fuli_graph/store_task_contexts.py +217 -0
- package/graph-provider/fuli_graph/store_transactions.py +40 -0
- package/graph-provider/fuli_graph/task_context_models.py +50 -0
- package/graph-provider/fuli_graph/task_context_routes.py +31 -0
- package/graph-provider/fuli_graph/workflow_observation_models.py +1 -1
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +8 -8
- package/package.json +5 -2
- package/skills/capturing-session-knowledge/SKILL.md +18 -6
- package/src/adaptive-runtime/executor-pool.js +46 -5
- package/src/adaptive-runtime/graph-services.js +10 -3
- package/src/adaptive-runtime/lease-client.js +66 -14
- package/src/adaptive-runtime/runtime-broker.js +24 -9
- package/src/agent-tools/employee-definitions.js +43 -0
- package/src/agent-tools/graph-definitions.js +25 -6
- package/src/agent-tools/graph-handlers.js +11 -0
- package/src/agent-tools/project-agent-definitions.js +35 -4
- package/src/agent-tools/project-agent-memory-definitions.js +36 -0
- package/src/agent-tools.js +6 -2
- package/src/agents/codex/lifecycle-hook.js +107 -0
- package/src/agents/codex/lifecycle-hooks.js +231 -0
- package/src/agents/cursor/lifecycle-hook.js +133 -0
- package/src/agents/cursor/lifecycle-hooks.js +50 -0
- package/src/agents/lifecycle-message.js +19 -0
- package/src/app/agent-request-context.js +23 -0
- package/src/cli/command-registry.js +2 -0
- package/src/cli/employee-command.js +17 -0
- package/src/cli/remote-mcp-command.js +282 -0
- package/src/cli/setup-command.js +5 -0
- package/src/cli.js +10 -0
- package/src/employees/catalog/jefa.json +21 -0
- package/src/employees/install-package.js +77 -0
- package/src/employees/management-policy.js +88 -0
- package/src/employees/manifest.js +53 -0
- package/src/employees/package-registry.js +107 -0
- package/src/employees/project-selection.js +50 -0
- package/src/employees/recruitment.js +199 -0
- package/src/employees/service.js +83 -0
- package/src/employees/task-entry.js +39 -0
- package/src/graphiti/agent-knowledge-workflows.js +135 -25
- package/src/graphiti/federated-application.js +78 -35
- package/src/graphiti/project-agent-control-plane.js +20 -3
- package/src/graphiti/project-agent-mapping.js +155 -5
- package/src/graphiti/project-agent-memory.js +115 -0
- package/src/graphiti/project-agent-task-entry.js +126 -0
- package/src/graphiti/project-agent-workflows.js +30 -14
- package/src/graphiti/provider-client.js +66 -4
- package/src/http/api-router.js +25 -5
- package/src/http/employee-api-router.js +64 -0
- package/src/http/error-mapping.js +4 -0
- package/src/http/graph-api-router.js +2 -0
- package/src/http/request-policy.js +6 -3
- package/src/http/static-handler.js +4 -3
- package/src/mcp/create-mcp-server.js +98 -37
- package/src/mcp/direct-call.js +38 -0
- package/src/mcp/instructions.js +8 -7
- package/src/mcp/provider-task-context-registry.js +74 -0
- package/src/mcp/remote-http-server.js +761 -0
- package/src/mcp/session-id.js +29 -3
- package/src/mcp/task-context-registry.js +88 -29
- package/src/mcp/tool-annotations.js +10 -2
- package/src/mcp/tool-result.js +8 -3
- package/src/mcp-server.js +20 -8
- package/src/native-runtime/runtime.js +58 -8
- package/src/server.js +76 -28
- package/src/setup/agent-installation-status.js +38 -6
- package/src/setup/agents.js +20 -8
- package/src/setup/claude-code-config.js +55 -13
- package/src/setup/codex-bootstrap.js +11 -10
- package/src/setup/codex-config.js +74 -7
- package/src/setup/config-backup.js +18 -2
- package/src/setup/cursor-config.js +22 -5
- package/src/setup/setup.js +30 -22
- package/src/storage/json-file.js +11 -3
- package/dist/web/assets/KnowledgeEditDialog-CZ5ziPrD.js +0 -1
- package/dist/web/assets/KnowledgePage-BRYC9S5Y.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-DEh8XWby.js +0 -1
- package/dist/web/assets/ProjectAgentsPage-Crh7PrXa.css +0 -1
- package/dist/web/assets/ProjectAgentsPage-DPnaH-TR.js +0 -11
- package/dist/web/assets/SearchableSelect-D3HM8YF8.js +0 -1
- package/dist/web/assets/conversation-launcher-settings-DrJEotdf.js +0 -1
- package/dist/web/assets/index-BnH5mcZx.css +0 -1
- package/dist/web/assets/index-GqJf1xqg.js +0 -2
package/src/mcp/session-id.js
CHANGED
|
@@ -3,11 +3,20 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
4
|
const WORKFLOW_OBSERVATION_TOOL = 'record_workflow_transition_observation';
|
|
5
5
|
const PROJECT_AGENT_SOURCE_TOOLS = new Set([
|
|
6
|
+
'call_employee_tool',
|
|
7
|
+
'begin_task_context',
|
|
8
|
+
'verify_task_checkpoint',
|
|
9
|
+
'checkpoint_task_knowledge',
|
|
10
|
+
'get_collaboration_preferences',
|
|
11
|
+
'capture_session_knowledge',
|
|
12
|
+
'record_decision_trace',
|
|
6
13
|
'create_project_agent_assignment',
|
|
7
14
|
'replace_project_agent_assignment',
|
|
8
15
|
'coordinate_project_agent_task',
|
|
9
16
|
'submit_project_agent_task',
|
|
10
17
|
'get_project_agent_context',
|
|
18
|
+
'get_project_agent_memory',
|
|
19
|
+
'checkpoint_project_agent_memory',
|
|
11
20
|
'record_project_agent_task_activity',
|
|
12
21
|
'preflight_executor',
|
|
13
22
|
'report_executor_health',
|
|
@@ -15,6 +24,7 @@ const PROJECT_AGENT_SOURCE_TOOLS = new Set([
|
|
|
15
24
|
]);
|
|
16
25
|
|
|
17
26
|
const SESSION_ID_TOOLS = new Set([
|
|
27
|
+
'get_collaboration_preferences',
|
|
18
28
|
'begin_task_context',
|
|
19
29
|
'verify_task_checkpoint',
|
|
20
30
|
'capture_session_knowledge',
|
|
@@ -51,14 +61,26 @@ export function normalizeAgentSessionInput(
|
|
|
51
61
|
}
|
|
52
62
|
const {
|
|
53
63
|
sessionId: _sessionId,
|
|
64
|
+
session_id: _session_id,
|
|
54
65
|
idempotencyKey: _idempotencyKey,
|
|
66
|
+
idempotency_key: _idempotency_key,
|
|
55
67
|
observedAt: _observedAt,
|
|
68
|
+
observed_at: _observed_at,
|
|
56
69
|
hostSessionId: _hostSessionId,
|
|
70
|
+
host_session_id: _host_session_id,
|
|
57
71
|
hostObservedAt: _hostObservedAt,
|
|
72
|
+
host_observed_at: _host_observed_at,
|
|
73
|
+
sourceApplication: _sourceApplication,
|
|
74
|
+
source_application: _source_application,
|
|
75
|
+
sourceSessionId: _sourceSessionId,
|
|
76
|
+
source_session_id: _source_session_id,
|
|
77
|
+
sourceSessionVerified: _sourceSessionVerified,
|
|
78
|
+
source_session_verified: _source_session_verified,
|
|
58
79
|
...safeInput
|
|
59
80
|
} = input ?? {};
|
|
60
81
|
return {
|
|
61
82
|
...safeInput,
|
|
83
|
+
sourceApplication: normalizeMcpSourceApplication(sourceApplication),
|
|
62
84
|
hostSessionId,
|
|
63
85
|
hostObservedAt: clock().toISOString()
|
|
64
86
|
};
|
|
@@ -73,24 +95,28 @@ export function normalizeAgentSessionInput(
|
|
|
73
95
|
source_application: _source_application,
|
|
74
96
|
sourceSessionId: _sourceSessionId,
|
|
75
97
|
source_session_id: _source_session_id,
|
|
98
|
+
sourceSessionVerified: _sourceSessionVerified,
|
|
99
|
+
source_session_verified: _source_session_verified,
|
|
76
100
|
...safeInput
|
|
77
101
|
} = input ?? {};
|
|
78
102
|
return {
|
|
79
103
|
...safeInput,
|
|
104
|
+
...(SESSION_ID_TOOLS.has(name) && nativeThreadId ? { sessionId: nativeThreadId } : {}),
|
|
80
105
|
sourceApplication: safeSourceApplication,
|
|
81
|
-
sourceSessionId: hostSessionId
|
|
106
|
+
sourceSessionId: hostSessionId,
|
|
107
|
+
...(name === 'call_employee_tool' ? { sourceSessionVerified: Boolean(nativeThreadId && nativeThreadId === hostSessionId) } : {}),
|
|
82
108
|
};
|
|
83
109
|
}
|
|
84
110
|
if (!nativeThreadId || !SESSION_ID_TOOLS.has(name)) return input;
|
|
85
111
|
if (!input || typeof input !== 'object' || Array.isArray(input)) return input;
|
|
86
|
-
if (typeof input.sessionId !== 'string' || input.sessionId
|
|
112
|
+
if (typeof input.sessionId !== 'string' || input.sessionId === nativeThreadId) {
|
|
87
113
|
return input;
|
|
88
114
|
}
|
|
89
115
|
return { ...input, sessionId: nativeThreadId };
|
|
90
116
|
}
|
|
91
117
|
|
|
92
118
|
export function normalizeMcpSourceApplication(value) {
|
|
93
|
-
if (!['codex', 'claude_code', 'cursor', 'kiro', 'other'].includes(value)) {
|
|
119
|
+
if (!['codex', 'claude', 'claude_code', 'cursor', 'kiro', 'other'].includes(value)) {
|
|
94
120
|
throw new TypeError('Unsupported MCP source application');
|
|
95
121
|
}
|
|
96
122
|
return value;
|
|
@@ -18,59 +18,91 @@ export class TaskContextRegistry {
|
|
|
18
18
|
this.tokens = new Map();
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
begin({ sessionId, personalProjectId
|
|
21
|
+
begin({ sessionId, turnId = null, personalProjectId, projectAgentId = null,
|
|
22
|
+
sourceApplication = 'other', sourceSessionId = null, memoryRevision = null }) {
|
|
22
23
|
assertNonEmpty(sessionId, 'sessionId');
|
|
23
|
-
const
|
|
24
|
+
const key = sessionKey(sourceApplication, sessionId);
|
|
25
|
+
const previous = this.sessions.get(key) ?? null;
|
|
26
|
+
if (!previous) this.#reserveSessionSlot();
|
|
24
27
|
if (previous) this.tokens.delete(previous.token);
|
|
25
28
|
|
|
26
29
|
const task = {
|
|
27
30
|
token: this.tokenFactory(),
|
|
28
31
|
sessionId,
|
|
32
|
+
turnId,
|
|
29
33
|
personalProjectId: personalProjectId ?? null,
|
|
34
|
+
projectAgentId,
|
|
35
|
+
sourceApplication,
|
|
36
|
+
sourceSessionId,
|
|
37
|
+
memoryRevision,
|
|
38
|
+
agentMemory: null,
|
|
30
39
|
checkpoint: null
|
|
31
40
|
};
|
|
32
|
-
this.sessions.delete(
|
|
33
|
-
this.sessions.set(
|
|
41
|
+
this.sessions.delete(key);
|
|
42
|
+
this.sessions.set(key, task);
|
|
34
43
|
this.tokens.set(task.token, task);
|
|
35
|
-
this.#evictOldest();
|
|
36
44
|
return {
|
|
37
|
-
...task,
|
|
38
|
-
previousCheckpointMissing: Boolean(previous && !previous.checkpoint)
|
|
45
|
+
...snapshot(task),
|
|
46
|
+
previousCheckpointMissing: Boolean(previous && (!previous.checkpoint || previous.checkpoint.phase === 'prepare'))
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
checkpoint(token, checkpoint) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
checkpoint(token, checkpoint, sourceApplication = 'other') {
|
|
51
|
+
const task = this.#task(token, sourceApplication);
|
|
52
|
+
if (task.checkpoint?.phase === 'prepare') {
|
|
53
|
+
if (!sameCheckpointClaim(task.checkpoint, checkpoint)) {
|
|
54
|
+
throw checkpointConflict('Task checkpoint has different input');
|
|
55
|
+
}
|
|
56
|
+
} else if (task.checkpoint) {
|
|
57
|
+
if (sameCheckpoint(task.checkpoint, checkpoint)) return snapshot(task);
|
|
58
|
+
throw checkpointConflict(
|
|
59
|
+
'Fuli task context has already been checkpointed with a different disposition'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
task.checkpoint = Object.freeze({ ...checkpoint, phase: 'complete' });
|
|
63
|
+
return snapshot(task);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
prepare(token, checkpoint, sourceApplication = 'other', agentMemory = null) {
|
|
67
|
+
const task = this.#task(token, sourceApplication);
|
|
68
|
+
if (agentMemory !== null && agentMemory !== undefined) {
|
|
48
69
|
throw new ApplicationError(
|
|
49
70
|
ApplicationErrorCode.VALIDATION,
|
|
50
|
-
'
|
|
71
|
+
'The in-memory task context registry cannot atomically persist durable Agent memory'
|
|
51
72
|
);
|
|
52
73
|
}
|
|
53
|
-
task.checkpoint
|
|
54
|
-
|
|
74
|
+
if (task.checkpoint && !sameCheckpointClaim(task.checkpoint, checkpoint)) {
|
|
75
|
+
throw checkpointConflict('Task checkpoint has different input');
|
|
76
|
+
}
|
|
77
|
+
if (!task.checkpoint) task.checkpoint = { ...checkpoint, phase: 'prepare' };
|
|
78
|
+
return snapshot(task);
|
|
55
79
|
}
|
|
56
80
|
|
|
57
|
-
context(token) {
|
|
81
|
+
context(token, sourceApplication = 'other') {
|
|
82
|
+
return snapshot(this.#task(token, sourceApplication));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#task(token, sourceApplication) {
|
|
58
86
|
assertNonEmpty(token, 'taskContextToken');
|
|
59
87
|
const task = this.tokens.get(token);
|
|
60
|
-
if (!task) throw unknownTokenError();
|
|
88
|
+
if (!task || task.sourceApplication !== sourceApplication) throw unknownTokenError();
|
|
89
|
+
const key = sessionKey(task.sourceApplication, task.sessionId);
|
|
90
|
+
if (this.sessions.get(key) !== task) throw unknownTokenError();
|
|
91
|
+
this.sessions.delete(key);
|
|
92
|
+
this.sessions.set(key, task);
|
|
61
93
|
return task;
|
|
62
94
|
}
|
|
63
95
|
|
|
64
|
-
verify(sessionId) {
|
|
96
|
+
verify(sessionId, sourceApplication = 'other') {
|
|
65
97
|
assertNonEmpty(sessionId, 'sessionId');
|
|
66
|
-
const task = this.sessions.get(sessionId);
|
|
98
|
+
const task = this.sessions.get(sessionKey(sourceApplication, sessionId));
|
|
67
99
|
if (!task) {
|
|
68
100
|
return {
|
|
69
101
|
status: 'not_started',
|
|
70
102
|
guidance: 'No Fuli task context was started; do not block the Agent.'
|
|
71
103
|
};
|
|
72
104
|
}
|
|
73
|
-
if (task.checkpoint) {
|
|
105
|
+
if (task.checkpoint && task.checkpoint.phase !== 'prepare') {
|
|
74
106
|
return {
|
|
75
107
|
status: 'checkpointed',
|
|
76
108
|
disposition: task.checkpoint.disposition
|
|
@@ -79,27 +111,54 @@ export class TaskContextRegistry {
|
|
|
79
111
|
return {
|
|
80
112
|
status: 'checkpoint_required',
|
|
81
113
|
decision: 'block',
|
|
82
|
-
|
|
114
|
+
task_context_token: task.token,
|
|
115
|
+
reason: `FULI_CHECKPOINT_REQUIRED: ${task.token} Before finishing, call checkpoint_task_knowledge once with either a bounded durable candidate batch or retain_nothing. Do not store raw transcripts, guesses, or temporary output.`
|
|
83
116
|
};
|
|
84
117
|
}
|
|
85
118
|
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
119
|
+
#reserveSessionSlot() {
|
|
120
|
+
if (this.sessions.size < MAX_ACTIVE_SESSIONS) return;
|
|
121
|
+
for (const [key, task] of this.sessions) {
|
|
122
|
+
if (task.checkpoint?.phase !== 'complete') continue;
|
|
123
|
+
this.sessions.delete(key);
|
|
90
124
|
this.tokens.delete(task.token);
|
|
125
|
+
return;
|
|
91
126
|
}
|
|
127
|
+
throw new ApplicationError(
|
|
128
|
+
ApplicationErrorCode.VALIDATION,
|
|
129
|
+
'Fuli has too many unfinished in-memory task contexts; finish an existing context or restart this fallback MCP process'
|
|
130
|
+
);
|
|
92
131
|
}
|
|
93
132
|
}
|
|
94
133
|
|
|
134
|
+
function snapshot(task) {
|
|
135
|
+
return structuredClone(task);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sessionKey(sourceApplication, sessionId) {
|
|
139
|
+
return JSON.stringify([sourceApplication, sessionId]);
|
|
140
|
+
}
|
|
141
|
+
|
|
95
142
|
function assertNonEmpty(value, label) {
|
|
96
143
|
if (typeof value !== 'string' || !value.trim()) {
|
|
97
|
-
throw new
|
|
144
|
+
throw new ApplicationError(
|
|
145
|
+
ApplicationErrorCode.VALIDATION,
|
|
146
|
+
`${label} must be a nonempty string`
|
|
147
|
+
);
|
|
98
148
|
}
|
|
99
149
|
}
|
|
100
150
|
|
|
101
151
|
function sameCheckpoint(left, right) {
|
|
102
|
-
return left
|
|
103
|
-
&& left.reason === right.reason
|
|
152
|
+
return sameCheckpointClaim(left, right)
|
|
104
153
|
&& left.captureStatus === right.captureStatus;
|
|
105
154
|
}
|
|
155
|
+
|
|
156
|
+
function sameCheckpointClaim(left, right) {
|
|
157
|
+
return left.fingerprint === right.fingerprint
|
|
158
|
+
&& left.disposition === right.disposition
|
|
159
|
+
&& left.reason === right.reason;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function checkpointConflict(message) {
|
|
163
|
+
return new ApplicationError(ApplicationErrorCode.VALIDATION, message);
|
|
164
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const READ_TOOLS = new Set([
|
|
2
|
+
'list_employee_templates',
|
|
3
|
+
'list_employee_tools',
|
|
2
4
|
'verify_task_checkpoint',
|
|
3
5
|
'search_current_project_knowledge',
|
|
4
6
|
'discover_common_knowledge_candidates',
|
|
@@ -11,6 +13,7 @@ const READ_TOOLS = new Set([
|
|
|
11
13
|
'get_project_agent',
|
|
12
14
|
'list_project_agent_assignments',
|
|
13
15
|
'get_project_agent_context',
|
|
16
|
+
'get_project_agent_memory',
|
|
14
17
|
'view_project_agent_task',
|
|
15
18
|
'view_project_agent_activity',
|
|
16
19
|
'get_project_agent_coordination_policy',
|
|
@@ -33,8 +36,11 @@ const READ_TOOLS = new Set([
|
|
|
33
36
|
]);
|
|
34
37
|
|
|
35
38
|
const WRITE_TOOLS = new Set([
|
|
39
|
+
'recruit_employee',
|
|
40
|
+
'call_employee_tool',
|
|
36
41
|
'begin_task_context',
|
|
37
42
|
'checkpoint_task_knowledge',
|
|
43
|
+
'checkpoint_project_agent_memory',
|
|
38
44
|
'get_collaboration_preferences',
|
|
39
45
|
'get_user_taste_skill',
|
|
40
46
|
'resolve_deferred_preference_conflict',
|
|
@@ -93,6 +99,7 @@ const WRITE_TOOLS = new Set([
|
|
|
93
99
|
]);
|
|
94
100
|
|
|
95
101
|
const DESTRUCTIVE_TOOLS = new Set([
|
|
102
|
+
'recruit_employee',
|
|
96
103
|
'resolve_deferred_preference_conflict',
|
|
97
104
|
'apply_common_knowledge_promotion',
|
|
98
105
|
'review_project_proposal',
|
|
@@ -109,7 +116,7 @@ export function annotationsFor(name) {
|
|
|
109
116
|
return {
|
|
110
117
|
readOnlyHint: readOnly,
|
|
111
118
|
destructiveHint: DESTRUCTIVE_TOOLS.has(name),
|
|
112
|
-
idempotentHint: readOnly || name === 'upsert_personal_project' ||
|
|
119
|
+
idempotentHint: readOnly || name === 'recruit_employee' || name === 'upsert_personal_project' ||
|
|
113
120
|
name === 'upsert_project_agent' ||
|
|
114
121
|
name === 'delete_project_agent' ||
|
|
115
122
|
name === 'cleanup_test_project_agents' ||
|
|
@@ -131,7 +138,8 @@ export function annotationsFor(name) {
|
|
|
131
138
|
name === 'record_knowledge_usage' || name === 'record_decision_trace' ||
|
|
132
139
|
name === 'record_knowledge_feedback' ||
|
|
133
140
|
name === 'record_workflow_transition_observation' ||
|
|
134
|
-
name === 'checkpoint_task_knowledge'
|
|
141
|
+
name === 'checkpoint_task_knowledge' ||
|
|
142
|
+
name === 'checkpoint_project_agent_memory',
|
|
135
143
|
openWorldHint: name === 'capture_session_knowledge' ||
|
|
136
144
|
name === 'coordinate_project_agent_task' ||
|
|
137
145
|
name === 'submit_project_agent_task' ||
|
package/src/mcp/tool-result.js
CHANGED
|
@@ -100,9 +100,11 @@ export function applicationErrorMessage(error) {
|
|
|
100
100
|
return boundedMessage(String(error.message));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
export function protocolErrorResult(message) {
|
|
103
|
+
export function protocolErrorResult(message, validationErrors = []) {
|
|
104
104
|
if (message.includes('Input validation error')) {
|
|
105
|
-
|
|
105
|
+
const error = new ApplicationError('validation', 'Invalid tool arguments');
|
|
106
|
+
error.validationErrors = validationErrors;
|
|
107
|
+
return errorToolResult(error);
|
|
106
108
|
}
|
|
107
109
|
return errorToolResult(new Error('Protocol tool failure'));
|
|
108
110
|
}
|
|
@@ -117,8 +119,10 @@ function sanitize(value, state, seen = new WeakSet(), depth = 0) {
|
|
|
117
119
|
seen.add(value);
|
|
118
120
|
if (Array.isArray(value)) {
|
|
119
121
|
if (value.length > RESULT_ITEM_LIMIT) state.truncated = true;
|
|
120
|
-
|
|
122
|
+
const result = value.slice(0, RESULT_ITEM_LIMIT)
|
|
121
123
|
.map((item) => sanitize(item, state, seen, depth + 1));
|
|
124
|
+
seen.delete(value);
|
|
125
|
+
return result;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
const sanitized = {};
|
|
@@ -132,6 +136,7 @@ function sanitize(value, state, seen = new WeakSet(), depth = 0) {
|
|
|
132
136
|
sanitized[key] = sanitize(item, state, seen, depth + 1);
|
|
133
137
|
included += 1;
|
|
134
138
|
}
|
|
139
|
+
seen.delete(value);
|
|
135
140
|
return sanitized;
|
|
136
141
|
}
|
|
137
142
|
|
package/src/mcp-server.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveGraphRuntimeOptions
|
|
12
12
|
} from './graphiti/runtime-config.js';
|
|
13
13
|
import { runStdio } from './mcp/runtime.js';
|
|
14
|
+
import { prepareDirectAgentToolCall } from './mcp/direct-call.js';
|
|
14
15
|
import { normalizeMcpSourceApplication } from './mcp/session-id.js';
|
|
15
16
|
import { applicationErrorMessage } from './mcp/tool-result.js';
|
|
16
17
|
|
|
@@ -30,13 +31,19 @@ async function main(args) {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const { runtimeConfigPath } = resolveGraphRuntimeOptions(args, process.env);
|
|
33
|
-
if (args.includes('--call')) {
|
|
34
|
-
await runCall({ args, runtimeConfigPath });
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
34
|
const configuredSourceApplication = args.includes('--source-application')
|
|
38
35
|
? requiredOption(args, '--source-application')
|
|
39
36
|
: null;
|
|
37
|
+
if (args.includes('--call')) {
|
|
38
|
+
await runCall({
|
|
39
|
+
args,
|
|
40
|
+
runtimeConfigPath,
|
|
41
|
+
sourceApplication: configuredSourceApplication
|
|
42
|
+
? normalizeMcpSourceApplication(configuredSourceApplication)
|
|
43
|
+
: 'other'
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
40
47
|
await runStdio({
|
|
41
48
|
runtimeConfigPath,
|
|
42
49
|
sourceApplication: configuredSourceApplication
|
|
@@ -45,15 +52,20 @@ async function main(args) {
|
|
|
45
52
|
});
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
async function runCall({ args, runtimeConfigPath }) {
|
|
55
|
+
async function runCall({ args, runtimeConfigPath, sourceApplication }) {
|
|
56
|
+
const toolName = requiredOption(args, '--call');
|
|
57
|
+
const prepared = await prepareDirectAgentToolCall({
|
|
58
|
+
toolName,
|
|
59
|
+
input: parseInput(args),
|
|
60
|
+
sourceApplication
|
|
61
|
+
});
|
|
49
62
|
const app = openFederatedGraphApplication({ runtimeConfigPath });
|
|
50
63
|
const leaseClient = createRuntimeLeaseClient({ runtimeConfigPath });
|
|
51
64
|
bindRuntimeLeaseAgentTools(app, leaseClient);
|
|
52
65
|
try {
|
|
53
|
-
const toolName = requiredOption(args, '--call');
|
|
54
66
|
const result = await leaseClient.withGraphLease(
|
|
55
67
|
`cli-call:${toolName}`,
|
|
56
|
-
() => callAgentTool(app, toolName,
|
|
68
|
+
() => callAgentTool(app, toolName, prepared.input)
|
|
57
69
|
);
|
|
58
70
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
59
71
|
} finally {
|
|
@@ -86,7 +98,7 @@ function startupMessage(error) {
|
|
|
86
98
|
|
|
87
99
|
function helpText() {
|
|
88
100
|
return 'Usage: node src/mcp-server.js [--runtime-config <graph-runtime.json>] ' +
|
|
89
|
-
'[--source-application <codex|claude_code|cursor|kiro|other>]\n' +
|
|
101
|
+
'[--source-application <codex|claude|claude_code|cursor|kiro|other>]\n' +
|
|
90
102
|
' node src/mcp-server.js --tools\n' +
|
|
91
103
|
' node src/mcp-server.js --runtime-config <graph-runtime.json> ' +
|
|
92
104
|
'--call <tool> [--input <json>]\n';
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from 'node:fs';
|
|
14
14
|
import {
|
|
15
15
|
mkdir,
|
|
16
|
+
readdir,
|
|
16
17
|
readFile,
|
|
17
18
|
rename,
|
|
18
19
|
rm,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
import { Readable } from 'node:stream';
|
|
23
24
|
import { pipeline } from 'node:stream/promises';
|
|
24
25
|
import { createConnection } from 'node:net';
|
|
25
|
-
import { dirname, join } from 'node:path';
|
|
26
|
+
import { dirname, join, relative } from 'node:path';
|
|
26
27
|
|
|
27
28
|
import { FULI_VERSION } from '../package-metadata.js';
|
|
28
29
|
import { readJsonFile, writeJsonFileAtomic } from '../storage/json-file.js';
|
|
@@ -284,12 +285,18 @@ export async function ensureNativeRuntime(input, dependencies = {}) {
|
|
|
284
285
|
}
|
|
285
286
|
|
|
286
287
|
const providerPython = join(input.paths.nativeProviderVenvPath, 'bin', 'python');
|
|
288
|
+
const providerSource = join(dirname(input.paths.graphComposePath), 'graph-provider');
|
|
289
|
+
const providerFingerprint = await deps.providerSourceFingerprint(providerSource);
|
|
290
|
+
if (!/^[a-f0-9]{64}$/.test(providerFingerprint)) {
|
|
291
|
+
throw new Error('Native Graph Provider source fingerprint is invalid.');
|
|
292
|
+
}
|
|
287
293
|
const expected = {
|
|
288
294
|
version: MANIFEST_VERSION,
|
|
289
295
|
status: 'ready',
|
|
290
296
|
mode: 'native',
|
|
291
297
|
neo4jVersion: NATIVE_NEO4J_VERSION,
|
|
292
298
|
providerVersion: `${FULI_VERSION}+native.${PROVIDER_RUNTIME_REVISION}`,
|
|
299
|
+
providerFingerprint,
|
|
293
300
|
javaHome,
|
|
294
301
|
uvCommand,
|
|
295
302
|
neo4jHome: input.paths.nativeNeo4jHome,
|
|
@@ -307,14 +314,17 @@ export async function ensureNativeRuntime(input, dependencies = {}) {
|
|
|
307
314
|
sha256: NATIVE_NEO4J_SHA256
|
|
308
315
|
});
|
|
309
316
|
}
|
|
310
|
-
if (
|
|
317
|
+
if (
|
|
318
|
+
!deps.pathExists(providerPython) ||
|
|
319
|
+
saved?.providerVersion !== expected.providerVersion ||
|
|
320
|
+
saved?.providerFingerprint !== expected.providerFingerprint
|
|
321
|
+
) {
|
|
311
322
|
deps.onProgress(input, 'Installing the native Graph Provider…');
|
|
312
323
|
await deps.installProvider({
|
|
313
324
|
uvCommand,
|
|
314
325
|
venvPath: input.paths.nativeProviderVenvPath,
|
|
315
326
|
providerPython,
|
|
316
|
-
providerSource
|
|
317
|
-
packageRoot: dirname(input.paths.graphComposePath)
|
|
327
|
+
providerSource
|
|
318
328
|
});
|
|
319
329
|
}
|
|
320
330
|
if (!neo4jReady(input.paths, deps.pathExists) || !deps.pathExists(providerPython)) {
|
|
@@ -356,6 +366,7 @@ function manifestReady(saved, expected, paths, pathExists) {
|
|
|
356
366
|
saved.mode === expected.mode &&
|
|
357
367
|
saved.neo4jVersion === expected.neo4jVersion &&
|
|
358
368
|
saved.providerVersion === expected.providerVersion &&
|
|
369
|
+
saved.providerFingerprint === expected.providerFingerprint &&
|
|
359
370
|
saved.javaHome === expected.javaHome &&
|
|
360
371
|
saved.uvCommand === expected.uvCommand &&
|
|
361
372
|
saved.neo4jHome === expected.neo4jHome &&
|
|
@@ -385,6 +396,7 @@ function nativeDependencies(overrides) {
|
|
|
385
396
|
resolveUvCommand,
|
|
386
397
|
installNeo4j,
|
|
387
398
|
installProvider,
|
|
399
|
+
providerSourceFingerprint,
|
|
388
400
|
onProgress: (input, message) => input.onProgress?.(message),
|
|
389
401
|
...overrides
|
|
390
402
|
};
|
|
@@ -431,15 +443,53 @@ async function installNeo4j({ paths, home, url, sha256 }) {
|
|
|
431
443
|
await runCommand('tar', ['-xzf', archive, '-C', paths.nativeRuntimeDir]);
|
|
432
444
|
}
|
|
433
445
|
|
|
434
|
-
async function installProvider({ uvCommand, venvPath, providerPython,
|
|
446
|
+
async function installProvider({ uvCommand, venvPath, providerPython, providerSource }) {
|
|
435
447
|
await mkdir(dirname(venvPath), { recursive: true });
|
|
436
448
|
if (!existsSync(providerPython)) {
|
|
437
449
|
await runCommand(uvCommand, ['venv', '--python', '3.12', '--seed', venvPath]);
|
|
438
450
|
}
|
|
439
|
-
await runCommand(uvCommand,
|
|
451
|
+
await runCommand(uvCommand, nativeProviderInstallArguments({
|
|
452
|
+
providerPython,
|
|
453
|
+
providerSource
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function nativeProviderInstallArguments({ providerPython, providerSource }) {
|
|
458
|
+
return [
|
|
440
459
|
'pip', 'install', '--python', providerPython, '--upgrade',
|
|
441
|
-
|
|
442
|
-
|
|
460
|
+
'--reinstall-package', 'fuli-graph-provider',
|
|
461
|
+
providerSource
|
|
462
|
+
];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async function providerSourceFingerprint(providerSource) {
|
|
466
|
+
const files = [
|
|
467
|
+
join(providerSource, 'pyproject.toml'),
|
|
468
|
+
...await pythonSourceFiles(join(providerSource, 'fuli_graph'))
|
|
469
|
+
].sort();
|
|
470
|
+
const hash = createHash('sha256');
|
|
471
|
+
for (const path of files) {
|
|
472
|
+
const name = relative(providerSource, path).replaceAll('\\', '/');
|
|
473
|
+
hash.update(name);
|
|
474
|
+
hash.update('\0');
|
|
475
|
+
hash.update(await readFile(path));
|
|
476
|
+
hash.update('\0');
|
|
477
|
+
}
|
|
478
|
+
return hash.digest('hex');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function pythonSourceFiles(directory) {
|
|
482
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
483
|
+
const files = [];
|
|
484
|
+
for (const entry of entries) {
|
|
485
|
+
const path = join(directory, entry.name);
|
|
486
|
+
if (entry.isDirectory()) {
|
|
487
|
+
if (entry.name !== '__pycache__') files.push(...await pythonSourceFiles(path));
|
|
488
|
+
} else if (entry.isFile() && entry.name.endsWith('.py')) {
|
|
489
|
+
files.push(path);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return files;
|
|
443
493
|
}
|
|
444
494
|
|
|
445
495
|
async function downloadFile(url, destination) {
|