fuli-context 0.2.0
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/LICENSE +201 -0
- package/README.md +178 -0
- package/compose.graphiti.yml +86 -0
- package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
- package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
- package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
- package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
- package/dist/web/assets/console-tbvTUi6p.js +1 -0
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-B3s5CXhv.js +1 -0
- package/dist/web/assets/index-Devf7glZ.css +1 -0
- package/dist/web/assets/index-DwUDhQKc.js +6 -0
- package/dist/web/assets/model-DVT8sjy9.js +1 -0
- package/dist/web/assets/paths-B790iniO.js +1 -0
- package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
- package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
- package/dist/web/index.html +22 -0
- package/graph-provider/Dockerfile +16 -0
- package/graph-provider/fuli_graph/__init__.py +5 -0
- package/graph-provider/fuli_graph/app.py +547 -0
- package/graph-provider/fuli_graph/auth.py +17 -0
- package/graph-provider/fuli_graph/collaboration_context.py +290 -0
- package/graph-provider/fuli_graph/config.py +34 -0
- package/graph-provider/fuli_graph/graph_models.py +123 -0
- package/graph-provider/fuli_graph/graph_projection.py +405 -0
- package/graph-provider/fuli_graph/graph_query.py +561 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
- package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
- package/graph-provider/fuli_graph/knowledge_management.py +816 -0
- package/graph-provider/fuli_graph/knowledge_records.py +65 -0
- package/graph-provider/fuli_graph/knowledge_search.py +723 -0
- package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
- package/graph-provider/fuli_graph/model_validation.py +58 -0
- package/graph-provider/fuli_graph/models.py +1179 -0
- package/graph-provider/fuli_graph/personal_project_access.py +29 -0
- package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
- package/graph-provider/fuli_graph/project_action_models.py +142 -0
- package/graph-provider/fuli_graph/project_knowledge.py +707 -0
- package/graph-provider/fuli_graph/project_management.py +251 -0
- package/graph-provider/fuli_graph/provider_values.py +40 -0
- package/graph-provider/fuli_graph/runtime.py +350 -0
- package/graph-provider/fuli_graph/search_projection.py +156 -0
- package/graph-provider/fuli_graph/store.py +24 -0
- package/graph-provider/fuli_graph/store_identity.py +237 -0
- package/graph-provider/fuli_graph/store_knowledge.py +447 -0
- package/graph-provider/fuli_graph/store_projects.py +242 -0
- package/graph-provider/fuli_graph/store_publication.py +370 -0
- package/graph-provider/fuli_graph/store_records.py +134 -0
- package/graph-provider/pyproject.toml +35 -0
- package/npm-shrinkwrap.json +6045 -0
- package/package.json +99 -0
- package/skills/capturing-session-knowledge/SKILL.md +226 -0
- package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
- package/skills/grilling-project/SKILL.md +129 -0
- package/skills/grilling-project/agents/openai.yaml +4 -0
- package/skills/grilling-project/references/project-profile.md +105 -0
- package/src/agent/candidate-read-service.js +53 -0
- package/src/agent/context-pack-service.js +83 -0
- package/src/agent/public-read-policy.js +39 -0
- package/src/agent/public-read-service.js +69 -0
- package/src/agent/read-projection.js +107 -0
- package/src/agent/search-service.js +61 -0
- package/src/agent/space-boundary.js +33 -0
- package/src/agent-tools/graph-definitions.js +479 -0
- package/src/agent-tools/graph-handlers.js +96 -0
- package/src/agent-tools/schema.js +37 -0
- package/src/agent-tools.js +16 -0
- package/src/app/application-error.js +13 -0
- package/src/app/bootstrap.js +8 -0
- package/src/app/create-application.js +143 -0
- package/src/app/state-service.js +38 -0
- package/src/candidates.js +78 -0
- package/src/classifier.js +63 -0
- package/src/cli/command-arguments.js +14 -0
- package/src/cli/command-registry.js +78 -0
- package/src/cli/commands/candidates.js +17 -0
- package/src/cli/commands/ingestion.js +32 -0
- package/src/cli/commands/reads.js +50 -0
- package/src/cli/commands/spaces.js +20 -0
- package/src/cli/invocation.js +47 -0
- package/src/cli/local-runtime-command.js +94 -0
- package/src/cli/local-runtime-options.js +64 -0
- package/src/cli/migrate-command.js +71 -0
- package/src/cli/migration-destination.js +117 -0
- package/src/cli/setup-agent-selection.js +195 -0
- package/src/cli/setup-command.js +85 -0
- package/src/cli/uninstall-command.js +67 -0
- package/src/cli.js +90 -0
- package/src/context-pack.js +206 -0
- package/src/defaults.js +1 -0
- package/src/extractor.js +77 -0
- package/src/fact-history.js +39 -0
- package/src/facts/fact-service.js +89 -0
- package/src/graphiti/agent-access-policy.js +88 -0
- package/src/graphiti/capture-policy.js +84 -0
- package/src/graphiti/federated-application.js +1292 -0
- package/src/graphiti/personal-context.js +29 -0
- package/src/graphiti/project-path-context.js +159 -0
- package/src/graphiti/provider-client.js +288 -0
- package/src/graphiti/runtime-config.js +85 -0
- package/src/graphiti/setup.js +43 -0
- package/src/graphiti/source-marker.js +142 -0
- package/src/http/api-router.js +133 -0
- package/src/http/bounded-json-array.js +19 -0
- package/src/http/error-mapping.js +36 -0
- package/src/http/graph-api-router.js +257 -0
- package/src/http/lens-route.js +50 -0
- package/src/http/request-policy.js +41 -0
- package/src/http/response.js +29 -0
- package/src/http/static-handler.js +55 -0
- package/src/ingestion.js +161 -0
- package/src/lens/lens-correction-service.js +81 -0
- package/src/lens/lens-fact-writer.js +85 -0
- package/src/lens/lens-input.js +62 -0
- package/src/lens/lens-query-projection.js +87 -0
- package/src/lens/lens-query-ranking.js +62 -0
- package/src/lens/lens-query.js +143 -0
- package/src/lens/lens-resource-projection.js +93 -0
- package/src/lens/lens-resource-selection.js +45 -0
- package/src/lens/lens-resource-service.js +103 -0
- package/src/lens/lens-search-query.js +122 -0
- package/src/lens/lens-service.js +183 -0
- package/src/local-runtime/lifecycle.js +254 -0
- package/src/mcp/create-mcp-server.js +85 -0
- package/src/mcp/instructions.js +10 -0
- package/src/mcp/project-action-preview-tokens.js +72 -0
- package/src/mcp/runtime.js +71 -0
- package/src/mcp/tool-annotations.js +63 -0
- package/src/mcp/tool-result.js +167 -0
- package/src/mcp/tool-schema.js +60 -0
- package/src/mcp-server.js +76 -0
- package/src/models.js +44 -0
- package/src/observer.js +83 -0
- package/src/package-metadata.js +6 -0
- package/src/project-rules.js +33 -0
- package/src/publication/canonical-json.js +39 -0
- package/src/publication/outbox-service.js +46 -0
- package/src/publication/publication-envelope-schema.js +75 -0
- package/src/publication/publication-envelope.js +37 -0
- package/src/publication/publication-policy.js +82 -0
- package/src/publication/publication-service.js +48 -0
- package/src/router.js +58 -0
- package/src/runtime-options.js +139 -0
- package/src/security/episode-visibility.js +14 -0
- package/src/security/fact-visibility.js +15 -0
- package/src/security/sensitive-content.js +18 -0
- package/src/security/source-metadata-policy.js +8 -0
- package/src/server/application-lifecycle.js +34 -0
- package/src/server/listen.js +24 -0
- package/src/server.js +135 -0
- package/src/setup/agent-installation-status.js +97 -0
- package/src/setup/agents.js +123 -0
- package/src/setup/codex-bootstrap.js +118 -0
- package/src/setup/codex-config.js +122 -0
- package/src/setup/config-backup.js +22 -0
- package/src/setup/container-runtime.js +388 -0
- package/src/setup/cursor-config.js +61 -0
- package/src/setup/graph-runtime.js +360 -0
- package/src/setup/node-runtime.js +24 -0
- package/src/setup/options.js +91 -0
- package/src/setup/paths.js +47 -0
- package/src/setup/session-skill.js +91 -0
- package/src/setup/setup.js +85 -0
- package/src/setup/uninstall.js +90 -0
- package/src/storage/episode-evidence-preview.js +100 -0
- package/src/storage/fact-search-order.js +18 -0
- package/src/storage/file/episode-evidence.js +37 -0
- package/src/storage/file/episode-index.js +59 -0
- package/src/storage/file/fact-search.js +60 -0
- package/src/storage/file-store.js +437 -0
- package/src/storage/import-json.js +70 -0
- package/src/storage/json-file.js +41 -0
- package/src/storage/legacy-record-validation.js +152 -0
- package/src/storage/legacy-snapshot-validation.js +32 -0
- package/src/storage/migrate.js +128 -0
- package/src/storage/migrations/001-initial.sql +83 -0
- package/src/storage/migrations/002-storage-integrity.sql +113 -0
- package/src/storage/record-validation.js +46 -0
- package/src/storage/snapshot-defaults.js +54 -0
- package/src/storage/sqlite/candidates-repository.js +84 -0
- package/src/storage/sqlite/episode-evidence-repository.js +154 -0
- package/src/storage/sqlite/episode-privacy-sql.js +33 -0
- package/src/storage/sqlite/episodes-repository.js +52 -0
- package/src/storage/sqlite/fact-search-repository.js +162 -0
- package/src/storage/sqlite/facts-repository.js +146 -0
- package/src/storage/sqlite/imports-repository.js +39 -0
- package/src/storage/sqlite/mapper.js +84 -0
- package/src/storage/sqlite/open-database.js +15 -0
- package/src/storage/sqlite/outbox-repository.js +103 -0
- package/src/storage/sqlite/snapshot-repository.js +69 -0
- package/src/storage/sqlite/spaces-repository.js +52 -0
- package/src/storage/sqlite/subscriptions-repository.js +54 -0
- package/src/storage/sqlite-store.js +159 -0
- package/src/storage/stable-json.js +24 -0
- package/src/storage/store-port.js +20 -0
- package/src/store.js +2 -0
- package/src/workspace-protocol/array-validation.js +19 -0
- package/src/workspace-protocol/base64url-contract.js +27 -0
- package/src/workspace-protocol/constants.js +37 -0
- package/src/workspace-protocol/error-contract.js +24 -0
- package/src/workspace-protocol/fact-contract.js +110 -0
- package/src/workspace-protocol/governance-contract.js +47 -0
- package/src/workspace-protocol/index.js +53 -0
- package/src/workspace-protocol/json-budget.js +19 -0
- package/src/workspace-protocol/manifest-contract.js +186 -0
- package/src/workspace-protocol/publication-contract.js +212 -0
- package/src/workspace-protocol/query-contract.js +149 -0
- package/src/workspace-protocol/scalar-contract.js +192 -0
- package/src/workspace-protocol/sync-contract.js +184 -0
- package/src/workspace-protocol/text-contract.js +39 -0
- package/src/workspace-protocol/validation.js +66 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { connectCursor, disconnectCursor } from './cursor-config.js';
|
|
7
|
+
import { connectCodex, disconnectCodex } from './codex-config.js';
|
|
8
|
+
|
|
9
|
+
export function discoverAgents({
|
|
10
|
+
platform = process.platform,
|
|
11
|
+
env = process.env,
|
|
12
|
+
homeDir = homedir(),
|
|
13
|
+
fileExists = existsSync,
|
|
14
|
+
commandExists = (command) => defaultCommandExists(command, platform)
|
|
15
|
+
} = {}) {
|
|
16
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
17
|
+
const codexHome = nonEmpty(env.CODEX_HOME) ?? pathApi.join(homeDir, '.codex');
|
|
18
|
+
const skillName = 'capturing-session-knowledge';
|
|
19
|
+
const projectSkillName = 'grilling-project';
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
id: 'codex',
|
|
23
|
+
label: 'Codex',
|
|
24
|
+
command: 'codex',
|
|
25
|
+
configPath: pathApi.join(codexHome, 'config.toml'),
|
|
26
|
+
globalInstructionsPath: pathApi.join(codexHome, 'AGENTS.md'),
|
|
27
|
+
globalInstructionsOverridePath: pathApi.join(codexHome, 'AGENTS.override.md'),
|
|
28
|
+
skillPath: pathApi.join(homeDir, '.agents', 'skills', skillName),
|
|
29
|
+
projectSkillPath: pathApi.join(homeDir, '.agents', 'skills', projectSkillName),
|
|
30
|
+
available: Boolean(commandExists('codex') || fileExists(pathApi.join(codexHome, 'config.toml')))
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'claude-code',
|
|
34
|
+
label: 'Claude Code',
|
|
35
|
+
command: 'claude',
|
|
36
|
+
configPath: pathApi.join(homeDir, '.claude.json'),
|
|
37
|
+
skillPath: pathApi.join(homeDir, '.claude', 'skills', skillName),
|
|
38
|
+
projectSkillPath: pathApi.join(homeDir, '.claude', 'skills', projectSkillName),
|
|
39
|
+
available: Boolean(commandExists('claude'))
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'cursor',
|
|
43
|
+
label: 'Cursor',
|
|
44
|
+
command: 'cursor',
|
|
45
|
+
configPath: pathApi.join(homeDir, '.cursor', 'mcp.json'),
|
|
46
|
+
skillPath: pathApi.join(homeDir, '.cursor', 'skills', skillName),
|
|
47
|
+
projectSkillPath: pathApi.join(homeDir, '.cursor', 'skills', projectSkillName),
|
|
48
|
+
available: Boolean(commandExists('cursor'))
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildAgentCommands(agent, context) {
|
|
54
|
+
const serverArgs = [
|
|
55
|
+
context.mcpServerPath,
|
|
56
|
+
'--runtime-config',
|
|
57
|
+
context.runtimeConfigPath
|
|
58
|
+
];
|
|
59
|
+
const mcpArgs = [context.nodePath, ...serverArgs];
|
|
60
|
+
if (agent.id === 'codex') {
|
|
61
|
+
return {
|
|
62
|
+
remove: [agent.command, ['mcp', 'remove', 'fuli']],
|
|
63
|
+
add: [agent.command, ['mcp', 'add', 'fuli', '--', ...mcpArgs]]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (agent.id === 'claude-code') {
|
|
67
|
+
return {
|
|
68
|
+
remove: [agent.command, ['mcp', 'remove', '--scope', 'user', 'fuli']],
|
|
69
|
+
add: [agent.command, ['mcp', 'add', '--scope', 'user', 'fuli', '--', ...mcpArgs]]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
throw new TypeError(`Unsupported agent: ${agent.id}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function connectAgent(agent, context, {
|
|
76
|
+
runCommand = defaultRunCommand,
|
|
77
|
+
connectCodexConfig = connectCodex,
|
|
78
|
+
connectCursorConfig = connectCursor
|
|
79
|
+
} = {}) {
|
|
80
|
+
if (agent.id === 'codex') return connectCodexConfig(agent, context);
|
|
81
|
+
if (agent.id === 'cursor') return connectCursorConfig(agent, context);
|
|
82
|
+
|
|
83
|
+
const commands = buildAgentCommands(agent, context);
|
|
84
|
+
runCommand(...commands.remove);
|
|
85
|
+
const result = runCommand(...commands.add);
|
|
86
|
+
if (result?.status !== 0) {
|
|
87
|
+
throw new Error(`Could not connect ${agent.label} to Fuli`);
|
|
88
|
+
}
|
|
89
|
+
return { id: agent.id, label: agent.label, status: 'connected' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function disconnectAgent(agent, {
|
|
93
|
+
disconnectCodexConfig = disconnectCodex,
|
|
94
|
+
disconnectJsonConfig = disconnectCursor
|
|
95
|
+
} = {}) {
|
|
96
|
+
if (agent.id === 'codex') return disconnectCodexConfig(agent);
|
|
97
|
+
if (agent.id === 'cursor' || agent.id === 'claude-code') {
|
|
98
|
+
return disconnectJsonConfig(agent);
|
|
99
|
+
}
|
|
100
|
+
throw new TypeError(`Unsupported agent: ${agent.id}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function defaultCommandExists(command, platform = process.platform) {
|
|
104
|
+
const locator = platform === 'win32' ? 'where.exe' : 'which';
|
|
105
|
+
const result = spawnSync(locator, [command], {
|
|
106
|
+
encoding: 'utf8',
|
|
107
|
+
windowsHide: true,
|
|
108
|
+
stdio: 'ignore'
|
|
109
|
+
});
|
|
110
|
+
return result.status === 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function defaultRunCommand(command, args) {
|
|
114
|
+
return spawnSync(command, args, {
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
windowsHide: true,
|
|
117
|
+
stdio: 'pipe'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function nonEmpty(value) {
|
|
122
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
123
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync
|
|
9
|
+
} from 'node:fs';
|
|
10
|
+
import { basename, dirname, join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
const BLOCK_START = '<!-- BEGIN FULI MANAGED INSTRUCTIONS -->';
|
|
13
|
+
const BLOCK_END = '<!-- END FULI MANAGED INSTRUCTIONS -->';
|
|
14
|
+
|
|
15
|
+
const GLOBAL_INSTRUCTIONS = `${BLOCK_START}
|
|
16
|
+
At the start of every user task, before any other tool or answer, call exactly
|
|
17
|
+
\`get_collaboration_preferences\` with \`projectPath\`=current working directory. Never substitute
|
|
18
|
+
project action or Fuli tool.
|
|
19
|
+
Apply all returned \`effective_preferences\` before answers or tools. For write tools, enforce
|
|
20
|
+
them in the actual payload; mentioning them later is not compliance.
|
|
21
|
+
Before using a relevant \`deferred_conflict\`, call
|
|
22
|
+
\`resolve_deferred_preference_conflict\`; ignore unrelated ones.
|
|
23
|
+
The current request wins on conflict.
|
|
24
|
+
${BLOCK_END}`;
|
|
25
|
+
|
|
26
|
+
export function installCodexBootstrap(agent, _context, {
|
|
27
|
+
fileExists = existsSync,
|
|
28
|
+
readText = defaultReadText,
|
|
29
|
+
writeText = writeTextAtomic
|
|
30
|
+
} = {}) {
|
|
31
|
+
if (!agent?.globalInstructionsPath) return { changed: false };
|
|
32
|
+
const instructionsPath = activeGlobalInstructionsPath(agent, {
|
|
33
|
+
fileExists,
|
|
34
|
+
readText
|
|
35
|
+
});
|
|
36
|
+
const current = readText(instructionsPath);
|
|
37
|
+
const next = replaceFuliGlobalInstructions(current);
|
|
38
|
+
if (next === current) return { changed: false };
|
|
39
|
+
writeText(instructionsPath, next);
|
|
40
|
+
return { changed: true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function removeCodexBootstrap(agent, {
|
|
44
|
+
fileExists = existsSync,
|
|
45
|
+
readText = defaultReadText,
|
|
46
|
+
writeText = writeTextAtomic
|
|
47
|
+
} = {}) {
|
|
48
|
+
let changed = false;
|
|
49
|
+
for (const filePath of [
|
|
50
|
+
agent.globalInstructionsPath,
|
|
51
|
+
agent.globalInstructionsOverridePath
|
|
52
|
+
].filter(Boolean)) {
|
|
53
|
+
if (!fileExists(filePath)) continue;
|
|
54
|
+
const current = readText(filePath);
|
|
55
|
+
const next = removeFuliGlobalInstructions(current);
|
|
56
|
+
if (next === current) continue;
|
|
57
|
+
writeText(filePath, next);
|
|
58
|
+
changed = true;
|
|
59
|
+
}
|
|
60
|
+
return { changed };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isCodexBootstrapCurrent(agent, _context, {
|
|
64
|
+
fileExists = existsSync,
|
|
65
|
+
readText = defaultReadText
|
|
66
|
+
} = {}) {
|
|
67
|
+
if (!agent?.globalInstructionsPath) return false;
|
|
68
|
+
try {
|
|
69
|
+
const instructionsPath = activeGlobalInstructionsPath(agent, {
|
|
70
|
+
fileExists,
|
|
71
|
+
readText
|
|
72
|
+
});
|
|
73
|
+
const current = readText(instructionsPath);
|
|
74
|
+
return replaceFuliGlobalInstructions(current) === current;
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function replaceFuliGlobalInstructions(source) {
|
|
81
|
+
const withoutBlock = removeFuliGlobalInstructions(source).trimEnd();
|
|
82
|
+
return `${withoutBlock ? `${withoutBlock}\n\n` : ''}${GLOBAL_INSTRUCTIONS}\n`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function removeFuliGlobalInstructions(source) {
|
|
86
|
+
const start = source.indexOf(BLOCK_START);
|
|
87
|
+
if (start === -1) return source;
|
|
88
|
+
const end = source.indexOf(BLOCK_END, start);
|
|
89
|
+
if (end === -1) return source;
|
|
90
|
+
const before = source.slice(0, start).trimEnd();
|
|
91
|
+
const after = source.slice(end + BLOCK_END.length).trimStart();
|
|
92
|
+
if (before && after) return `${before}\n\n${after}`;
|
|
93
|
+
if (before) return `${before}\n`;
|
|
94
|
+
return after;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function activeGlobalInstructionsPath(agent, { fileExists, readText }) {
|
|
98
|
+
const override = agent.globalInstructionsOverridePath;
|
|
99
|
+
if (override && fileExists(override) && readText(override).trim()) return override;
|
|
100
|
+
return agent.globalInstructionsPath;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function defaultReadText(filePath) {
|
|
104
|
+
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function writeTextAtomic(filePath, value) {
|
|
108
|
+
const directory = dirname(filePath);
|
|
109
|
+
const temporary = join(directory, `.${basename(filePath)}.${randomUUID()}.tmp`);
|
|
110
|
+
mkdirSync(directory, { recursive: true });
|
|
111
|
+
try {
|
|
112
|
+
writeFileSync(temporary, value, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
113
|
+
renameSync(temporary, filePath);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
rmSync(temporary, { force: true });
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync
|
|
9
|
+
} from 'node:fs';
|
|
10
|
+
import { basename, dirname, join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
installCodexBootstrap,
|
|
14
|
+
removeCodexBootstrap
|
|
15
|
+
} from './codex-bootstrap.js';
|
|
16
|
+
|
|
17
|
+
export function connectCodex(agent, context, {
|
|
18
|
+
readConfig = readText,
|
|
19
|
+
writeConfig = writeTextAtomic,
|
|
20
|
+
installBootstrap = installCodexBootstrap
|
|
21
|
+
} = {}) {
|
|
22
|
+
const current = readConfig(agent.configPath);
|
|
23
|
+
const next = replaceFuliTable(current, {
|
|
24
|
+
command: context.nodePath,
|
|
25
|
+
args: [context.mcpServerPath, '--runtime-config', context.runtimeConfigPath]
|
|
26
|
+
});
|
|
27
|
+
const bootstrap = installBootstrap(agent, context);
|
|
28
|
+
const registrationChanged = next !== current;
|
|
29
|
+
if (registrationChanged) writeConfig(agent.configPath, next);
|
|
30
|
+
return {
|
|
31
|
+
id: agent.id,
|
|
32
|
+
label: agent.label,
|
|
33
|
+
status: 'connected',
|
|
34
|
+
newTaskRequired: registrationChanged || bootstrap.changed === true
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function disconnectCodex(agent, {
|
|
39
|
+
fileExists = existsSync,
|
|
40
|
+
readConfig = readText,
|
|
41
|
+
writeConfig = writeTextAtomic,
|
|
42
|
+
removeBootstrap = removeCodexBootstrap
|
|
43
|
+
} = {}) {
|
|
44
|
+
const bootstrap = removeBootstrap(agent);
|
|
45
|
+
let registrationChanged = false;
|
|
46
|
+
if (fileExists(agent.configPath)) {
|
|
47
|
+
const current = readConfig(agent.configPath);
|
|
48
|
+
const next = removeFuliTables(current);
|
|
49
|
+
if (next !== current) {
|
|
50
|
+
writeConfig(agent.configPath, next);
|
|
51
|
+
registrationChanged = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
id: agent.id,
|
|
56
|
+
label: agent.label,
|
|
57
|
+
status: registrationChanged || bootstrap.changed ? 'disconnected' : 'not_connected'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function replaceFuliTable(source, { command, args }) {
|
|
62
|
+
const withoutFuli = removeFuliTables(source);
|
|
63
|
+
const lines = withoutFuli.replaceAll('\r\n', '\n').split('\n');
|
|
64
|
+
while (lines.length && !lines.at(-1).trim()) lines.pop();
|
|
65
|
+
const block = [
|
|
66
|
+
'[mcp_servers.fuli]',
|
|
67
|
+
`command = ${tomlString(command)}`,
|
|
68
|
+
`args = [${args.map(tomlString).join(', ')}]`,
|
|
69
|
+
'enabled = true',
|
|
70
|
+
'required = true',
|
|
71
|
+
'startup_timeout_sec = 30',
|
|
72
|
+
'tool_timeout_sec = 120',
|
|
73
|
+
'default_tools_approval_mode = "auto"'
|
|
74
|
+
];
|
|
75
|
+
return `${[...lines, ...(lines.length ? [''] : []), ...block].join('\n')}\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function removeFuliTables(source) {
|
|
79
|
+
const lines = source.replaceAll('\r\n', '\n').split('\n');
|
|
80
|
+
const kept = [];
|
|
81
|
+
let skipping = false;
|
|
82
|
+
let removed = false;
|
|
83
|
+
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
const header = /^\s*\[([^\]]+)]\s*(?:#.*)?$/.exec(line)?.[1]?.trim();
|
|
86
|
+
if (header) {
|
|
87
|
+
skipping = isFuliTable(header);
|
|
88
|
+
if (skipping) removed = true;
|
|
89
|
+
}
|
|
90
|
+
if (!skipping) kept.push(line);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!removed) return source;
|
|
94
|
+
while (kept.length && !kept.at(-1).trim()) kept.pop();
|
|
95
|
+
return kept.length ? `${kept.join('\n')}\n` : '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isFuliTable(header) {
|
|
99
|
+
return /^mcp_servers\.(?:fuli|"fuli"|'fuli')(?:\.|$)/.test(header);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function tomlString(value) {
|
|
103
|
+
if (typeof value !== 'string' || !value) throw new TypeError('Codex MCP values must be nonempty');
|
|
104
|
+
return JSON.stringify(value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readText(filePath) {
|
|
108
|
+
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function writeTextAtomic(filePath, value) {
|
|
112
|
+
const directory = dirname(filePath);
|
|
113
|
+
const tempPath = join(directory, `.${basename(filePath)}.${randomUUID()}.tmp`);
|
|
114
|
+
mkdirSync(directory, { recursive: true });
|
|
115
|
+
try {
|
|
116
|
+
writeFileSync(tempPath, value, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
117
|
+
renameSync(tempPath, filePath);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
rmSync(tempPath, { force: true });
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync
|
|
5
|
+
} from 'node:fs';
|
|
6
|
+
import { extname, join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export function backupAgentConfig(agent, {
|
|
9
|
+
backupDir,
|
|
10
|
+
now = () => new Date(),
|
|
11
|
+
fileExists = existsSync,
|
|
12
|
+
makeDirectory = mkdirSync,
|
|
13
|
+
copyFile = copyFileSync
|
|
14
|
+
}) {
|
|
15
|
+
if (!fileExists(agent.configPath)) return null;
|
|
16
|
+
makeDirectory(backupDir, { recursive: true });
|
|
17
|
+
const timestamp = now().toISOString().replaceAll(':', '-').replaceAll('.', '-');
|
|
18
|
+
const extension = extname(agent.configPath) || '.bak';
|
|
19
|
+
const backupPath = join(backupDir, `${agent.id}-${timestamp}${extension}`);
|
|
20
|
+
copyFile(agent.configPath, backupPath);
|
|
21
|
+
return backupPath;
|
|
22
|
+
}
|