fuli-context 0.7.6 → 0.7.8

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 (150) hide show
  1. package/README.md +69 -13
  2. package/README.zh-CN.md +60 -14
  3. package/dist/web/assets/{AboutPage-sLV9WChi.js → AboutPage-9CVX91tr.js} +1 -1
  4. package/dist/web/assets/{ConnectionsPage-BWn4ywGJ.js → ConnectionsPage-CWbCVFdv.js} +1 -1
  5. package/dist/web/assets/EmployeeRecruitDialog-CQgNpGJ_.js +1 -0
  6. package/dist/web/assets/EmployeeRecruitDialog-CfFlyKSh.css +1 -0
  7. package/dist/web/assets/EmployeeWorkbenchPage-CWg4WJhr.css +1 -0
  8. package/dist/web/assets/EmployeeWorkbenchPage-CwU4qBgD.js +1 -0
  9. package/dist/web/assets/KnowledgeEditDialog-B6TGAcFH.js +1 -0
  10. package/dist/web/assets/{KnowledgeEditDialog-DWUV_NPp.css → KnowledgeEditDialog-iv-dgUeD.css} +1 -1
  11. package/dist/web/assets/{KnowledgeInspector-CRQWOaos.js → KnowledgeInspector-M2pLY0bR.js} +1 -1
  12. package/dist/web/assets/{KnowledgeOrganizerPage-Dfj3vIpU.js → KnowledgeOrganizerPage-DzeAlk9b.js} +1 -1
  13. package/dist/web/assets/KnowledgePage-BJu9w0fF.js +1 -0
  14. package/dist/web/assets/{KnowledgeWorkspace-ChEB5Bwk.js → KnowledgeWorkspace-CCt8rnBb.js} +1 -1
  15. package/dist/web/assets/{OverviewPage-Cd66M0RT.js → OverviewPage-CZswVqui.js} +1 -1
  16. package/dist/web/assets/{PersonalProfilePage-RCFHL0Wz.js → PersonalProfilePage-f1nSF6kx.js} +1 -1
  17. package/dist/web/assets/PersonalProjectsPage-D__eJf0t.js +1 -0
  18. package/dist/web/assets/ProjectAgentsPage-C3vW6wmk.css +1 -0
  19. package/dist/web/assets/ProjectAgentsPage-LrwagMGj.js +11 -0
  20. package/dist/web/assets/{PublicProjectsPage-mstPEjHe.js → PublicProjectsPage-BdC2Qk4n.js} +1 -1
  21. package/dist/web/assets/{ReviewPage-6Y1SmfZy.js → ReviewPage-Cbgl8_Si.js} +1 -1
  22. package/dist/web/assets/SearchableSelect-ERm-k4KI.js +1 -0
  23. package/dist/web/assets/{SettingsPage--LdTMUm7.js → SettingsPage-DWso4x29.js} +1 -1
  24. package/dist/web/assets/{WritingTastePage-CIpKswvs.js → WritingTastePage-CFpCjmIY.js} +1 -1
  25. package/dist/web/assets/catalog-WGLn7ERJ.js +1 -0
  26. package/dist/web/assets/{console-BT1P2Db4.js → console-DQgu0JIA.js} +1 -1
  27. package/dist/web/assets/conversation-launcher-settings-0KtlG_LV.js +1 -0
  28. package/dist/web/assets/{i18n-js6gfc7j.js → i18n-ByRiA3pJ.js} +3 -3
  29. package/dist/web/assets/{identity-CfDfanpa.js → identity-CxdPRMwH.js} +1 -1
  30. package/dist/web/assets/index-JAZe6lfX.js +2 -0
  31. package/dist/web/assets/index-wcXzzggp.css +1 -0
  32. package/dist/web/assets/{model-DJkLAJsg.js → model-Db4-KefQ.js} +1 -1
  33. package/dist/web/assets/{useApi-CROJJdhE-BdSti7Y2.js → useApi-CROJJdhE-CePMjc08.js} +1 -1
  34. package/dist/web/assets/{vue-router-Kk67EaCP.js → vue-router-CzX-hn3P.js} +1 -1
  35. package/dist/web/index.html +7 -6
  36. package/docs/employee-agents.md +99 -0
  37. package/docs/project-agent-memory.md +479 -0
  38. package/graph-provider/fuli_graph/knowledge_audit_models.py +171 -0
  39. package/graph-provider/fuli_graph/model_base.py +5 -0
  40. package/graph-provider/fuli_graph/model_validation.py +32 -0
  41. package/graph-provider/fuli_graph/models.py +26 -170
  42. package/graph-provider/fuli_graph/project_agent_context_models.py +29 -0
  43. package/graph-provider/fuli_graph/project_agent_executor_models.py +35 -7
  44. package/graph-provider/fuli_graph/project_agent_memory_models.py +79 -0
  45. package/graph-provider/fuli_graph/project_agent_memory_routes.py +46 -0
  46. package/graph-provider/fuli_graph/project_agent_models.py +9 -0
  47. package/graph-provider/fuli_graph/project_agent_recruitment_values.py +48 -0
  48. package/graph-provider/fuli_graph/project_agent_routes.py +21 -1
  49. package/graph-provider/fuli_graph/project_agent_task_models.py +217 -29
  50. package/graph-provider/fuli_graph/runtime.py +37 -1
  51. package/graph-provider/fuli_graph/store.py +6 -0
  52. package/graph-provider/fuli_graph/store_project_agent_context.py +65 -0
  53. package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +175 -11
  54. package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +33 -10
  55. package/graph-provider/fuli_graph/store_project_agent_executors.py +181 -14
  56. package/graph-provider/fuli_graph/store_project_agent_memory.py +174 -0
  57. package/graph-provider/fuli_graph/store_project_agent_task_activity.py +423 -0
  58. package/graph-provider/fuli_graph/store_project_agent_task_persistence.py +152 -0
  59. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +187 -21
  60. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +748 -162
  61. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +230 -18
  62. package/graph-provider/fuli_graph/store_project_agent_tasks.py +261 -468
  63. package/graph-provider/fuli_graph/store_project_agents.py +214 -46
  64. package/graph-provider/fuli_graph/store_task_contexts.py +217 -0
  65. package/graph-provider/fuli_graph/store_transactions.py +40 -0
  66. package/graph-provider/fuli_graph/task_context_models.py +50 -0
  67. package/graph-provider/fuli_graph/task_context_routes.py +31 -0
  68. package/graph-provider/fuli_graph/workflow_observation_models.py +1 -1
  69. package/graph-provider/pyproject.toml +1 -1
  70. package/npm-shrinkwrap.json +8 -8
  71. package/package.json +5 -2
  72. package/skills/capturing-session-knowledge/SKILL.md +18 -6
  73. package/src/adaptive-runtime/executor-pool.js +46 -5
  74. package/src/adaptive-runtime/graph-services.js +10 -3
  75. package/src/adaptive-runtime/lease-client.js +66 -14
  76. package/src/adaptive-runtime/runtime-broker.js +24 -9
  77. package/src/agent-tools/employee-definitions.js +43 -0
  78. package/src/agent-tools/graph-definitions.js +25 -6
  79. package/src/agent-tools/graph-handlers.js +11 -0
  80. package/src/agent-tools/project-agent-definitions.js +35 -4
  81. package/src/agent-tools/project-agent-memory-definitions.js +36 -0
  82. package/src/agent-tools.js +6 -2
  83. package/src/agents/codex/lifecycle-hook.js +107 -0
  84. package/src/agents/codex/lifecycle-hooks.js +231 -0
  85. package/src/agents/cursor/lifecycle-hook.js +133 -0
  86. package/src/agents/cursor/lifecycle-hooks.js +50 -0
  87. package/src/agents/lifecycle-message.js +19 -0
  88. package/src/app/agent-request-context.js +23 -0
  89. package/src/cli/command-registry.js +2 -0
  90. package/src/cli/employee-command.js +17 -0
  91. package/src/cli/remote-mcp-command.js +282 -0
  92. package/src/cli/setup-command.js +5 -0
  93. package/src/cli/update-command.js +6 -43
  94. package/src/cli.js +10 -0
  95. package/src/employees/catalog/jefa.json +21 -0
  96. package/src/employees/install-package.js +77 -0
  97. package/src/employees/management-policy.js +88 -0
  98. package/src/employees/manifest.js +53 -0
  99. package/src/employees/package-registry.js +107 -0
  100. package/src/employees/project-selection.js +50 -0
  101. package/src/employees/recruitment.js +199 -0
  102. package/src/employees/service.js +83 -0
  103. package/src/employees/task-entry.js +39 -0
  104. package/src/graphiti/agent-knowledge-workflows.js +135 -25
  105. package/src/graphiti/federated-application.js +78 -35
  106. package/src/graphiti/project-agent-control-plane.js +20 -3
  107. package/src/graphiti/project-agent-mapping.js +155 -5
  108. package/src/graphiti/project-agent-memory.js +115 -0
  109. package/src/graphiti/project-agent-task-entry.js +126 -0
  110. package/src/graphiti/project-agent-workflows.js +30 -14
  111. package/src/graphiti/provider-client.js +66 -4
  112. package/src/http/api-router.js +30 -5
  113. package/src/http/employee-api-router.js +64 -0
  114. package/src/http/error-mapping.js +4 -0
  115. package/src/http/graph-api-router.js +2 -0
  116. package/src/http/request-policy.js +6 -3
  117. package/src/http/static-handler.js +4 -3
  118. package/src/mcp/create-mcp-server.js +98 -37
  119. package/src/mcp/direct-call.js +38 -0
  120. package/src/mcp/instructions.js +8 -7
  121. package/src/mcp/provider-task-context-registry.js +74 -0
  122. package/src/mcp/remote-http-server.js +761 -0
  123. package/src/mcp/session-id.js +29 -3
  124. package/src/mcp/task-context-registry.js +88 -29
  125. package/src/mcp/tool-annotations.js +10 -2
  126. package/src/mcp/tool-result.js +8 -3
  127. package/src/mcp-server.js +20 -8
  128. package/src/native-runtime/runtime.js +58 -8
  129. package/src/semantic-version.js +42 -0
  130. package/src/server.js +76 -28
  131. package/src/setup/agent-installation-status.js +38 -6
  132. package/src/setup/agents.js +20 -8
  133. package/src/setup/claude-code-config.js +55 -13
  134. package/src/setup/codex-bootstrap.js +11 -10
  135. package/src/setup/codex-config.js +74 -7
  136. package/src/setup/config-backup.js +18 -2
  137. package/src/setup/cursor-config.js +22 -5
  138. package/src/setup/setup.js +30 -22
  139. package/src/storage/json-file.js +11 -3
  140. package/src/system/package-version-checker.js +72 -0
  141. package/src/system/system-service.js +3 -0
  142. package/dist/web/assets/KnowledgeEditDialog-CZ5ziPrD.js +0 -1
  143. package/dist/web/assets/KnowledgePage-BRYC9S5Y.js +0 -1
  144. package/dist/web/assets/PersonalProjectsPage-DEh8XWby.js +0 -1
  145. package/dist/web/assets/ProjectAgentsPage-Crh7PrXa.css +0 -1
  146. package/dist/web/assets/ProjectAgentsPage-DPnaH-TR.js +0 -11
  147. package/dist/web/assets/SearchableSelect-D3HM8YF8.js +0 -1
  148. package/dist/web/assets/conversation-launcher-settings-DrJEotdf.js +0 -1
  149. package/dist/web/assets/index-BnH5mcZx.css +0 -1
  150. package/dist/web/assets/index-GqJf1xqg.js +0 -2
@@ -1,5 +1,8 @@
1
1
  import { createHash } from 'node:crypto';
2
2
 
3
+ import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
4
+ import { agentMemoryRecord, prepareProjectAgentMemory } from './project-agent-memory.js';
5
+
3
6
  import { onlineSourceUri } from './source-uri.js';
4
7
  import {
5
8
  relatedProjectGuidance,
@@ -8,28 +11,58 @@ import {
8
11
 
9
12
  export async function beginTaskContext(application, {
10
13
  sessionId,
14
+ turnId = null,
11
15
  projectPath,
12
- taskPrompt = null
16
+ personalProjectId = null,
17
+ taskPrompt = null,
18
+ sourceApplication = 'other',
19
+ sourceSessionId = null,
20
+ projectAgentId = null,
21
+ workKind = null,
22
+ requiredCapabilities = []
13
23
  }) {
24
+ const continuationToken = /^FULI_CHECKPOINT_REQUIRED: (fuli-task-[a-zA-Z0-9-]{8,128})\b/.exec(taskPrompt ?? '')?.[1];
25
+ const resumed = continuationToken
26
+ ? await application.taskContextRegistry.context(continuationToken, sourceApplication) : null;
27
+ if (resumed && resumed.sessionId !== sessionId) {
28
+ throw validationError('Checkpoint continuation belongs to another host session');
29
+ }
14
30
  const preferences = await application.getCollaborationPreferences({
31
+ personalProjectId,
15
32
  projectPath,
33
+ sessionId,
34
+ turnId,
16
35
  taskPrompt,
36
+ projectAgentId: resumed?.projectAgentId ?? projectAgentId,
37
+ sourceApplication,
38
+ sourceSessionId,
39
+ workKind,
40
+ requiredCapabilities,
17
41
  agentInvocation: true,
18
42
  agentToolName: 'begin_task_context'
19
43
  });
20
- const task = application.taskContextRegistry.begin({
44
+ if (resumed && resumed.personalProjectId !== preferences.context.personal_project_id) {
45
+ throw validationError('Checkpoint continuation belongs to another project');
46
+ }
47
+ const task = resumed ?? await application.taskContextRegistry.begin({
21
48
  sessionId,
22
- personalProjectId: preferences.context.personal_project_id
49
+ turnId,
50
+ personalProjectId: preferences.context.personal_project_id,
51
+ projectAgentId: preferences.context.project_agent_id,
52
+ sourceApplication,
53
+ sourceSessionId,
54
+ memoryRevision: preferences.project_agent_context?.memory?.revision ?? null
23
55
  });
24
56
  return {
25
57
  taskContextToken: task.token,
26
58
  task_context_token: task.token,
27
- checkpoint_required: true,
59
+ checkpoint_required: task.checkpoint?.phase !== 'complete',
60
+ resumed_checkpoint: Boolean(resumed),
28
61
  previous_checkpoint_missing: task.previousCheckpointMissing,
29
62
  ...preferences,
30
63
  task_guidance: {
31
64
  retrieval: 'Inspect task_knowledge_recall before asking for a stable project fact or method again. On a miss, use search_current_project_knowledge with focused action, artifact, target-system, or identifier queries; never use the full conversational request as the only query.',
32
- checkpoint: 'Before finishing, call checkpoint_task_knowledge with capture_candidates or retain_nothing.'
65
+ checkpoint: 'Before finishing, call checkpoint_task_knowledge with capture_candidates or retain_nothing. When durable role context changed, include agentMemory with the loaded revision and a bounded merged summary, decisions, open threads and next actions. Do not overwrite from truncated context or store raw transcripts.'
33
66
  }
34
67
  };
35
68
  }
@@ -38,43 +71,120 @@ export async function checkpointTaskKnowledge(application, {
38
71
  taskContextToken,
39
72
  disposition,
40
73
  reason,
41
- capture = null
74
+ capture = null,
75
+ agentMemory = null,
76
+ sourceApplication = 'other',
77
+ personalProjectId = null,
78
+ remoteSessionId = null
42
79
  }) {
43
80
  if (!['capture_candidates', 'retain_nothing'].includes(disposition)) {
44
- throw new TypeError('Unknown task knowledge checkpoint disposition');
81
+ throw validationError('Unknown task knowledge checkpoint disposition');
82
+ }
83
+ const task = await application.taskContextRegistry.context(taskContextToken, sourceApplication);
84
+ if (personalProjectId && task.personalProjectId !== personalProjectId) {
85
+ throw validationError('Task checkpoint belongs to another project');
86
+ }
87
+ if (remoteSessionId && task.sessionId !== remoteSessionId) {
88
+ throw validationError('Task checkpoint belongs to another remote session');
45
89
  }
46
- const task = application.taskContextRegistry.context(taskContextToken);
47
-
48
- let captureResult = null;
49
90
  if (disposition === 'capture_candidates') {
50
91
  if (!capture) {
51
- throw new TypeError('capture_candidates requires a bounded capture payload');
92
+ throw validationError('capture_candidates requires a bounded capture payload');
93
+ }
94
+ } else if (capture) {
95
+ throw validationError('retain_nothing cannot include a capture payload');
96
+ }
97
+ const fingerprint = createHash('sha256').update(JSON.stringify(
98
+ canonicalCheckpoint({ disposition, reason, capture, agentMemory })
99
+ )).digest('hex');
100
+ if (task.checkpoint && task.checkpoint.fingerprint !== fingerprint) {
101
+ throw validationError(
102
+ 'Task checkpoint already has different input; resume the original review or begin a new task context'
103
+ );
104
+ }
105
+ if (task.checkpoint?.phase === 'complete') {
106
+ return { status: 'checkpointed', disposition, reason,
107
+ personal_project_id: task.personalProjectId, project_agent_id: task.projectAgentId,
108
+ replayed: true, capture_status: task.checkpoint.captureStatus };
109
+ }
110
+ const captureInput = capture ? {
111
+ ...capture,
112
+ targetKind: 'personal',
113
+ spaceId: application.config.personal.spaceId,
114
+ personalProjectId: task.personalProjectId,
115
+ projectAgentId: task.projectAgentId ?? null,
116
+ sessionId: task.sessionId,
117
+ sourceApplication,
118
+ idempotencyKey: `${task.token}:knowledge`
119
+ } : null;
120
+ // Local validation has no side effects. Reject correctable input before
121
+ // binding an immutable checkpoint or advancing the Agent's memory revision.
122
+ if (captureInput) application.validateCaptureSessionKnowledge(captureInput);
123
+ let memoryResult = null;
124
+ let memoryRequest = null;
125
+ if (agentMemory) {
126
+ if (!task.projectAgentId || !task.personalProjectId) {
127
+ throw validationError('Task has no durable project Agent; do not guess a memory target');
52
128
  }
53
- captureResult = await application.captureSessionKnowledge({
54
- targetKind: 'personal',
55
- spaceId: application.config.personal.spaceId,
129
+ const prepared = await prepareProjectAgentMemory(application, {
130
+ personalSpaceId: application.config.personal.spaceId,
56
131
  personalProjectId: task.personalProjectId,
57
- sessionId: task.sessionId,
58
- ...capture
132
+ agentId: task.projectAgentId,
133
+ expectedRevision: agentMemory.expectedRevision,
134
+ idempotencyKey: `${task.token}:memory`,
135
+ memory: agentMemory.memory, sourceApplication,
136
+ // The logical client session survives independent lifecycle-hook MCP
137
+ // processes; their host session ids do not. The Provider persists this
138
+ // stable task session as the memory checkpoint provenance.
139
+ sourceSessionId: task.sessionId
59
140
  });
60
- } else if (capture) {
61
- throw new TypeError('retain_nothing cannot include a capture payload');
141
+ if (prepared.status === 'capture_disabled') memoryResult = prepared;
142
+ else memoryRequest = { expected_revision: prepared.request.expected_revision,
143
+ memory: prepared.request.memory };
62
144
  }
63
-
64
- application.taskContextRegistry.checkpoint(taskContextToken, {
65
- disposition,
66
- reason,
67
- captureStatus: captureResult?.status ?? null
68
- });
145
+ const checkpoint = { disposition, reason, fingerprint, captureStatus: null };
146
+ const preparedTask = await application.taskContextRegistry.prepare(
147
+ taskContextToken, checkpoint, sourceApplication, memoryRequest
148
+ );
149
+ if (memoryRequest) {
150
+ if (!preparedTask.agentMemory) {
151
+ throw new TypeError('Provider did not confirm atomic Agent memory preparation');
152
+ }
153
+ memoryResult = { status: 'checkpointed', ...agentMemoryRecord(preparedTask.agentMemory) };
154
+ }
155
+ const captureResult = captureInput
156
+ ? await application.captureSessionKnowledge(captureInput) : null;
157
+ await application.taskContextRegistry.checkpoint(taskContextToken, {
158
+ ...checkpoint, captureStatus: captureResult?.status ?? null
159
+ }, sourceApplication);
69
160
  return {
70
161
  status: 'checkpointed',
71
162
  disposition,
72
163
  reason,
73
164
  personal_project_id: task.personalProjectId,
74
- capture: captureResult
165
+ project_agent_id: task.projectAgentId ?? null,
166
+ capture: captureResult,
167
+ agent_memory: memoryResult ? {
168
+ status: memoryResult.status, agentId: memoryResult.agentId,
169
+ checkpointId: memoryResult.checkpointId, revision: memoryResult.revision,
170
+ sourceApplication: memoryResult.sourceApplication,
171
+ sourceSessionId: memoryResult.sourceSessionId, createdAt: memoryResult.createdAt
172
+ } : null
75
173
  };
76
174
  }
77
175
 
176
+ function canonicalCheckpoint(value) {
177
+ if (Array.isArray(value)) return value.map(canonicalCheckpoint);
178
+ if (!value || typeof value !== 'object') return value;
179
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [
180
+ key, canonicalCheckpoint(value[key])
181
+ ]));
182
+ }
183
+
184
+ function validationError(message) {
185
+ return new ApplicationError(ApplicationErrorCode.VALIDATION, message);
186
+ }
187
+
78
188
  export async function recordDecisionTrace(application, input) {
79
189
  const trace = decisionTraceKnowledge(input);
80
190
  return application.captureSessionKnowledge({
@@ -1,4 +1,5 @@
1
1
  import { dirname } from 'node:path';
2
+ import { createEmployeeService } from '../employees/service.js';
2
3
 
3
4
  import { GraphitiProviderClient } from './provider-client.js';
4
5
  import {
@@ -30,7 +31,7 @@ import {
30
31
  } from './source-marker.js';
31
32
  import { resolvePersonalProjectPath } from './project-path-context.js';
32
33
  import { mergeExternalKnowledgeProjection } from '../external-knowledge/graph-projection.js';
33
- import { TaskContextRegistry } from '../mcp/task-context-registry.js';
34
+ import { ProviderTaskContextRegistry } from '../mcp/provider-task-context-registry.js';
34
35
  import { attachExternalKnowledgeRuntime } from '../external-knowledge/runtime.js';
35
36
  import { resolveSetupPaths } from '../setup/paths.js';
36
37
  import {
@@ -79,6 +80,7 @@ import {
79
80
  getCollaborationPreferences as getCollaborationPreferencesWorkflow
80
81
  } from './collaboration-preference-workflow.js';
81
82
  import { buildUserTasteSkill } from './user-taste-skill.js';
83
+ import { resolveTaskEntryAgent, loadProjectAgentContinuity } from './project-agent-task-entry.js';
82
84
  import { getWritingTasteProfile as getWritingTasteProfileWorkflow } from './writing-taste-profile-workflow.js';
83
85
  import {
84
86
  groupSubscriptions,
@@ -114,6 +116,7 @@ export function openFederatedGraphApplication({
114
116
  env,
115
117
  fetchImpl
116
118
  });
119
+ app.employees = createEmployeeService({ app, runtimeConfigPath });
117
120
  }
118
121
  return app;
119
122
  }
@@ -137,7 +140,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
137
140
  agentAccessPolicyStore = new AgentAccessPolicyStore(),
138
141
  consoleUrl = sourceConsoleUrl(null),
139
142
  projectPathResolver = resolvePersonalProjectPath,
140
- taskContextRegistry = new TaskContextRegistry(),
143
+ taskContextRegistry = null,
141
144
  providerRequestTimeoutMs = undefined
142
145
  } = {}) {
143
146
  super();
@@ -147,7 +150,6 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
147
150
  this.agentAccessPolicyStore = agentAccessPolicyStore;
148
151
  this.consoleUrl = consoleUrl;
149
152
  this.projectPathResolver = projectPathResolver;
150
- this.taskContextRegistry = taskContextRegistry;
151
153
  this.personal = new GraphitiProviderClient({
152
154
  baseUrl: config.personal.providerUrl,
153
155
  accessToken: config.personal.accessToken,
@@ -155,6 +157,9 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
155
157
  fetchImpl,
156
158
  requestTimeoutMs: providerRequestTimeoutMs
157
159
  });
160
+ this.taskContextRegistry = taskContextRegistry ?? new ProviderTaskContextRegistry(
161
+ this.personal, config.personal.spaceId
162
+ );
158
163
  this.workspaces = new Map(config.workspaces.map((workspace) => {
159
164
  const provider = createWorkspaceProvider(workspace, {
160
165
  fetchImpl, requestTimeoutMs: providerRequestTimeoutMs
@@ -171,8 +176,8 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
171
176
  return checkpointTaskKnowledgeWorkflow(this, input);
172
177
  }
173
178
 
174
- verifyTaskCheckpoint({ sessionId }) {
175
- return this.taskContextRegistry.verify(sessionId);
179
+ verifyTaskCheckpoint({ sessionId, sourceApplication = 'other' }) {
180
+ return this.taskContextRegistry.verify(sessionId, sourceApplication);
176
181
  }
177
182
 
178
183
  async recordDecisionTrace(input) {
@@ -180,6 +185,10 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
180
185
  return recordDecisionTraceWorkflow(this, input);
181
186
  }
182
187
 
188
+ validateCaptureSessionKnowledge(input) {
189
+ if (this.getCapturePolicy().enabled) this.#prepareKnowledgeCapture(input);
190
+ }
191
+
183
192
  async captureSessionKnowledge(input) {
184
193
  const capturePolicy = this.getCapturePolicy();
185
194
  if (!capturePolicy.enabled) {
@@ -189,15 +198,8 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
189
198
  capturePolicy
190
199
  };
191
200
  }
192
- assertNoCredentials(input);
193
- const episode = providerEpisode(input);
201
+ const { episode, workspace } = this.#prepareKnowledgeCapture(input);
194
202
  if (input.targetKind === 'personal') {
195
- if (input.spaceId !== this.config.personal.spaceId) {
196
- throw new ApplicationError(
197
- ApplicationErrorCode.VALIDATION,
198
- 'Personal capture target must be the active personal space'
199
- );
200
- }
201
203
  const result = await this.personal.commit({
202
204
  space_id: input.spaceId,
203
205
  personal_project_id: input.personalProjectId ?? null,
@@ -206,20 +208,6 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
206
208
  });
207
209
  return { route: 'personal', ...result };
208
210
  }
209
- if (input.projectAgentId) {
210
- throw new ApplicationError(
211
- ApplicationErrorCode.VALIDATION,
212
- 'Project Agent knowledge can only be captured in the personal graph'
213
- );
214
- }
215
- if (episode.sensitivity !== 'normal') {
216
- throw new ApplicationError(
217
- ApplicationErrorCode.VALIDATION,
218
- 'Private or restricted knowledge cannot enter a team-shared project queue. Use targetKind "personal" with personalProjectId to keep it in the personal graph'
219
- );
220
- }
221
- assertPublicKnowledgeEligible(episode);
222
- const workspace = this.#workspace(input.providerUrl);
223
211
  const draft = await this.personal.createPublicationDraft({
224
212
  personal_space_id: this.config.personal.spaceId,
225
213
  target_project_id: input.spaceId,
@@ -257,19 +245,33 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
257
245
  projectAgentId = null,
258
246
  projectPath = null,
259
247
  taskPrompt = null,
248
+ sourceApplication = 'other',
249
+ sourceSessionId = null,
250
+ sessionId = null,
251
+ turnId = null,
252
+ workKind = null,
253
+ requiredCapabilities = [],
260
254
  limit = 100,
261
255
  agentInvocation = false,
262
256
  agentToolName = 'get_collaboration_preferences'
263
257
  } = {}) {
264
- const projectResolution = await this.#resolvePreferenceProject({
265
- personalProjectId,
266
- projectPath
267
- });
268
- return getCollaborationPreferencesWorkflow(
258
+ 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(
269
271
  this,
270
272
  projectResolution,
271
273
  {
272
- projectAgentId,
274
+ projectAgentId: selectedAgentId,
273
275
  taskPrompt,
274
276
  limit,
275
277
  agentInvocation,
@@ -277,6 +279,19 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
277
279
  },
278
280
  (items, toolName) => this.#recordAgentViews(items, toolName)
279
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 };
280
295
  }
281
296
 
282
297
  async getUserTasteSkill({
@@ -518,7 +533,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
518
533
 
519
534
  async searchCurrentProjectKnowledge(input) {
520
535
  const resolution = await this.#resolvePreferenceProject({
521
- personalProjectId: null,
536
+ personalProjectId: input.personalProjectId ?? null,
522
537
  projectPath: input.projectPath
523
538
  });
524
539
  return searchCurrentProjectKnowledgeWorkflow(this, resolution, input);
@@ -1164,7 +1179,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
1164
1179
  };
1165
1180
  }
1166
1181
 
1167
- close() {}
1182
+ close() { return this.employees?.close(); }
1168
1183
 
1169
1184
  async #recordAgentViews(items, toolName) {
1170
1185
  const unique = new Map(items.map((item) => [
@@ -1195,6 +1210,34 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
1195
1210
  return this.#resolvePreferenceProject(input);
1196
1211
  }
1197
1212
 
1213
+ #prepareKnowledgeCapture(input) {
1214
+ assertNoCredentials(input);
1215
+ const episode = providerEpisode(input);
1216
+ if (input.targetKind === 'personal') {
1217
+ if (input.spaceId !== this.config.personal.spaceId) {
1218
+ throw new ApplicationError(
1219
+ ApplicationErrorCode.VALIDATION,
1220
+ 'Personal capture target must be the active personal space'
1221
+ );
1222
+ }
1223
+ return { episode };
1224
+ }
1225
+ if (input.projectAgentId) {
1226
+ throw new ApplicationError(
1227
+ ApplicationErrorCode.VALIDATION,
1228
+ 'Project Agent knowledge can only be captured in the personal graph'
1229
+ );
1230
+ }
1231
+ if (episode.sensitivity !== 'normal') {
1232
+ throw new ApplicationError(
1233
+ ApplicationErrorCode.VALIDATION,
1234
+ 'Private or restricted knowledge cannot enter a team-shared project queue. Use targetKind "personal" with personalProjectId to keep it in the personal graph'
1235
+ );
1236
+ }
1237
+ assertPublicKnowledgeEligible(episode);
1238
+ return { episode, workspace: this.#workspace(input.providerUrl) };
1239
+ }
1240
+
1198
1241
  #workspace(providerUrl) {
1199
1242
  const requested = providerUrl
1200
1243
  ? canonicalProviderUrl(providerUrl)
@@ -1,4 +1,8 @@
1
1
  import { agentProjectResolution } from './agent-knowledge-workflows.js';
2
+ import {
3
+ getProjectAgentMemory,
4
+ checkpointProjectAgentMemory
5
+ } from './project-agent-memory.js';
2
6
  import {
3
7
  authorizeExecutor as authorizeExecutorWorkflow,
4
8
  cleanupProjectAgentTestRoles as cleanupProjectAgentTestRolesWorkflow,
@@ -48,6 +52,16 @@ export const projectAgentControlPlaneHooks = Object.freeze({
48
52
  });
49
53
 
50
54
  export class ProjectAgentControlPlaneApplication {
55
+ async getProjectAgentMemory(input) {
56
+ this.#assertSpace(input.personalSpaceId);
57
+ return getProjectAgentMemory(this, input);
58
+ }
59
+
60
+ async checkpointProjectAgentMemory(input) {
61
+ this.#assertSpace(input.personalSpaceId);
62
+ return checkpointProjectAgentMemory(this, input);
63
+ }
64
+
51
65
  async upsertProjectAgent(input) {
52
66
  this.#assertSpace(input.personalSpaceId);
53
67
  return upsertProjectAgentWorkflow(this, input);
@@ -252,7 +266,10 @@ export class ProjectAgentControlPlaneApplication {
252
266
  }
253
267
 
254
268
  async getProjectAgentContext(input) {
255
- const resolution = await this.#resolveProject(input.projectPath);
269
+ const resolution = await this.#resolveProject(
270
+ input.projectPath,
271
+ input.personalProjectId ?? null
272
+ );
256
273
  return getProjectAgentContextWorkflow(
257
274
  this,
258
275
  agentProjectResolution(resolution),
@@ -266,9 +283,9 @@ export class ProjectAgentControlPlaneApplication {
266
283
  );
267
284
  }
268
285
 
269
- #resolveProject(projectPath) {
286
+ #resolveProject(projectPath, personalProjectId = null) {
270
287
  return this[projectAgentControlPlaneHooks.resolvePreferenceProject]({
271
- personalProjectId: null,
288
+ personalProjectId,
272
289
  projectPath
273
290
  });
274
291
  }