fuli-context 0.12.0 → 0.12.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 +6 -5
- package/README.zh-CN.md +5 -4
- package/dist/web/assets/{AboutPage-BAD0iPVX.js → AboutPage-Dized8Uz.js} +1 -1
- package/dist/web/assets/{AgentConversations-1iGTd4sz.js → AgentConversations-Ccy41mEA.js} +1 -1
- package/dist/web/assets/{AgentHand-D_RafKOM.js → AgentHand-UzVhbKUC.js} +1 -1
- package/dist/web/assets/{AgentName-B5LKQ09q.js → AgentName-DJpvkub2.js} +1 -1
- package/dist/web/assets/AgentProfilePage-CFViQ8kj.js +1 -0
- package/dist/web/assets/AgentsDirectoryPage-CBFTg_IE.js +1 -0
- package/dist/web/assets/AgentsDirectoryPage-Cv-vxw2C.css +1 -0
- package/dist/web/assets/{ConnectionsPage-Tr2rRVOZ.js → ConnectionsPage-uXcabab_.js} +1 -1
- package/dist/web/assets/EmployeeRecruitDialog-D8Hvhc-M.js +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-TBl_HNj6.js +3 -0
- package/dist/web/assets/KnowledgeEditDialog-HmiqLC-Y.js +1 -0
- package/dist/web/assets/KnowledgeInspector-Cj2yK4Qh.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-DT3BROZm.js +1 -0
- package/dist/web/assets/KnowledgePage-C4J4VueL.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DA_dqVRG.js +4 -0
- package/dist/web/assets/{NotFoundPage-fetSnp0N.js → NotFoundPage-x_Yd59mm.js} +1 -1
- package/dist/web/assets/{OverviewPage-BD4yY2Xz.js → OverviewPage-3eZjQ26v.js} +1 -1
- package/dist/web/assets/OverviewPage-8LBN8Shi.css +1 -0
- package/dist/web/assets/PersonalProfilePage-CDQk096L.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-CJxp4Wyz.js +1 -0
- package/dist/web/assets/{ProjectAgentFirstTask-BcxqmKXQ.js → ProjectAgentFirstTask-BkRJ0zwO.js} +1 -1
- package/dist/web/assets/{ProjectAgentsPage-BWDJBESb.js → ProjectAgentsPage-BuNNqSEy.js} +8 -8
- package/dist/web/assets/{ProjectScopePicker-yodM7lF_.js → ProjectScopePicker-CiDxM2Yx.js} +1 -1
- package/dist/web/assets/PublicProjectsPage-ByOT559r.js +1 -0
- package/dist/web/assets/{ReviewPage-Cy0Rt4CO.js → ReviewPage-C0Vdr9uq.js} +1 -1
- package/dist/web/assets/RoundtablesPage-Drou-B81.css +1 -0
- package/dist/web/assets/RoundtablesPage-L3ShEWS3.js +1 -0
- package/dist/web/assets/SettingsPage-BE8pWbI6.css +1 -0
- package/dist/web/assets/SettingsPage-McYuinGz.js +1 -0
- package/dist/web/assets/{SharedJefaReportPage-C56kPciB.js → SharedJefaReportPage-Bg6lIk3E.js} +1 -1
- package/dist/web/assets/UiButton-x8hiHNRM.js +1 -0
- package/dist/web/assets/UiDialog-mjletK0y.js +1 -0
- package/dist/web/assets/{UiDisclosure-D0iMTplF.js → UiDisclosure-_IxeiexW.js} +1 -1
- package/dist/web/assets/{UiSegmented-DIJW6wv3.js → UiSegmented-WFfz9Evl.js} +1 -1
- package/dist/web/assets/{UiSelect-CMR0QqF6.js → UiSelect-K29jnV-1.js} +1 -1
- package/dist/web/assets/{WritingTastePage-QgNzssfF.js → WritingTastePage-DCULB-d2.js} +1 -1
- package/dist/web/assets/agent-pins-DS4mNsNt.js +1 -0
- package/dist/web/assets/agent-profile-Bh3MJGvb.css +1 -0
- package/dist/web/assets/{attention-store-B1DI8wmd.js → attention-store-CA5y8j1R.js} +1 -1
- package/dist/web/assets/catalog-D0VEfOAV.js +1 -0
- package/dist/web/assets/{client-CR-W5Wzp.js → client-DnwnUBQV.js} +1 -1
- package/dist/web/assets/{console-D7GHOMEC.js → console-Cw7TzOSC.js} +1 -1
- package/dist/web/assets/conversation-launcher-settings-Yc13viwr.js +1 -0
- package/dist/web/assets/{i18n-CVFhT_k7.js → i18n-oJFlt8hW.js} +3 -3
- package/dist/web/assets/{identity-CM6Dmy00.js → identity-DSGS4CZc.js} +1 -1
- package/dist/web/assets/index-BfHCcUL2.css +1 -0
- package/dist/web/assets/index-DO10_fLc.js +5 -0
- package/dist/web/assets/{judgment-types-CJBEyJs1.js → judgment-types-DR931cZ7.js} +1 -1
- package/dist/web/assets/{model-WFhQZ3v_.js → model-BJ-UF4cs.js} +1 -1
- package/dist/web/assets/{personal-profile-graph-BwCh2M9G.js → personal-profile-graph-SOK0BR0w.js} +1 -1
- package/dist/web/assets/project-team-api-C_0qJk_B.js +1 -0
- package/dist/web/assets/{shared-report-CeIvrL9a.js → shared-report-CiX64ZiD.js} +1 -1
- package/dist/web/assets/useAgentRoster-B84mzZIg.js +1 -0
- package/dist/web/assets/{useApi-CROJJdhE-BYhbjaZB.js → useApi-CROJJdhE-7gt0QyQ5.js} +1 -1
- package/dist/web/assets/{vue-router-DaxrQwX3.js → vue-router-CCvPGaLD.js} +1 -1
- package/dist/web/index.html +15 -14
- package/dist/web/licenses/ant-design-icons.txt +22 -0
- package/docs/agent-conversations-and-collaboration.md +3 -3
- package/docs/agent-roundtable.md +33 -0
- package/docs/client-connections.md +86 -0
- package/docs/employee-agents.md +3 -3
- package/docs/project-agent-memory.md +6 -6
- package/graph-provider/fuli_graph/default_project_lead.py +1 -1
- package/graph-provider/fuli_graph/project_agent_delegations.py +70 -2
- package/graph-provider/fuli_graph/project_agent_memory_routes.py +18 -1
- package/graph-provider/fuli_graph/project_agent_remote_origins.py +154 -0
- package/graph-provider/fuli_graph/project_agent_task_models.py +1 -1
- package/graph-provider/fuli_graph/runtime.py +7 -0
- package/graph-provider/fuli_graph/store_project_agent_context.py +3 -2
- package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +1 -1
- package/graph-provider/fuli_graph/store_project_agent_task_reads.py +1 -1
- package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +5 -5
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +2 -2
- package/graph-provider/fuli_graph/store_project_agent_tasks.py +2 -2
- package/graph-provider/fuli_graph/store_project_agents.py +6 -6
- package/graph-provider/fuli_graph/system_agents.py +12 -2
- package/graph-provider/fuli_graph/system_hr_identity.py +2 -2
- package/graph-provider/pyproject.toml +2 -1
- package/npm-shrinkwrap.json +280 -5
- package/package.json +6 -2
- package/skills/operating-fuli/SKILL.md +3 -3
- package/src/agent-peer/coordinator-http.js +117 -0
- package/src/agent-peer/coordinator-service.js +258 -0
- package/src/agent-peer/coordinator-store.js +95 -0
- package/src/agent-peer/device-identity.js +70 -0
- package/src/agent-peer/http.js +47 -0
- package/src/agent-peer/node-store.js +127 -0
- package/src/agent-peer/origin-proof.js +56 -0
- package/src/agent-peer/peer-port.js +140 -0
- package/src/agent-peer/peer-runtime.js +298 -0
- package/src/agent-peer/peer-transport.js +78 -0
- package/src/agent-peer/protocol.js +161 -0
- package/src/agent-peer/runner.js +251 -0
- package/src/agent-roundtable/http.js +17 -3
- package/src/agent-roundtable/service.js +272 -43
- package/src/agent-roundtable/tools.js +15 -1
- package/src/agent-roundtable/wake.js +12 -3
- package/src/agent-tools/graph-definitions.js +14 -5
- package/src/agent-tools/graph-handlers.js +1 -0
- package/src/agent-tools/interface-catalog.js +10 -1
- package/src/agent-tools/project-agent-definitions.js +1 -1
- package/src/agents/identity-receipt.js +1 -1
- package/src/cli/command-registry.js +3 -0
- package/src/cli/connect-command.js +28 -0
- package/src/cli/mcp-command.js +99 -0
- package/src/cli/peer-command.js +100 -0
- package/src/cli/setup-agent-selection.js +2 -2
- package/src/cli/setup-command.js +7 -2
- package/src/cli.js +16 -0
- package/src/employees/catalog/bole.json +2 -2
- package/src/graphiti/federated-application.js +4 -1
- package/src/graphiti/project-agent-control-plane.js +7 -0
- package/src/graphiti/project-agent-task-entry.js +4 -4
- package/src/graphiti/project-agent-team-context.js +16 -18
- package/src/graphiti/project-team-view.js +82 -0
- package/src/graphiti/provider-client.js +9 -0
- package/src/graphiti/task-entry-preferences.js +1 -1
- package/src/http/api-router.js +10 -1
- package/src/http/graph-api-router.js +7 -0
- package/src/mcp/tool-annotations.js +4 -3
- package/src/mcp-server.js +2 -100
- package/src/server.js +14 -2
- package/src/setup/client-connection.js +16 -0
- package/src/system/system-service.js +5 -0
- package/dist/web/assets/AgentProfilePage-CioOdkBM.js +0 -1
- package/dist/web/assets/AgentsDirectoryPage--1A9nvNA.js +0 -1
- package/dist/web/assets/AgentsDirectoryPage-C7OUQH9v.css +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-1QevD4Qv.js +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-B0vQIgwA.js +0 -3
- package/dist/web/assets/KnowledgeEditDialog-ClnrfPZi.js +0 -1
- package/dist/web/assets/KnowledgeInspector-Te1hJZbx.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-CCCrWl6Y.js +0 -1
- package/dist/web/assets/KnowledgePage-DGOcBwr8.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-cKn1NCRe.js +0 -4
- package/dist/web/assets/OverviewPage-By-fUzSG.css +0 -1
- package/dist/web/assets/PersonalProfilePage-DGJVP_VB.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-D25HYMgD.js +0 -1
- package/dist/web/assets/PublicProjectsPage-CyNxVzW3.js +0 -1
- package/dist/web/assets/RoundtablesPage-BjVhvwMc.js +0 -1
- package/dist/web/assets/RoundtablesPage-Cb0R4GaM.css +0 -1
- package/dist/web/assets/SettingsPage-CCZqZtnH.js +0 -1
- package/dist/web/assets/SettingsPage-DYz5bTwa.css +0 -1
- package/dist/web/assets/UiDialog-CvDxg3B1.js +0 -1
- package/dist/web/assets/agent-pins-BcJ_qr8n.js +0 -1
- package/dist/web/assets/agent-profile-BZj3ezWe.css +0 -1
- package/dist/web/assets/catalog-DbXv0_bR.js +0 -1
- package/dist/web/assets/conversation-launcher-settings-Dj2GMzCI.js +0 -1
- package/dist/web/assets/index-CJ6Jmok9.css +0 -1
- package/dist/web/assets/index-CRa3GnYi.js +0 -5
- package/dist/web/assets/useAgentRoster-DCArHlxK.js +0 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { resolveSetupPaths } from '../setup/paths.js';
|
|
2
|
+
import { resolveGraphRuntimeOptions } from '../graphiti/runtime-config.js';
|
|
3
|
+
import { clientConnectionConfig } from '../setup/client-connection.js';
|
|
4
|
+
|
|
5
|
+
export function runConnectCommand(args, { env = process.env, write = console.log } = {}) {
|
|
6
|
+
if (args.length === 1 && ['--help', '-h'].includes(args[0])) {
|
|
7
|
+
write('fuli connect [--format standard|vscode|server] [--data-dir DIR | --runtime-config FILE]\nPrint local MCP configuration; merge the fuli entry into your client settings. No files are changed.');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const options = new Map();
|
|
11
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
12
|
+
const flag = args[index];
|
|
13
|
+
if (!['--format', '--data-dir', '--runtime-config'].includes(flag)) throw new TypeError(`Unknown connect option: ${flag}`);
|
|
14
|
+
if (options.has(flag)) throw new TypeError(`Duplicate ${flag}`);
|
|
15
|
+
const value = args[index + 1];
|
|
16
|
+
if (!value?.trim() || value.startsWith('--')) throw new TypeError(`Missing value for ${flag}`);
|
|
17
|
+
options.set(flag, value);
|
|
18
|
+
}
|
|
19
|
+
if (options.has('--data-dir') && options.has('--runtime-config')) {
|
|
20
|
+
throw new TypeError('Choose --data-dir or --runtime-config, not both');
|
|
21
|
+
}
|
|
22
|
+
const paths = resolveSetupPaths({ dataDir: options.get('--data-dir'), env });
|
|
23
|
+
const runtimeConfigPath = options.has('--data-dir') ? paths.graphRuntimeConfigPath
|
|
24
|
+
: resolveGraphRuntimeOptions(options.has('--runtime-config') ? ['--runtime-config', options.get('--runtime-config')] : [], env).runtimeConfigPath;
|
|
25
|
+
const config = clientConnectionConfig({ mcpServerPath: paths.mcpServerPath, runtimeConfigPath }, options.get('--format'));
|
|
26
|
+
write(JSON.stringify(config, null, 2));
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { callAgentTool, listAgentTools } from '../agent-tools.js';
|
|
2
|
+
import {
|
|
3
|
+
bindRuntimeLeaseAgentTools,
|
|
4
|
+
createRuntimeLeaseClient
|
|
5
|
+
} from '../adaptive-runtime/lease-client.js';
|
|
6
|
+
import { ApplicationError } from '../app/application-error.js';
|
|
7
|
+
import { openFederatedGraphApplication } from '../graphiti/federated-application.js';
|
|
8
|
+
import {
|
|
9
|
+
GraphRuntimeConfigurationError,
|
|
10
|
+
resolveGraphRuntimeOptions
|
|
11
|
+
} from '../graphiti/runtime-config.js';
|
|
12
|
+
import { runStdio } from '../mcp/runtime.js';
|
|
13
|
+
import { prepareDirectAgentToolCall } from '../mcp/direct-call.js';
|
|
14
|
+
import { normalizeMcpSourceApplication } from '../mcp/session-id.js';
|
|
15
|
+
import { applicationErrorMessage } from '../mcp/tool-result.js';
|
|
16
|
+
|
|
17
|
+
export async function runMcpCommand(args, { env = process.env } = {}) {
|
|
18
|
+
if (args.includes('--tools')) {
|
|
19
|
+
process.stdout.write(`${JSON.stringify(listAgentTools(), null, 2)}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
23
|
+
process.stdout.write(helpText());
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { runtimeConfigPath } = resolveGraphRuntimeOptions(args, env);
|
|
28
|
+
const configuredSourceApplication = args.includes('--source-application')
|
|
29
|
+
? requiredOption(args, '--source-application')
|
|
30
|
+
: null;
|
|
31
|
+
if (args.includes('--call')) {
|
|
32
|
+
await runCall({
|
|
33
|
+
args,
|
|
34
|
+
runtimeConfigPath,
|
|
35
|
+
sourceApplication: configuredSourceApplication
|
|
36
|
+
? normalizeMcpSourceApplication(configuredSourceApplication)
|
|
37
|
+
: 'other'
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await runStdio({
|
|
42
|
+
runtimeConfigPath,
|
|
43
|
+
sourceApplication: configuredSourceApplication
|
|
44
|
+
? normalizeMcpSourceApplication(configuredSourceApplication)
|
|
45
|
+
: undefined
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function runCall({ args, runtimeConfigPath, sourceApplication }) {
|
|
50
|
+
const toolName = requiredOption(args, '--call');
|
|
51
|
+
const prepared = await prepareDirectAgentToolCall({
|
|
52
|
+
toolName,
|
|
53
|
+
input: parseInput(args),
|
|
54
|
+
sourceApplication
|
|
55
|
+
});
|
|
56
|
+
const app = openFederatedGraphApplication({ runtimeConfigPath });
|
|
57
|
+
const leaseClient = createRuntimeLeaseClient({ runtimeConfigPath });
|
|
58
|
+
bindRuntimeLeaseAgentTools(app, leaseClient);
|
|
59
|
+
try {
|
|
60
|
+
const result = await leaseClient.withGraphLease(
|
|
61
|
+
`cli-call:${toolName}`,
|
|
62
|
+
() => callAgentTool(app, toolName, prepared.input)
|
|
63
|
+
);
|
|
64
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
65
|
+
} finally {
|
|
66
|
+
await leaseClient.close();
|
|
67
|
+
await app.close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function option(args, flag) {
|
|
72
|
+
const index = args.indexOf(flag);
|
|
73
|
+
return index === -1 ? null : args[index + 1];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function requiredOption(args, flag) {
|
|
77
|
+
const value = option(args, flag);
|
|
78
|
+
if (!value) throw new TypeError(`Missing value for ${flag}`);
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseInput(args) {
|
|
83
|
+
const raw = option(args, '--input');
|
|
84
|
+
return raw ? JSON.parse(raw) : {};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function startupMessage(error) {
|
|
88
|
+
if (error instanceof ApplicationError) return applicationErrorMessage(error);
|
|
89
|
+
if (error instanceof GraphRuntimeConfigurationError) return error.message;
|
|
90
|
+
return 'MCP server failed to start';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function helpText() {
|
|
94
|
+
return 'Usage: fuli mcp [--runtime-config <graph-runtime.json>] ' +
|
|
95
|
+
'[--source-application <codex|claude|claude_code|cursor|kiro|other>]\n' +
|
|
96
|
+
' fuli mcp --tools\n' +
|
|
97
|
+
' fuli mcp --runtime-config <graph-runtime.json> ' +
|
|
98
|
+
'--call <tool> [--input <json>]\n';
|
|
99
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import { resolveSetupPaths } from '../setup/paths.js';
|
|
4
|
+
import { DEFAULT_RUNTIME_SETTINGS, readRuntimeSettings } from '../system/runtime-settings.js';
|
|
5
|
+
import { shortFingerprint } from '../agent-peer/protocol.js';
|
|
6
|
+
|
|
7
|
+
const USAGE = `fuli peer <action> (LAN roundtable Beta; the local console must be running)
|
|
8
|
+
status
|
|
9
|
+
host --address IP [--port PORT] [--name NAME] turn this computer into the coordinator
|
|
10
|
+
invite print a one-use pairing invitation (10 minutes)
|
|
11
|
+
join (--invitation TEXT | --invitation-file FILE) [--name NAME]
|
|
12
|
+
share --project ID --clients codex[,claude_code] [--folder DIR] (repeat --project for more; replaces the list)
|
|
13
|
+
revoke --device FINGERPRINT coordinator only
|
|
14
|
+
off leave: stop listening, sharing and trusting devices`;
|
|
15
|
+
|
|
16
|
+
// The owner's LAN roundtable controls through the local console, so the CLI
|
|
17
|
+
// and Settings share one runtime and one set of local-only checks.
|
|
18
|
+
export async function runPeerCommand(args, { env = process.env, fetchImpl = fetch, write = console.log } = {}) {
|
|
19
|
+
const [action, ...rest] = args;
|
|
20
|
+
if (!action || action === '--help' || action === '-h') { write(USAGE); return; }
|
|
21
|
+
const paths = resolveSetupPaths({ dataDir: option(rest, '--data-dir'), env });
|
|
22
|
+
const settings = paths.runtimeSettingsPath ? readRuntimeSettings(paths.runtimeSettingsPath) : DEFAULT_RUNTIME_SETTINGS;
|
|
23
|
+
const base = `http://127.0.0.1:${settings.ports.console}`;
|
|
24
|
+
const call = async (method, path, body) => {
|
|
25
|
+
let response;
|
|
26
|
+
try {
|
|
27
|
+
response = await fetchImpl(`${base}${path}`, { method, headers: { 'content-type': 'application/json' },
|
|
28
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }) });
|
|
29
|
+
} catch {
|
|
30
|
+
throw new Error('The local FULI console is not running; start it with `fuli start`.');
|
|
31
|
+
}
|
|
32
|
+
const value = await response.json().catch(() => ({}));
|
|
33
|
+
if (!response.ok) throw new Error(value.message ?? `Request failed (${response.status})`);
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
switch (action) {
|
|
37
|
+
case 'status': return printStatus(await call('GET', '/api/peer'), write);
|
|
38
|
+
case 'host': {
|
|
39
|
+
const address = option(rest, '--address');
|
|
40
|
+
if (!address) throw new Error('Pass --address with one of this computer\'s LAN addresses (see `fuli peer status`).');
|
|
41
|
+
return printStatus(await call('POST', '/api/peer/coordinator', { host: address,
|
|
42
|
+
port: Number(option(rest, '--port') ?? 0), name: option(rest, '--name') }), write);
|
|
43
|
+
}
|
|
44
|
+
case 'invite': {
|
|
45
|
+
const result = await call('POST', '/api/peer/invitations', {});
|
|
46
|
+
write(`Coordinator fingerprint: ${shortFingerprint(result.fingerprint)}`);
|
|
47
|
+
write(`Invitation (one use, until ${result.expiresAt}):\n${result.invitation}`);
|
|
48
|
+
write('Compare this fingerprint with the one shown on the joining device before you share it.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
case 'join': {
|
|
52
|
+
const file = option(rest, '--invitation-file');
|
|
53
|
+
const invitation = option(rest, '--invitation') ?? (file ? readFileSync(file, 'utf8').trim() : null);
|
|
54
|
+
if (!invitation) throw new Error('Pass --invitation or --invitation-file.');
|
|
55
|
+
return printStatus(await call('POST', '/api/peer/join', { invitation, name: option(rest, '--name') }), write);
|
|
56
|
+
}
|
|
57
|
+
case 'share': {
|
|
58
|
+
const projects = options(rest, '--project');
|
|
59
|
+
const clients = (option(rest, '--clients') ?? '').split(',').map((client) => client.trim()).filter(Boolean);
|
|
60
|
+
const folder = option(rest, '--folder');
|
|
61
|
+
if (projects.length > 1 && folder) throw new Error('--folder applies to a single --project.');
|
|
62
|
+
return printStatus(await call('PUT', '/api/peer/shares', { shares: projects.map((projectId) => ({ projectId, clients,
|
|
63
|
+
...(folder ? { workingDirectory: folder } : {}) })) }), write);
|
|
64
|
+
}
|
|
65
|
+
case 'revoke': {
|
|
66
|
+
const device = option(rest, '--device');
|
|
67
|
+
if (!/^[0-9a-f]{64}$/.test(device ?? '')) throw new Error('Pass --device with the full fingerprint from `fuli peer status`.');
|
|
68
|
+
return printStatus(await call('POST', `/api/peer/devices/${device}/revoke`, {}), write);
|
|
69
|
+
}
|
|
70
|
+
case 'off': return printStatus(await call('POST', '/api/peer/disable', {}), write);
|
|
71
|
+
default: throw new Error(`Unknown peer action: ${action}\n${USAGE}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function printStatus(status, write) {
|
|
76
|
+
if (!status.role) {
|
|
77
|
+
write('LAN roundtable (Beta): off.');
|
|
78
|
+
if (status.remoteRemovalPending) write('This device has stopped sharing. Pairing removal will retry when the coordinator is available.');
|
|
79
|
+
if (status.addresses.length) write(`LAN addresses: ${status.addresses.join(', ')}`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
write(`LAN roundtable (Beta): ${status.role === 'coordinator' ? 'coordinator' : 'paired device'}`);
|
|
83
|
+
write(`This device: ${status.device.shortFingerprint}`);
|
|
84
|
+
write(`Coordinator: ${status.coordinator.name} ${status.coordinator.url} (${status.coordinator.shortFingerprint})`);
|
|
85
|
+
for (const device of status.devices.filter((entry) => !entry.self)) {
|
|
86
|
+
write(` device ${device.name} ${device.shortFingerprint} ${device.status}${device.online ? ' online' : ''} ${device.nodeId}`);
|
|
87
|
+
}
|
|
88
|
+
for (const share of status.shares) {
|
|
89
|
+
write(` shared project lead: ${share.projectName} · ${share.clients.join(', ')} · ${share.state}${share.workingDirectory ? '' : ' · folder from recent sessions'}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function option(args, name) {
|
|
94
|
+
const index = args.indexOf(name);
|
|
95
|
+
return index >= 0 && index + 1 < args.length ? args[index + 1] : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function options(args, name) {
|
|
99
|
+
return args.flatMap((value, index) => (value === name && index + 1 < args.length ? [args[index + 1]] : []));
|
|
100
|
+
}
|
|
@@ -86,7 +86,7 @@ export function selectedAgentIds(agents, state) {
|
|
|
86
86
|
|
|
87
87
|
export function formatAgentCheckboxSelection(agents, state) {
|
|
88
88
|
return [
|
|
89
|
-
'
|
|
89
|
+
'Clients available for automatic setup:',
|
|
90
90
|
...agents.map((agent, index) => {
|
|
91
91
|
const pointer = index === state.activeIndex ? '❯' : ' ';
|
|
92
92
|
const checkbox = state.selected[index] ? '[x]' : '[ ]';
|
|
@@ -161,7 +161,7 @@ async function promptCheckboxAgentSelection(agents, { input, output }) {
|
|
|
161
161
|
|
|
162
162
|
async function promptTextAgentSelection(agents, { input, output }) {
|
|
163
163
|
const terminal = createInterface({ input, output });
|
|
164
|
-
output.write('
|
|
164
|
+
output.write('Clients available for automatic setup:\n');
|
|
165
165
|
for (const [index, agent] of agents.entries()) {
|
|
166
166
|
output.write(` ${index + 1}. ${agent.label}\n`);
|
|
167
167
|
}
|
package/src/cli/setup-command.js
CHANGED
|
@@ -67,7 +67,8 @@ export function formatSetupPreview(plan, options) {
|
|
|
67
67
|
`Management UI: ${options.noStart
|
|
68
68
|
? 'will not start'
|
|
69
69
|
: `http://127.0.0.1:${runtimeSettings.ports.console}`}`,
|
|
70
|
-
`
|
|
70
|
+
`Automatic client setup: ${agentSummary}`,
|
|
71
|
+
'Other MCP clients: connect after setup with fuli connect (no model required)'
|
|
71
72
|
];
|
|
72
73
|
if (
|
|
73
74
|
runtimeMode !== 'native' &&
|
|
@@ -80,7 +81,7 @@ export function formatSetupPreview(plan, options) {
|
|
|
80
81
|
return lines.join('\n');
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
export function formatSetupResult(result) {
|
|
84
|
+
export function formatSetupResult(result, plan) {
|
|
84
85
|
const lines = [result.status === 'ready'
|
|
85
86
|
? 'Fuli is ready.'
|
|
86
87
|
: 'Fuli started, but one or more Agents are not connected.'];
|
|
@@ -106,6 +107,10 @@ export function formatSetupResult(result) {
|
|
|
106
107
|
: `${agent.label}: connected`);
|
|
107
108
|
for (const step of agent.nextSteps ?? []) lines.push(`${agent.label}: ${step}`);
|
|
108
109
|
}
|
|
110
|
+
const dataDir = plan?.paths?.dataDir;
|
|
111
|
+
lines.push('Other MCP clients: run fuli connect and merge the fuli entry into your MCP settings.');
|
|
112
|
+
if (dataDir) lines.push(`For this installation, use --data-dir ${JSON.stringify(dataDir)}.`);
|
|
113
|
+
lines.push('MCP provides tools and memory. Automatic conversation capture and wake/resume require a client adapter.');
|
|
109
114
|
return lines.join('\n');
|
|
110
115
|
}
|
|
111
116
|
|
package/src/cli.js
CHANGED
|
@@ -11,6 +11,9 @@ 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
13
|
import { runSetupCommand } from './cli/setup-command.js';
|
|
14
|
+
import { runConnectCommand } from './cli/connect-command.js';
|
|
15
|
+
import { runPeerCommand } from './cli/peer-command.js';
|
|
16
|
+
import { runMcpCommand, startupMessage } from './cli/mcp-command.js';
|
|
14
17
|
import { runUninstallCommand } from './cli/uninstall-command.js';
|
|
15
18
|
import { runUpdateCommand } from './cli/update-command.js';
|
|
16
19
|
import { runWorkspaceConnectionCommand } from './cli/workspace-connection-command.js';
|
|
@@ -32,6 +35,19 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
|
|
|
32
35
|
await runSetupCommand(commandArgs, { env });
|
|
33
36
|
return;
|
|
34
37
|
}
|
|
38
|
+
if (command === 'connect') {
|
|
39
|
+
assertSupportedNodeVersion();
|
|
40
|
+
return runConnectCommand(commandArgs, { env });
|
|
41
|
+
}
|
|
42
|
+
if (command === 'peer') {
|
|
43
|
+
assertSupportedNodeVersion();
|
|
44
|
+
return runPeerCommand(commandArgs, { env });
|
|
45
|
+
}
|
|
46
|
+
if (command === 'mcp') {
|
|
47
|
+
assertSupportedNodeVersion();
|
|
48
|
+
try { return await runMcpCommand(commandArgs, { env }); }
|
|
49
|
+
catch (error) { throw new Error(startupMessage(error)); }
|
|
50
|
+
}
|
|
35
51
|
if (command === 'graph') {
|
|
36
52
|
assertSupportedNodeVersion();
|
|
37
53
|
const [action, ...graphArgs] = commandArgs;
|
|
@@ -2,6 +2,7 @@ import { dirname, join } from 'node:path';
|
|
|
2
2
|
import { createEmployeeService } from '../employees/service.js';
|
|
3
3
|
import { createAgentRoundtable } from '../agent-roundtable/service.js';
|
|
4
4
|
import { createRoundtableStore } from '../agent-roundtable/store.js';
|
|
5
|
+
import { createPeerPort } from '../agent-peer/peer-port.js';
|
|
5
6
|
import { createJudgmentService } from '../judgment/service.js';
|
|
6
7
|
import { JudgmentStore } from '../judgment/store.js';
|
|
7
8
|
import { createJudgmentEngine } from '../judgment/engine.js';
|
|
@@ -125,6 +126,8 @@ export function openFederatedGraphApplication({
|
|
|
125
126
|
app.employees = createEmployeeService({ app, runtimeConfigPath });
|
|
126
127
|
app.judgment = createJudgmentService({ app, engine: createJudgmentEngine({ app }),
|
|
127
128
|
openStore: () => new JudgmentStore(join(dirname(runtimeConfigPath), 'judgment.sqlite')) });
|
|
129
|
+
app.peer = createPeerPort({ dataDir: dirname(runtimeConfigPath), provider: app.personal,
|
|
130
|
+
spaceId: () => app.config.personal.spaceId });
|
|
128
131
|
app.roundtable = createAgentRoundtable({ app, env,
|
|
129
132
|
openStore: () => createRoundtableStore(join(dirname(runtimeConfigPath), 'agent-roundtable.sqlite')) });
|
|
130
133
|
}
|
|
@@ -1169,7 +1172,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
|
|
|
1169
1172
|
};
|
|
1170
1173
|
}
|
|
1171
1174
|
|
|
1172
|
-
close() { this.judgment?.close(); this.roundtable?.close(); return this.employees?.close(); }
|
|
1175
|
+
close() { this.judgment?.close(); this.roundtable?.close(); this.peer?.close(); return this.employees?.close(); }
|
|
1173
1176
|
|
|
1174
1177
|
async #recordAgentViews(items, toolName) {
|
|
1175
1178
|
const unique = new Map(items.map((item) => [
|
|
@@ -2,6 +2,7 @@ import { planAgentCollaboration, agentLoan, agentVerification } from './agent-co
|
|
|
2
2
|
import { queryConversations, resumeConversation, updateConversationPolicy } from './agent-conversations.js';
|
|
3
3
|
import { agentProjectResolution } from './agent-knowledge-workflows.js';
|
|
4
4
|
import { listAgentAttention, changeAgentAttention } from './agent-attention.js';
|
|
5
|
+
import { loadProjectTeam } from './project-team-view.js';
|
|
5
6
|
import {
|
|
6
7
|
getProjectAgentMemory,
|
|
7
8
|
agentMemoryView,
|
|
@@ -205,6 +206,12 @@ export class ProjectAgentControlPlaneApplication {
|
|
|
205
206
|
return getProjectAgentCoordinationPolicyWorkflow(this, input);
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
async getProjectTeam(input) {
|
|
210
|
+
this.#assertSpace(input.personalSpaceId);
|
|
211
|
+
if (!input.personalProjectId) throw new TypeError('personalProjectId is required');
|
|
212
|
+
return loadProjectTeam(this, input.personalSpaceId, input.personalProjectId);
|
|
213
|
+
}
|
|
214
|
+
|
|
208
215
|
async updateProjectAgentCoordinationPolicy(input) {
|
|
209
216
|
this.#assertSpace(input.personalSpaceId);
|
|
210
217
|
return updateProjectAgentCoordinationPolicyWorkflow(this, input);
|
|
@@ -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 { isPeerRoleAgent } from './project-team-view.js';
|
|
5
6
|
import { resolveTaskAgentMention } from './project-agent-mention.js';
|
|
6
7
|
|
|
7
8
|
const ENTRY_TOOLS = new Set(['begin_task_context', 'get_collaboration_preferences']);
|
|
@@ -49,14 +50,14 @@ export async function resolveTaskEntryAgent(application, resolution, input) {
|
|
|
49
50
|
const implicit = !mention?.agentId && !input.projectAgentId;
|
|
50
51
|
const peerOnly = agent && implicit && unrelatedPeer(agent, workKind, input.requiredCapabilities);
|
|
51
52
|
if (implicit && value.reason !== 'project_lead_required' && (peerOnly || value.status === 'unassigned')) {
|
|
52
|
-
// Every task gets a FULI Agent: a project without a lead has
|
|
53
|
+
// Every task gets a FULI Agent: a project without a lead has AR hire one.
|
|
53
54
|
const staffed = await application.personal.staffDefaultProjectLead(scope).catch(() => null);
|
|
54
55
|
if (staffed?.agent) {
|
|
55
56
|
value = staffed;
|
|
56
57
|
agent = projectAgentRecord(staffed.agent);
|
|
57
58
|
} else if (peerOnly) {
|
|
58
59
|
return { status: 'unassigned', reason: 'specialist_required', worker_started: false,
|
|
59
|
-
required_action: 'Use coordinate_project_agent_task to select or recruit a qualified durable owner before implementation.
|
|
60
|
+
required_action: 'Use coordinate_project_agent_task to select or recruit a qualified durable owner before implementation. AR and project managers collaborate in their own roles; do not attribute development to them by fallback.' };
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
const employeeId = /^employee\.([a-z][a-z0-9-]{0,63})$/.exec(agent?.agentId ?? '')?.[1];
|
|
@@ -159,8 +160,7 @@ function boundedRoleText(value, limit) {
|
|
|
159
160
|
function unrelatedPeer(agent, workKind, required = []) {
|
|
160
161
|
const profile = agent.profile;
|
|
161
162
|
const capabilities = (profile.capabilities ?? []).map(value => value.toLowerCase());
|
|
162
|
-
const peer =
|
|
163
|
-
|| profile.agentType === 'hr' || capabilities.some(value => ['fuli.employee:jefa', 'fuli.employee:bole', 'fuli.employee:tonborg'].includes(value));
|
|
163
|
+
const peer = isPeerRoleAgent({ agentId: agent.agentId, agentType: profile.agentType, capabilities });
|
|
164
164
|
return peer && !(profile.workKinds ?? []).some(kind => kind.toLowerCase() === workKind.toLowerCase())
|
|
165
165
|
&& !(required.length && required.every(value => capabilities.includes(value.toLowerCase())));
|
|
166
166
|
}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
+
import { loadProjectTeam } from './project-team-view.js';
|
|
2
|
+
|
|
1
3
|
// Only public role/assignment data crosses the team boundary. Member memory
|
|
2
4
|
// is restored separately inside each authorized worker's isolated context.
|
|
3
5
|
export async function loadProjectTeamContext(application, personalSpaceId, projectId) {
|
|
4
6
|
if (typeof application.personal.getProjectAgentCoordinationPolicy !== 'function') return null;
|
|
5
|
-
const
|
|
6
|
-
if (
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
user_facing_agent_id: policy.team_lead_agent_id,
|
|
19
|
-
reporting_lines: roster.filter(agent => agent.role === 'member')
|
|
20
|
-
.map(agent => ({ agent_id: agent.agent_id, reports_to_agent_id: policy.team_lead_agent_id })),
|
|
21
|
-
unavailable_agent_ids: ids.filter(id => !roster.some(agent => agent.agent_id === id)),
|
|
7
|
+
const team = await loadProjectTeam(application, personalSpaceId, projectId);
|
|
8
|
+
if (team.status === 'no_lead') return null;
|
|
9
|
+
const leadId = team.lead?.agentId ?? team.unavailableLead.agentId;
|
|
10
|
+
const entry = (person, role) => ({ agent_id: person.agentId, name: person.name,
|
|
11
|
+
responsibility: person.responsibility, role });
|
|
12
|
+
const roster = [...(team.lead ? [entry(team.lead, 'lead')] : []),
|
|
13
|
+
...team.members.map(member => entry(member, 'member'))];
|
|
14
|
+
return { lead_agent_id: leadId, roster,
|
|
15
|
+
user_facing_agent_id: leadId,
|
|
16
|
+
reporting_lines: team.members.map(member => ({ agent_id: member.agentId, reports_to_agent_id: leadId })),
|
|
17
|
+
unavailable_agent_ids: [...(team.lead ? [] : [leadId]), ...team.unavailableMemberIds],
|
|
18
|
+
assigned_collaborators: team.collaborators.map(person => entry(person, 'collaborator')),
|
|
19
|
+
assigned_peers: team.peers.map(person => entry(person, 'peer')),
|
|
22
20
|
peer_roles: ['hr', 'project_manager'], worker_started: false,
|
|
23
|
-
guidance: 'The project lead is the sole user-facing owner. Members report results and blockers to this lead; do not skip reporting levels or replace the lead when a member is named. The lead integrates and verifies member reports before reporting to the user. Split bounded work through an authorized coordination plan; report real worker evidence.
|
|
21
|
+
guidance: 'The project lead is the sole user-facing owner. Members report results and blockers to this lead; do not skip reporting levels or replace the lead when a member is named. Assigned collaborators outside the explicit team work through the lead and are not direct reports unless the policy names them. The lead integrates and verifies member reports before reporting to the user. Split bounded work through an authorized coordination plan; report real worker evidence. AR and project managers remain peers. Team membership grants no new tool, data, executor, or publication permissions.' };
|
|
24
22
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// One authoritative reading of a project's people. The coordination policy
|
|
2
|
+
// owns the explicit hierarchy; active assignments decide who currently works
|
|
3
|
+
// on the exact project. Assigned Agents outside the explicit team are shown as
|
|
4
|
+
// collaborators and peer roles stay peers, never converted into direct reports.
|
|
5
|
+
const PEER_AGENT_IDS = new Set(['employee.jefa', 'employee.bole', 'employee.tonborg', 'fuli-project-hr']);
|
|
6
|
+
const PEER_CAPABILITIES = new Set(['fuli.employee:jefa', 'fuli.employee:bole', 'fuli.employee:tonborg']);
|
|
7
|
+
|
|
8
|
+
export function isPeerRoleAgent({ agentId, agentType, capabilities = [] }) {
|
|
9
|
+
return PEER_AGENT_IDS.has(agentId) || agentType === 'hr'
|
|
10
|
+
|| capabilities.some(value => PEER_CAPABILITIES.has(String(value).toLowerCase()));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildProjectTeam({ policy, agents, projectId }) {
|
|
14
|
+
const leadId = policy?.team_lead_agent_id ?? null;
|
|
15
|
+
const memberIds = [...new Set(policy?.team_member_agent_ids ?? [])].filter(id => id !== leadId);
|
|
16
|
+
const all = Array.isArray(agents) ? agents : [];
|
|
17
|
+
const assigned = new Map();
|
|
18
|
+
for (const agent of all) {
|
|
19
|
+
const assignment = activeAssignment(agent, projectId);
|
|
20
|
+
if (assignment && isActive(agent)) assigned.set(agent.agent_id, person(agent, assignment));
|
|
21
|
+
}
|
|
22
|
+
const lead = leadId ? assigned.get(leadId) ?? null : null;
|
|
23
|
+
// Legacy or reclassified policies may still name a peer role as a member;
|
|
24
|
+
// it stays a peer and never gains a reporting line.
|
|
25
|
+
const members = memberIds.filter(id => assigned.has(id) && !assigned.get(id).peerRole)
|
|
26
|
+
.map(id => ({ ...assigned.get(id), reportsToAgentId: lead ? leadId : null }));
|
|
27
|
+
const reporting = new Set([leadId, ...members.map(member => member.agentId)]);
|
|
28
|
+
const collaborators = [];
|
|
29
|
+
const peers = [];
|
|
30
|
+
for (const [id, entry] of assigned) {
|
|
31
|
+
if (reporting.has(id)) continue;
|
|
32
|
+
(entry.peerRole ? peers : collaborators).push(entry);
|
|
33
|
+
}
|
|
34
|
+
const byName = (left, right) => left.name.localeCompare(right.name);
|
|
35
|
+
const former = leadId && !lead ? all.find(agent => agent.agent_id === leadId) : null;
|
|
36
|
+
return {
|
|
37
|
+
personalProjectId: projectId,
|
|
38
|
+
status: !leadId ? 'no_lead' : lead ? 'ready' : 'lead_unavailable',
|
|
39
|
+
lead,
|
|
40
|
+
unavailableLead: leadId && !lead ? { agentId: leadId, name: former ? displayName(former) : null } : null,
|
|
41
|
+
members,
|
|
42
|
+
collaborators: collaborators.sort(byName),
|
|
43
|
+
peers: peers.sort(byName),
|
|
44
|
+
unavailableMemberIds: memberIds.filter(id => !assigned.has(id))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function activeAssignment(agent, projectId) {
|
|
49
|
+
return (agent.assignments ?? []).find(assignment => assignment.personal_project_id === projectId
|
|
50
|
+
&& assignment.status === 'active') ?? null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isActive(agent) {
|
|
54
|
+
const status = agent.profile?.status;
|
|
55
|
+
return status === undefined || status === null || status === 'active';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function displayName(agent) {
|
|
59
|
+
return agent.profile?.display_name || agent.profile?.name || agent.agent_id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function person(agent, assignment) {
|
|
63
|
+
const profile = agent.profile ?? {};
|
|
64
|
+
return {
|
|
65
|
+
agentId: agent.agent_id,
|
|
66
|
+
name: displayName(agent),
|
|
67
|
+
employeeNumber: agent.employee_number ?? null,
|
|
68
|
+
occupationEmoji: profile.occupation_emoji ?? profile.occupationEmoji ?? null,
|
|
69
|
+
responsibility: assignment.responsibility || profile.responsibility || '',
|
|
70
|
+
peerRole: isPeerRoleAgent({ agentId: agent.agent_id, agentType: profile.agent_type,
|
|
71
|
+
capabilities: profile.capabilities ?? [] })
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function loadProjectTeam(application, personalSpaceId, projectId) {
|
|
76
|
+
const [policy, agents] = await Promise.all([
|
|
77
|
+
application.personal.getProjectAgentCoordinationPolicy(personalSpaceId, projectId),
|
|
78
|
+
application.personal.listProjectAgents(personalSpaceId, projectId, { status: 'active' })
|
|
79
|
+
]);
|
|
80
|
+
const list = Array.isArray(agents) ? agents : agents?.agents ?? agents?.items ?? [];
|
|
81
|
+
return buildProjectTeam({ policy, agents: list, projectId });
|
|
82
|
+
}
|
|
@@ -156,6 +156,15 @@ export class GraphitiProviderClient {
|
|
|
156
156
|
issueAgentDelegation(input) {
|
|
157
157
|
return this.#request('/v1/project-agent-context/delegations', { method: 'POST', body: input });
|
|
158
158
|
}
|
|
159
|
+
issueRemoteAgentDelegation(input) {
|
|
160
|
+
return this.#request('/v1/project-agent-context/remote-delegations', { method: 'POST', body: input });
|
|
161
|
+
}
|
|
162
|
+
issueRemoteAgentOrigin(input) {
|
|
163
|
+
return this.#request('/v1/project-agent-context/remote-origins', { method: 'POST', body: input });
|
|
164
|
+
}
|
|
165
|
+
getRemoteOriginAuthority(personalSpaceId) {
|
|
166
|
+
return this.#request(`/v1/project-agent-context/remote-origin-authority?${new URLSearchParams({ personal_space_id: personalSpaceId })}`);
|
|
167
|
+
}
|
|
159
168
|
verifyAgentDelegation(input) {
|
|
160
169
|
return this.#request('/v1/project-agent-context/delegations/verify', { method: 'POST', body: input });
|
|
161
170
|
}
|
|
@@ -42,7 +42,7 @@ export async function taskEntryPreferences(application, projectResolution, {
|
|
|
42
42
|
}) : selection;
|
|
43
43
|
if (context && selection?.requested_agent_id) {
|
|
44
44
|
context.requested_agent_id = selection.requested_agent_id;
|
|
45
|
-
context.requested_agent_guidance = 'The user requested this project specialist
|
|
45
|
+
context.requested_agent_guidance = 'The user requested this project specialist, but this session runs as the selected project lead. Keep the lead as accountable owner; coordinate a scoped workstream for the specialist and receive its report before replying. This request is not evidence that a worker ran. Do not load the specialist’s private memory, write memory or checkpoints as the specialist, or reuse another session’s task context. If this session was opened to act as the specialist, tell the user it is running as the lead.';
|
|
46
46
|
}
|
|
47
47
|
if (context && selection?.reporting_lead_agent_id) {
|
|
48
48
|
context.reporting_lead_agent_id = selection.reporting_lead_agent_id;
|
package/src/http/api-router.js
CHANGED
|
@@ -3,6 +3,7 @@ import { handleExternalKnowledgeApiRequest } from './external-knowledge-api-rout
|
|
|
3
3
|
import { handleGraphApiRequest } from './graph-api-router.js';
|
|
4
4
|
import { handleEmployeeApiRequest } from './employee-api-router.js';
|
|
5
5
|
import { handleRoundtableApiRequest } from '../agent-roundtable/http.js';
|
|
6
|
+
import { handlePeerApiRequest } from '../agent-peer/http.js';
|
|
6
7
|
import { handleJudgmentApiRequest } from '../judgment/http.js';
|
|
7
8
|
import { FULI_VERSION } from '../package-metadata.js';
|
|
8
9
|
|
|
@@ -12,9 +13,11 @@ export async function handleApiRequest({
|
|
|
12
13
|
app,
|
|
13
14
|
system = null,
|
|
14
15
|
externalKnowledge = null,
|
|
15
|
-
connectedKnowledge = null
|
|
16
|
+
connectedKnowledge = null,
|
|
17
|
+
peer = null
|
|
16
18
|
}) {
|
|
17
19
|
const url = new URL(request.url, 'http://127.0.0.1');
|
|
20
|
+
if (await handlePeerApiRequest({ request, response, url, peer })) return true;
|
|
18
21
|
if (await handleRoundtableApiRequest({ request, response, url, roundtable: app.roundtable })) return true;
|
|
19
22
|
|
|
20
23
|
if (url.pathname === '/api/health' && request.method === 'GET') {
|
|
@@ -32,6 +35,12 @@ export async function handleApiRequest({
|
|
|
32
35
|
return true;
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
if (system && url.pathname === '/api/system/client-connection' && request.method === 'GET') {
|
|
39
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
40
|
+
sendJson(response, 200, system.clientConnection());
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
if (system && url.pathname === '/api/system/settings' && request.method === 'PUT') {
|
|
36
45
|
sendJson(response, 200, system.updateSettings(await readJson(request)));
|
|
37
46
|
return true;
|
|
@@ -320,6 +320,13 @@ export async function handleGraphApiRequest({
|
|
|
320
320
|
}));
|
|
321
321
|
return true;
|
|
322
322
|
}
|
|
323
|
+
if (url.pathname === '/api/project-team' && request.method === 'GET') {
|
|
324
|
+
sendJson(response, 200, await app.getProjectTeam({
|
|
325
|
+
personalSpaceId: url.searchParams.get('personalSpaceId'),
|
|
326
|
+
personalProjectId: url.searchParams.get('personalProjectId')
|
|
327
|
+
}));
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
323
330
|
if (url.pathname === '/api/project-agent-coordination-policy' &&
|
|
324
331
|
(request.method === 'PUT' || request.method === 'PATCH')) {
|
|
325
332
|
sendJson(response, 200, await app.updateProjectAgentCoordinationPolicy(await readJson(request)));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const READ_TOOLS = new Set([
|
|
2
2
|
'get_judgment_policy', 'list_judgments', 'get_agent_pins',
|
|
3
|
-
'find_agents', 'read_agent_messages', 'read_agent_thread',
|
|
3
|
+
'find_agents', 'read_agent_messages', 'read_agent_thread', 'get_agent_message_status',
|
|
4
4
|
'get_agent_quality_gate',
|
|
5
5
|
'plan_agent_collaboration', 'list_agent_loans',
|
|
6
6
|
'list_agent_conversations', 'read_agent_conversation', 'get_agent_conversation_policy',
|
|
@@ -32,6 +32,7 @@ const READ_TOOLS = new Set([
|
|
|
32
32
|
'view_project_agent_task',
|
|
33
33
|
'view_project_agent_activity',
|
|
34
34
|
'get_project_agent_coordination_policy',
|
|
35
|
+
'get_project_team',
|
|
35
36
|
'get_project_agent_recruitment_policy',
|
|
36
37
|
'list_project_agent_recruitments',
|
|
37
38
|
'list_executors',
|
|
@@ -52,7 +53,7 @@ const READ_TOOLS = new Set([
|
|
|
52
53
|
|
|
53
54
|
const WRITE_TOOLS = new Set([
|
|
54
55
|
'review_with_tonborg', 'assess_agent_action', 'assess_task_completion', 'pin_agent',
|
|
55
|
-
'message_agent', 'reply_agent_message',
|
|
56
|
+
'message_agent', 'reply_agent_message', 'cancel_agent_message',
|
|
56
57
|
'record_agent_verification',
|
|
57
58
|
'request_agent_loan', 'decide_agent_loan',
|
|
58
59
|
'resume_agent_conversation', 'update_agent_conversation_policy',
|
|
@@ -172,7 +173,7 @@ export function annotationsFor(name) {
|
|
|
172
173
|
name === 'record_knowledge_usage' || name === 'record_decision_trace' ||
|
|
173
174
|
name === 'record_knowledge_feedback' ||
|
|
174
175
|
name === 'record_workflow_transition_observation' ||
|
|
175
|
-
name === 'message_agent' || name === 'reply_agent_message' ||
|
|
176
|
+
name === 'message_agent' || name === 'reply_agent_message' || name === 'cancel_agent_message' ||
|
|
176
177
|
name === 'checkpoint_task_knowledge' ||
|
|
177
178
|
name === 'checkpoint_project_agent_memory',
|
|
178
179
|
openWorldHint: name === 'review_with_tonborg' || name === 'assess_agent_action' || name === 'assess_task_completion' || name === 'capture_session_knowledge' ||
|