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