evolcore 0.0.29-test.0 → 0.0.30
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/CHANGELOG.md +7 -0
- package/dist/agents/codex-runner.js +61 -14
- package/dist/aun/aid/agentmd.js +224 -19
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +70 -4
- package/dist/aun/group/index.js +1 -0
- package/dist/{core → aun}/group/invite-service.js +4 -4
- package/dist/channels/daemon.js +1 -1
- package/dist/cli/agent-command.js +7 -7
- package/dist/cli/aun-commands.js +97 -37
- package/dist/cli/data-command.js +2 -1
- package/dist/cli/managed-command-guard.js +2 -2
- package/dist/cli/model.js +1 -1
- package/dist/cli/response.js +1 -1
- package/dist/cli/trigger-command.js +52 -29
- package/dist/config/boot-log.js +95 -1
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/peer-role-resolver.js +21 -0
- package/dist/core/agent-ipc-registration.js +409 -0
- package/dist/core/agent-runtime.js +435 -0
- package/dist/core/auth/delegation-authorization.js +58 -0
- package/dist/core/auth/operation-catalog.js +8 -0
- package/dist/core/bootstrap-service.js +65 -1
- package/dist/core/channel-runtime.js +435 -0
- package/dist/core/command/command-handler.js +4 -1
- package/dist/core/daemon-bootstrap.js +151 -0
- package/dist/core/daemon-control.js +205 -0
- package/dist/core/daemon-lifecycle.js +225 -0
- package/dist/core/data-migration.js +4 -1
- package/dist/core/handoff/ipc-registration.js +156 -0
- package/dist/core/ipc-registration.js +457 -0
- package/dist/core/memory/consolidation-trigger.js +8 -0
- package/dist/core/memory/consolidation.js +838 -0
- package/dist/core/message/aun-ipc-sender.js +226 -0
- package/dist/core/message/outbound.js +165 -0
- package/dist/core/permission/approval-gateway.js +13 -0
- package/dist/core/permission/ec-command-parser.js +80 -35
- package/dist/core/permission/sandbox-runtime.js +23 -2
- package/dist/core/permission/tool-error-code.js +1 -0
- package/dist/core/permission/tool-policy.js +61 -24
- package/dist/core/session/session-idle-monitor.js +17 -0
- package/dist/index.js +352 -4841
- package/dist/ipc.js +2 -1
- package/dist/stats/db.js +24 -0
- package/dist/stats/index.js +1 -1
- package/dist/stats/writer.js +25 -1
- package/dist/trigger/application-service.js +157 -0
- package/dist/trigger/authorization.js +371 -0
- package/dist/trigger/ipc-service.js +406 -0
- package/dist/trigger/local-files.js +133 -0
- package/dist/trigger/manager.js +235 -22
- package/dist/trigger/scheduler.js +30 -5
- package/dist/trigger/script-executor.js +176 -39
- package/dist/trigger/script-sandbox.js +458 -0
- package/dist/trigger/system-definitions.js +188 -0
- package/dist/trigger/validation.js +0 -3
- package/dist/utils/aid-bind.js +1 -1
- package/dist/utils/evolcore-version.js +74 -0
- package/dist/utils/logger.js +16 -0
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/model.md +3 -2
- package/kits/docs/evolcore/rpc.md +4 -0
- package/kits/docs/evolcore/trigger.md +15 -1
- package/kits/schemas/relation-config.schema.9.json +1 -1
- package/kits/templates/system-fragments/commands.md +0 -1
- package/package.json +1 -1
- package/dist/core/memory/launch-consolidation.js +0 -395
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { resolveCodexCliPath } from '../agents/baseagent.js';
|
|
8
|
+
import { ensureSessionRuntimeDir } from '../cli/task-context.js';
|
|
9
|
+
import { resolveBubblewrapPath } from '../core/permission/sandbox-runtime.js';
|
|
10
|
+
import { classifyProtectedPath, getExistingHClassMaskTargets, getExistingLClassReadOnlyTargets, isSameOrDescendant, } from '../core/permission/protected-paths.js';
|
|
11
|
+
import { resolveRoot } from '../paths.js';
|
|
12
|
+
import { buildCliLaunchCommand, killProcess, resolveCommandPath } from '../utils/cross-platform.js';
|
|
13
|
+
import { TRIGGER_LOCAL_FILE_LIMIT } from './local-files.js';
|
|
14
|
+
const SAFE_ENVIRONMENT_KEYS = [
|
|
15
|
+
'ComSpec', 'COMSPEC', 'LANG', 'LC_ALL', 'OS', 'PATH', 'PATHEXT',
|
|
16
|
+
'ProgramData', 'ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432',
|
|
17
|
+
'SystemDrive', 'SystemRoot', 'WINDIR',
|
|
18
|
+
];
|
|
19
|
+
const SYSTEM_READ_ROOTS = ['/usr/bin', '/usr/lib', '/usr/lib64', '/usr/local/bin', '/usr/local/lib', '/usr/share/locale', '/usr/share/zoneinfo', '/bin', '/sbin', '/lib', '/lib64'];
|
|
20
|
+
const SYSTEM_READ_FILES = ['/etc/ld.so.cache', '/etc/localtime', '/etc/passwd', '/etc/group'];
|
|
21
|
+
const CONTROL_DIRECTORY_NAMES = ['agents', 'AIDs', 'aids', 'data', 'CA', '.snapshots', 'logs', 'backups'];
|
|
22
|
+
const WINDOWS_SANDBOX_SETUP_VERSION = 5;
|
|
23
|
+
const WINDOWS_SANDBOX_ENV_EXCLUDES = [
|
|
24
|
+
'CODEX_HOME',
|
|
25
|
+
'EVOLCORE_DELEGATION_TOKEN',
|
|
26
|
+
'EVOLCORE_CODEX_SESSION_TOKEN',
|
|
27
|
+
'EVOLCORE_TASK_RUNTIME_CONTEXT',
|
|
28
|
+
'EVOLCORE_INSTANCE_SOCKET',
|
|
29
|
+
'EVOLCORE_DAEMON_SOCKET',
|
|
30
|
+
'EVOLCORE_IPC_SOCKET',
|
|
31
|
+
'OPENAI_API_KEY',
|
|
32
|
+
'CODEX_API_KEY',
|
|
33
|
+
'ANTHROPIC_API_KEY',
|
|
34
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
35
|
+
'GEMINI_API_KEY',
|
|
36
|
+
'GOOGLE_API_KEY',
|
|
37
|
+
'ECAGENT_API_KEY',
|
|
38
|
+
];
|
|
39
|
+
export function buildTriggerScriptLaunch(input) {
|
|
40
|
+
const authorization = input.executionAuthorization;
|
|
41
|
+
if (!authorization?.allowed || !authorization.scope)
|
|
42
|
+
throw new Error('Trigger execution authorization is missing');
|
|
43
|
+
const runtime = resolveRuntime(input.trigger.execution.script?.runtime || '');
|
|
44
|
+
if (authorization.scope === 'daemon') {
|
|
45
|
+
const launch = buildCliLaunchCommand(runtime, [input.scriptPath, ...input.scriptArgs]);
|
|
46
|
+
return { ...launch, cwd: input.projectPath, env: process.env };
|
|
47
|
+
}
|
|
48
|
+
if (!authorization.permissionMode || authorization.permissionMode === 'fullaccess') {
|
|
49
|
+
throw new Error('scoped Trigger execution requires an effective non-fullaccess permission mode');
|
|
50
|
+
}
|
|
51
|
+
if (process.platform !== 'linux' && process.platform !== 'win32') {
|
|
52
|
+
throw new Error('scoped Trigger script isolation is unavailable on this host');
|
|
53
|
+
}
|
|
54
|
+
if (process.platform === 'win32' && path.extname(runtime).toLowerCase() !== '.exe') {
|
|
55
|
+
throw new Error('scoped Windows Trigger scripts require a native executable runtime');
|
|
56
|
+
}
|
|
57
|
+
const projectPath = resolveRealDirectory(input.projectPath, 'Trigger project path');
|
|
58
|
+
const triggerDir = resolveRealDirectory(input.triggerDir, 'Trigger directory');
|
|
59
|
+
const root = resolveRealDirectory(resolveRoot(), 'EvolCore root');
|
|
60
|
+
const projectClass = classifyProtectedPath(projectPath, { root, selfAid: input.trigger.agentAid });
|
|
61
|
+
if (projectClass !== 'ordinary')
|
|
62
|
+
throw new Error(`scoped Trigger project path is protected: ${projectPath}`);
|
|
63
|
+
if ((isSameOrDescendant(root, projectPath) && !samePath(projectPath, root))
|
|
64
|
+
|| isSameOrDescendant(os.homedir(), projectPath)) {
|
|
65
|
+
throw new Error('scoped Trigger project must not contain the EvolCore root or the host home directory');
|
|
66
|
+
}
|
|
67
|
+
const runtimeDir = path.dirname(runtime);
|
|
68
|
+
if (classifyProtectedPath(runtime, { root, selfAid: input.trigger.agentAid }) !== 'ordinary'
|
|
69
|
+
|| isSameOrDescendant(root, runtimeDir) && !isSameOrDescendant(runtimeDir, root)
|
|
70
|
+
|| samePath(runtimeDir, path.parse(runtimeDir).root)) {
|
|
71
|
+
throw new Error('scoped Trigger runtime path is protected or too broad');
|
|
72
|
+
}
|
|
73
|
+
const scriptBytes = readScriptSnapshot(input.scriptPath, triggerDir);
|
|
74
|
+
const windowsBackend = process.platform === 'win32' ? resolveWindowsBackend() : undefined;
|
|
75
|
+
if (windowsBackend && (isSameOrDescendant(windowsBackend.home, projectPath)
|
|
76
|
+
|| isSameOrDescendant(projectPath, windowsBackend.home)
|
|
77
|
+
|| isSameOrDescendant(windowsBackend.home, runtimeDir)
|
|
78
|
+
|| isSameOrDescendant(runtimeDir, windowsBackend.home))) {
|
|
79
|
+
throw new Error('scoped Trigger project and runtime must not expose the Codex control home');
|
|
80
|
+
}
|
|
81
|
+
const bubblewrap = process.platform === 'linux' ? resolveBubblewrapPath() : undefined;
|
|
82
|
+
if (process.platform === 'linux' && !bubblewrap) {
|
|
83
|
+
throw new Error('bubblewrap is required for scoped Trigger script isolation');
|
|
84
|
+
}
|
|
85
|
+
const runtimeRoot = ensureSessionRuntimeDir(`trigger:${input.trigger.agentAid}:${input.trigger.id}`);
|
|
86
|
+
if (!runtimeRoot)
|
|
87
|
+
throw new Error('scoped Trigger runtime directory is unavailable');
|
|
88
|
+
assertRealDirectory(runtimeRoot, 'scoped Trigger runtime root');
|
|
89
|
+
if (isSameOrDescendant(path.dirname(runtimeRoot), projectPath)) {
|
|
90
|
+
throw new Error('scoped Trigger project must not contain the shared temporary directory');
|
|
91
|
+
}
|
|
92
|
+
const runDir = fs.mkdtempSync(path.join(runtimeRoot, 'run-'));
|
|
93
|
+
const cleanup = () => {
|
|
94
|
+
if (!isSameOrDescendant(runDir, runtimeRoot) || samePath(runDir, runtimeRoot))
|
|
95
|
+
return;
|
|
96
|
+
try {
|
|
97
|
+
assertRealDirectory(runDir, 'scoped Trigger run directory');
|
|
98
|
+
fs.rmSync(runDir, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
const workDir = path.join(runDir, 'work');
|
|
104
|
+
const snapshotDir = path.join(runDir, 'script');
|
|
105
|
+
fs.mkdirSync(workDir, { mode: 0o700 });
|
|
106
|
+
fs.mkdirSync(snapshotDir, { mode: 0o700 });
|
|
107
|
+
const scriptPath = path.join(snapshotDir, path.basename(input.scriptPath));
|
|
108
|
+
fs.writeFileSync(scriptPath, scriptBytes, { flag: 'wx', mode: 0o400 });
|
|
109
|
+
preserveNodeModuleType(runtime, input.scriptPath, snapshotDir);
|
|
110
|
+
const env = buildSandboxEnvironment(workDir);
|
|
111
|
+
const blockedPaths = protectedScriptPaths(root, input.trigger.agentAid);
|
|
112
|
+
const scriptArgs = [scriptPath, ...input.scriptArgs];
|
|
113
|
+
if (windowsBackend) {
|
|
114
|
+
const systemRoot = process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows';
|
|
115
|
+
const readRoots = [
|
|
116
|
+
path.join(systemRoot, 'System32'),
|
|
117
|
+
path.join(systemRoot, 'WinSxS'),
|
|
118
|
+
runtime,
|
|
119
|
+
runtimeDir,
|
|
120
|
+
process.execPath,
|
|
121
|
+
path.dirname(process.execPath),
|
|
122
|
+
snapshotDir,
|
|
123
|
+
].filter(value => fs.existsSync(value));
|
|
124
|
+
const entries = [
|
|
125
|
+
{ path: { type: 'special', value: { kind: 'root' } }, access: 'deny' },
|
|
126
|
+
...readRoots.map(value => filesystemEntry(value, 'read')),
|
|
127
|
+
filesystemEntry(projectPath, authorization.permissionMode === 'readonly' ? 'read' : 'write'),
|
|
128
|
+
filesystemEntry(workDir, 'write'),
|
|
129
|
+
...blockedPaths.map(value => filesystemEntry(value, 'deny')),
|
|
130
|
+
];
|
|
131
|
+
const sandboxState = JSON.stringify({
|
|
132
|
+
permissionProfile: {
|
|
133
|
+
type: 'managed',
|
|
134
|
+
file_system: { type: 'restricted', entries },
|
|
135
|
+
network: 'restricted',
|
|
136
|
+
},
|
|
137
|
+
sandboxCwd: pathToFileURL(projectPath).href,
|
|
138
|
+
});
|
|
139
|
+
if (sandboxState.length > 20_000)
|
|
140
|
+
throw new Error('scoped Windows Trigger filesystem policy exceeds the command-line budget');
|
|
141
|
+
const shellEnvironmentPolicy = `shell_environment_policy={inherit="all",exclude=${JSON.stringify(WINDOWS_SANDBOX_ENV_EXCLUDES)},include_only=[],set={}}`;
|
|
142
|
+
const sandboxArgs = [
|
|
143
|
+
'sandbox', '--sandbox-state-json', sandboxState, '--sandbox-state-disable-network',
|
|
144
|
+
'-c', 'windows.sandbox="elevated"',
|
|
145
|
+
'-c', shellEnvironmentPolicy,
|
|
146
|
+
];
|
|
147
|
+
const launch = {
|
|
148
|
+
command: windowsBackend.command,
|
|
149
|
+
args: [...sandboxArgs, '--', runtime, ...scriptArgs],
|
|
150
|
+
cwd: projectPath,
|
|
151
|
+
env: { ...env, CODEX_HOME: windowsBackend.home },
|
|
152
|
+
cleanup,
|
|
153
|
+
};
|
|
154
|
+
launch.prepare = signal => verifyWindowsReadIsolation(launch, sandboxArgs, runDir, workDir, signal);
|
|
155
|
+
return launch;
|
|
156
|
+
}
|
|
157
|
+
const args = [
|
|
158
|
+
'--die-with-parent', '--new-session', '--unshare-all', '--cap-drop', 'ALL',
|
|
159
|
+
'--tmpfs', '/', '--proc', '/proc', '--dev', '/dev', '--tmpfs', '/tmp',
|
|
160
|
+
];
|
|
161
|
+
const readRoots = [...SYSTEM_READ_ROOTS, ...SYSTEM_READ_FILES, runtimeDir]
|
|
162
|
+
.filter(value => fs.existsSync(value));
|
|
163
|
+
const visibleRoots = [...readRoots, projectPath];
|
|
164
|
+
const directoryMountpoints = new Set();
|
|
165
|
+
for (const value of [...visibleRoots, ...blockedPaths, runDir, workDir].filter(value => fs.existsSync(value))) {
|
|
166
|
+
const stat = fs.lstatSync(value);
|
|
167
|
+
let current = stat.isDirectory() ? path.resolve(value) : path.dirname(path.resolve(value));
|
|
168
|
+
while (current !== path.parse(current).root) {
|
|
169
|
+
directoryMountpoints.add(current);
|
|
170
|
+
current = path.dirname(current);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const value of [...directoryMountpoints].sort((left, right) => left.length - right.length)) {
|
|
174
|
+
args.push('--dir', value);
|
|
175
|
+
}
|
|
176
|
+
for (const value of new Set(readRoots))
|
|
177
|
+
args.push('--ro-bind', value, value);
|
|
178
|
+
args.push(authorization.permissionMode === 'readonly' ? '--ro-bind' : '--bind', projectPath, projectPath);
|
|
179
|
+
for (const value of blockedPaths) {
|
|
180
|
+
if (!visibleRoots.some(directory => isSameOrDescendant(value, directory)) || !fs.existsSync(value))
|
|
181
|
+
continue;
|
|
182
|
+
const stat = fs.lstatSync(value);
|
|
183
|
+
if (stat.isDirectory())
|
|
184
|
+
args.push('--tmpfs', value, '--remount-ro', value);
|
|
185
|
+
else
|
|
186
|
+
args.push('--ro-bind', '/dev/null', value);
|
|
187
|
+
}
|
|
188
|
+
args.push('--ro-bind', runDir, runDir, '--bind', workDir, workDir, '--remount-ro', '/');
|
|
189
|
+
args.push('--chdir', projectPath, '--', runtime, ...scriptArgs);
|
|
190
|
+
return { command: bubblewrap, args, cwd: projectPath, env, cleanup };
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
cleanup();
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function resolveRuntime(value) {
|
|
198
|
+
const runtime = value.trim();
|
|
199
|
+
if (!runtime || /[\0\r\n]/.test(runtime))
|
|
200
|
+
throw new Error('script.runtime is invalid');
|
|
201
|
+
const executable = runtime === 'node' ? process.execPath
|
|
202
|
+
: path.isAbsolute(runtime) ? runtime : resolveCommandPath(runtime);
|
|
203
|
+
if (!executable || !fs.statSync(executable).isFile())
|
|
204
|
+
throw new Error(`script runtime is unavailable: ${runtime}`);
|
|
205
|
+
return fs.realpathSync.native(executable);
|
|
206
|
+
}
|
|
207
|
+
function resolveWindowsBackend() {
|
|
208
|
+
const codexPath = resolveCodexCliPath();
|
|
209
|
+
if (!codexPath)
|
|
210
|
+
throw new Error('Codex CLI is required for scoped Windows Trigger script isolation');
|
|
211
|
+
let command = codexPath;
|
|
212
|
+
if (path.extname(command).toLowerCase() !== '.exe') {
|
|
213
|
+
const packageFile = path.join(path.dirname(codexPath), 'node_modules', '@openai', 'codex', 'package.json');
|
|
214
|
+
const platformName = `@openai/codex-win32-${process.arch}`;
|
|
215
|
+
const platformFile = createRequire(packageFile).resolve(`${platformName}/package.json`);
|
|
216
|
+
const target = process.arch === 'arm64' ? 'aarch64-pc-windows-msvc' : 'x86_64-pc-windows-msvc';
|
|
217
|
+
command = path.join(path.dirname(platformFile), 'vendor', target, 'bin', 'codex.exe');
|
|
218
|
+
}
|
|
219
|
+
if (!fs.statSync(command).isFile())
|
|
220
|
+
throw new Error('native Codex executable is unavailable');
|
|
221
|
+
const home = resolveRealDirectory(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'Codex control home');
|
|
222
|
+
let marker;
|
|
223
|
+
try {
|
|
224
|
+
marker = JSON.parse(fs.readFileSync(path.join(home, '.sandbox', 'setup_marker.json'), 'utf8'));
|
|
225
|
+
}
|
|
226
|
+
catch { }
|
|
227
|
+
let users;
|
|
228
|
+
try {
|
|
229
|
+
users = JSON.parse(fs.readFileSync(path.join(home, '.sandbox-secrets', 'sandbox_users.json'), 'utf8'));
|
|
230
|
+
}
|
|
231
|
+
catch { }
|
|
232
|
+
if (!marker || marker.version !== WINDOWS_SANDBOX_SETUP_VERSION || marker.allow_local_binding !== false
|
|
233
|
+
|| !Array.isArray(marker.proxy_ports) || marker.proxy_ports.length !== 0
|
|
234
|
+
|| !users || users.version !== WINDOWS_SANDBOX_SETUP_VERSION
|
|
235
|
+
|| users.offline?.username !== 'CodexSandboxOffline' || typeof users.offline?.password !== 'string' || !users.offline.password
|
|
236
|
+
|| users.online?.username !== 'CodexSandboxOnline' || typeof users.online?.password !== 'string' || !users.online.password
|
|
237
|
+
|| !fs.existsSync(path.join(home, '.sandbox-bin'))
|
|
238
|
+
|| !fs.readdirSync(path.join(home, '.sandbox-bin')).some(name => /^codex-command-runner-.+\.exe$/.test(name))) {
|
|
239
|
+
throw new Error('Windows restricted-read sandbox must be prepared in the host terminal; automatic elevation and host fallback are disabled');
|
|
240
|
+
}
|
|
241
|
+
return { command: fs.realpathSync.native(command), home };
|
|
242
|
+
}
|
|
243
|
+
function resolveRealDirectory(value, label) {
|
|
244
|
+
if (!path.isAbsolute(value))
|
|
245
|
+
throw new Error(`${label} must be absolute`);
|
|
246
|
+
assertRealDirectory(path.resolve(value), label);
|
|
247
|
+
return fs.realpathSync.native(value);
|
|
248
|
+
}
|
|
249
|
+
function assertRealDirectory(value, label) {
|
|
250
|
+
const stat = fs.lstatSync(value);
|
|
251
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !samePath(fs.realpathSync.native(value), path.resolve(value))) {
|
|
252
|
+
throw new Error(`${label} must be a real directory without symlinks or junctions`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function samePath(left, right) {
|
|
256
|
+
return isSameOrDescendant(left, right) && isSameOrDescendant(right, left);
|
|
257
|
+
}
|
|
258
|
+
function readScriptSnapshot(value, triggerDir) {
|
|
259
|
+
const scriptPath = path.resolve(value);
|
|
260
|
+
const validate = () => {
|
|
261
|
+
if (!isSameOrDescendant(scriptPath, triggerDir))
|
|
262
|
+
throw new Error('Trigger script must stay inside its directory');
|
|
263
|
+
let current = path.dirname(scriptPath);
|
|
264
|
+
while (true) {
|
|
265
|
+
assertRealDirectory(current, 'Trigger script parent');
|
|
266
|
+
if (samePath(current, triggerDir))
|
|
267
|
+
break;
|
|
268
|
+
const parent = path.dirname(current);
|
|
269
|
+
if (parent === current)
|
|
270
|
+
throw new Error('Trigger script directory is invalid');
|
|
271
|
+
current = parent;
|
|
272
|
+
}
|
|
273
|
+
const stat = fs.lstatSync(scriptPath);
|
|
274
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > TRIGGER_LOCAL_FILE_LIMIT) {
|
|
275
|
+
throw new Error('Trigger script must be a regular unlinked file under 4 MiB');
|
|
276
|
+
}
|
|
277
|
+
return stat;
|
|
278
|
+
};
|
|
279
|
+
const expected = validate();
|
|
280
|
+
const descriptor = fs.openSync(scriptPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
281
|
+
try {
|
|
282
|
+
const opened = fs.fstatSync(descriptor);
|
|
283
|
+
if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== expected.dev || opened.ino !== expected.ino
|
|
284
|
+
|| opened.size !== expected.size || opened.size > TRIGGER_LOCAL_FILE_LIMIT) {
|
|
285
|
+
throw new Error('Trigger script changed during validation');
|
|
286
|
+
}
|
|
287
|
+
const bytes = Buffer.alloc(opened.size + 1);
|
|
288
|
+
let length = 0;
|
|
289
|
+
while (length < bytes.length) {
|
|
290
|
+
const count = fs.readSync(descriptor, bytes, length, bytes.length - length, length);
|
|
291
|
+
if (!count)
|
|
292
|
+
break;
|
|
293
|
+
length += count;
|
|
294
|
+
}
|
|
295
|
+
const after = fs.fstatSync(descriptor);
|
|
296
|
+
const current = validate();
|
|
297
|
+
if (length !== opened.size || after.size !== opened.size || after.nlink !== 1
|
|
298
|
+
|| after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs
|
|
299
|
+
|| current.dev !== opened.dev || current.ino !== opened.ino
|
|
300
|
+
|| current.mtimeMs !== opened.mtimeMs || current.ctimeMs !== opened.ctimeMs) {
|
|
301
|
+
throw new Error('Trigger script changed during snapshotting');
|
|
302
|
+
}
|
|
303
|
+
return bytes.subarray(0, length);
|
|
304
|
+
}
|
|
305
|
+
finally {
|
|
306
|
+
fs.closeSync(descriptor);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function preserveNodeModuleType(runtime, scriptPath, snapshotDir) {
|
|
310
|
+
if (!/^node(?:\.exe)?$/i.test(path.basename(runtime)) || path.extname(scriptPath) !== '.js')
|
|
311
|
+
return;
|
|
312
|
+
let current = path.dirname(scriptPath);
|
|
313
|
+
while (true) {
|
|
314
|
+
const packageFile = path.join(current, 'package.json');
|
|
315
|
+
if (fs.existsSync(packageFile)) {
|
|
316
|
+
const stat = fs.lstatSync(packageFile);
|
|
317
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > TRIGGER_LOCAL_FILE_LIMIT) {
|
|
318
|
+
throw new Error('script package metadata must be a regular unlinked file under 4 MiB');
|
|
319
|
+
}
|
|
320
|
+
const metadata = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
321
|
+
if (metadata.type === 'module' || metadata.type === 'commonjs') {
|
|
322
|
+
fs.writeFileSync(path.join(snapshotDir, 'package.json'), JSON.stringify({ type: metadata.type }), { flag: 'wx', mode: 0o400 });
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const parent = path.dirname(current);
|
|
327
|
+
if (parent === current)
|
|
328
|
+
return;
|
|
329
|
+
current = parent;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function buildSandboxEnvironment(workDir) {
|
|
333
|
+
const env = {};
|
|
334
|
+
for (const key of SAFE_ENVIRONMENT_KEYS) {
|
|
335
|
+
const value = process.env[key];
|
|
336
|
+
if (typeof value === 'string' && value.length > 0)
|
|
337
|
+
env[key] = value;
|
|
338
|
+
}
|
|
339
|
+
const homeDir = process.platform === 'win32' ? path.join(workDir, 'home') : workDir;
|
|
340
|
+
if (homeDir !== workDir)
|
|
341
|
+
fs.mkdirSync(homeDir, { mode: 0o700 });
|
|
342
|
+
return {
|
|
343
|
+
...env, HOME: homeDir, USERPROFILE: homeDir,
|
|
344
|
+
APPDATA: path.join(workDir, 'appdata'), LOCALAPPDATA: path.join(workDir, 'localappdata'),
|
|
345
|
+
TMPDIR: workDir, TEMP: workDir, TMP: workDir, EVOLCORE_TRIGGER_SANDBOX: '1',
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function protectedScriptPaths(root, selfAid) {
|
|
349
|
+
const directoryRoots = CONTROL_DIRECTORY_NAMES.map(name => path.join(root, name));
|
|
350
|
+
const targets = [
|
|
351
|
+
...directoryRoots,
|
|
352
|
+
...getExistingHClassMaskTargets(root, { selfAid }).map(target => target.path),
|
|
353
|
+
...getExistingLClassReadOnlyTargets(root, { selfAid }).map(target => target.path),
|
|
354
|
+
path.join(root, 'daemon.json'), path.join(root, '.env'), path.join(root, '.device_id'),
|
|
355
|
+
path.join(root, '.lock'), path.join(root, 'daemon.pid'),
|
|
356
|
+
];
|
|
357
|
+
return [...new Set(targets)].filter(candidate => (directoryRoots.includes(candidate) || !directoryRoots.some(directory => isSameOrDescendant(candidate, directory))));
|
|
358
|
+
}
|
|
359
|
+
function filesystemEntry(value, access) {
|
|
360
|
+
return { path: { type: 'path', path: path.resolve(value) }, access };
|
|
361
|
+
}
|
|
362
|
+
async function verifyWindowsReadIsolation(launch, sandboxArgs, runDir, workDir, signal) {
|
|
363
|
+
signal?.throwIfAborted();
|
|
364
|
+
const canary = path.join(runDir, 'read-isolation-canary');
|
|
365
|
+
const writableProbe = path.join(workDir, 'write-isolation-probe');
|
|
366
|
+
fs.writeFileSync(canary, 'evolcore-read-isolation-canary', { flag: 'wx' });
|
|
367
|
+
const readProbe = [
|
|
368
|
+
'const fs = require("node:fs");',
|
|
369
|
+
'if (fs.readFileSync(process.argv[1], "utf8") !== "evolcore-read-isolation-canary") process.exit(91);',
|
|
370
|
+
'process.stdout.write("evolcore-trigger-sandbox-ready");',
|
|
371
|
+
].join('\n');
|
|
372
|
+
const probe = [
|
|
373
|
+
'const fs = require("node:fs");',
|
|
374
|
+
'let denied = false;',
|
|
375
|
+
'try { fs.readFileSync(process.argv[1]); } catch (error) {',
|
|
376
|
+
'if (error.code === "EACCES" || error.code === "EPERM") denied = true; else throw error;',
|
|
377
|
+
'}',
|
|
378
|
+
'if (!denied) { process.stderr.write("read isolation is not enforced"); process.exit(90); }',
|
|
379
|
+
'fs.writeFileSync(process.argv[2], "allowed");',
|
|
380
|
+
'process.stdout.write("evolcore-trigger-sandbox-ready");',
|
|
381
|
+
].join('\n');
|
|
382
|
+
try {
|
|
383
|
+
const readArgs = [...sandboxArgs];
|
|
384
|
+
const stateIndex = readArgs.indexOf('--sandbox-state-json') + 1;
|
|
385
|
+
const readState = JSON.parse(readArgs[stateIndex]);
|
|
386
|
+
readState.permissionProfile.file_system.entries.push(filesystemEntry(canary, 'read'));
|
|
387
|
+
readArgs[stateIndex] = JSON.stringify(readState);
|
|
388
|
+
if (readArgs[stateIndex].length > 20_000)
|
|
389
|
+
throw new Error('filesystem probe policy exceeds the command-line budget');
|
|
390
|
+
await runWindowsIsolationProbe(launch, readArgs, readProbe, [canary], signal);
|
|
391
|
+
await runWindowsIsolationProbe(launch, sandboxArgs, probe, [canary, writableProbe], signal);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
throw new Error(`Windows sandbox cannot prove scoped read isolation; Trigger script execution is refused: ${error?.message || String(error)}`, { cause: error });
|
|
395
|
+
}
|
|
396
|
+
finally {
|
|
397
|
+
try {
|
|
398
|
+
fs.unlinkSync(canary);
|
|
399
|
+
}
|
|
400
|
+
catch { }
|
|
401
|
+
try {
|
|
402
|
+
fs.unlinkSync(writableProbe);
|
|
403
|
+
}
|
|
404
|
+
catch { }
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function runWindowsIsolationProbe(launch, sandboxArgs, probe, probeArgs, signal) {
|
|
408
|
+
signal?.throwIfAborted();
|
|
409
|
+
return new Promise((resolve, reject) => {
|
|
410
|
+
const child = spawn(launch.command, [...sandboxArgs, '--', process.execPath, '-e', probe, ...probeArgs], {
|
|
411
|
+
cwd: launch.cwd, env: launch.env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
|
412
|
+
});
|
|
413
|
+
let stdout = '';
|
|
414
|
+
let stderr = '';
|
|
415
|
+
let settled = false;
|
|
416
|
+
let terminationError;
|
|
417
|
+
const terminate = (error) => {
|
|
418
|
+
if (settled || terminationError)
|
|
419
|
+
return;
|
|
420
|
+
terminationError = error;
|
|
421
|
+
if (!child.pid || !killProcess(child.pid, true)) {
|
|
422
|
+
try {
|
|
423
|
+
child.kill('SIGKILL');
|
|
424
|
+
}
|
|
425
|
+
catch { }
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
const abortHandler = () => terminate(new Error('isolation verification was aborted'));
|
|
429
|
+
const timer = setTimeout(() => terminate(new Error('isolation verification timed out')), 15_000);
|
|
430
|
+
timer.unref();
|
|
431
|
+
const finish = (error) => {
|
|
432
|
+
if (settled)
|
|
433
|
+
return;
|
|
434
|
+
settled = true;
|
|
435
|
+
clearTimeout(timer);
|
|
436
|
+
signal?.removeEventListener('abort', abortHandler);
|
|
437
|
+
if (error)
|
|
438
|
+
reject(error);
|
|
439
|
+
else
|
|
440
|
+
resolve();
|
|
441
|
+
};
|
|
442
|
+
child.stdout.on('data', bytes => { stdout = (stdout + bytes.toString()).slice(0, 2048); });
|
|
443
|
+
child.stderr.on('data', bytes => { stderr = (stderr + bytes.toString()).slice(0, 2048); });
|
|
444
|
+
child.once('error', error => finish(terminationError ?? error));
|
|
445
|
+
child.once('close', code => {
|
|
446
|
+
if (terminationError)
|
|
447
|
+
finish(terminationError);
|
|
448
|
+
else if (code === 0 && stdout === 'evolcore-trigger-sandbox-ready')
|
|
449
|
+
finish();
|
|
450
|
+
else
|
|
451
|
+
finish(new Error(stderr.trim() || `isolation verification exited with code ${code}`));
|
|
452
|
+
});
|
|
453
|
+
if (signal?.aborted)
|
|
454
|
+
abortHandler();
|
|
455
|
+
else
|
|
456
|
+
signal?.addEventListener('abort', abortHandler, { once: true });
|
|
457
|
+
});
|
|
458
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getPackageRoot } from '../paths.js';
|
|
4
|
+
import { TASK_RECOVERY_PROMPT } from '../core/task/task-recovery-service.js';
|
|
5
|
+
import { atomicWriteJson } from '../core/session/session-fs-store.js';
|
|
6
|
+
import { normalizeTriggerDefinition } from './validation.js';
|
|
7
|
+
export function seedUpgradeCheckTrigger(manager, owner) {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const scriptName = 'upgrade-check.cjs';
|
|
10
|
+
const definition = normalizeTriggerDefinition({
|
|
11
|
+
$schema_version: 3.1,
|
|
12
|
+
id: '__upgrade-check',
|
|
13
|
+
agentAid: owner.aid,
|
|
14
|
+
enabled: true,
|
|
15
|
+
name: '__upgrade-check',
|
|
16
|
+
description: 'System trigger: check EvolCore upgrades after install or upgrade.',
|
|
17
|
+
createdAt: now,
|
|
18
|
+
updatedAt: now,
|
|
19
|
+
origin: {
|
|
20
|
+
channelKey: 'daemon',
|
|
21
|
+
channelType: 'daemon',
|
|
22
|
+
channelId: owner.originPeerId || owner.aid,
|
|
23
|
+
session: 'main',
|
|
24
|
+
peerId: owner.originPeerId || owner.aid,
|
|
25
|
+
sessionKey: `daemon#${owner.originPeerId || owner.aid}#__system__`,
|
|
26
|
+
},
|
|
27
|
+
source: {
|
|
28
|
+
type: 'cron',
|
|
29
|
+
expression: '59 3 * * *',
|
|
30
|
+
timezone: 'Asia/Shanghai',
|
|
31
|
+
},
|
|
32
|
+
execution: {
|
|
33
|
+
type: 'script',
|
|
34
|
+
script: {
|
|
35
|
+
path: scriptName,
|
|
36
|
+
runtime: 'node',
|
|
37
|
+
timeoutMs: 60_000,
|
|
38
|
+
},
|
|
39
|
+
permissionMode: 'bypass',
|
|
40
|
+
onError: 'fail',
|
|
41
|
+
noopSentinel: '[[NOOP]]',
|
|
42
|
+
},
|
|
43
|
+
feedback: {
|
|
44
|
+
strategy: 'silent',
|
|
45
|
+
},
|
|
46
|
+
reliability: {
|
|
47
|
+
concurrency: 'forbid',
|
|
48
|
+
missedPolicy: 'run_once',
|
|
49
|
+
retry: {
|
|
50
|
+
maxAttempts: 0,
|
|
51
|
+
backoffMs: 30_000,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const dir = manager.triggerDir(definition.id);
|
|
56
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
57
|
+
const scriptPath = path.join(dir, scriptName);
|
|
58
|
+
const packageRoot = getPackageRoot();
|
|
59
|
+
fs.writeFileSync(scriptPath, `#!/usr/bin/env node
|
|
60
|
+
const { pathToFileURL } = require('node:url');
|
|
61
|
+
const entry = ${JSON.stringify(path.join(packageRoot, 'dist', 'utils', 'package-version-info.js'))};
|
|
62
|
+
async function main() {
|
|
63
|
+
const { refreshManagedPackageVersions } = await import(pathToFileURL(entry).href);
|
|
64
|
+
const results = await refreshManagedPackageVersions(undefined, { source: 'daily-check' });
|
|
65
|
+
const failed = results.some(result => result.status === 'unavailable');
|
|
66
|
+
process.stdout.write(JSON.stringify({
|
|
67
|
+
outcome: failed ? 'error' : 'success',
|
|
68
|
+
text: results.map(result => result.packageName + ': ' + result.status).join('; '),
|
|
69
|
+
files: [],
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
main().catch(() => {
|
|
73
|
+
process.stdout.write(JSON.stringify({ outcome: 'error', text: 'Version information refresh failed', files: [] }));
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
});
|
|
76
|
+
`);
|
|
77
|
+
fs.rmSync(path.join(dir, 'upgrade-check.sh'), { force: true });
|
|
78
|
+
fs.rmSync(path.join(dir, 'upgrade-check.js'), { force: true });
|
|
79
|
+
fs.chmodSync(scriptPath, 0o755);
|
|
80
|
+
atomicWriteJson(manager.definitionPath(definition.id), definition);
|
|
81
|
+
fs.rmSync(manager.activePath(definition.id), { force: true });
|
|
82
|
+
}
|
|
83
|
+
export function seedTaskRecoveryTrigger(manager, owner) {
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
const definition = normalizeTriggerDefinition({
|
|
86
|
+
$schema_version: 3.1,
|
|
87
|
+
id: '__task-recovery',
|
|
88
|
+
agentAid: owner.aid,
|
|
89
|
+
enabled: true,
|
|
90
|
+
name: '__task-recovery',
|
|
91
|
+
description: 'System trigger: recover failed tasks for local Agents.',
|
|
92
|
+
createdAt: now,
|
|
93
|
+
updatedAt: now,
|
|
94
|
+
origin: {
|
|
95
|
+
channelKey: 'daemon',
|
|
96
|
+
channelType: 'daemon',
|
|
97
|
+
channelId: owner.originPeerId || owner.aid,
|
|
98
|
+
session: 'main',
|
|
99
|
+
peerId: owner.originPeerId || owner.aid,
|
|
100
|
+
sessionKey: `daemon#${owner.originPeerId || owner.aid}#__task-recovery__`,
|
|
101
|
+
},
|
|
102
|
+
source: {
|
|
103
|
+
type: 'event',
|
|
104
|
+
eventPattern: 'task:error',
|
|
105
|
+
},
|
|
106
|
+
execution: {
|
|
107
|
+
type: 'target_session',
|
|
108
|
+
prompt: TASK_RECOVERY_PROMPT,
|
|
109
|
+
onError: 'retry',
|
|
110
|
+
noopSentinel: '[[NOOP]]',
|
|
111
|
+
},
|
|
112
|
+
feedback: { strategy: 'event_session' },
|
|
113
|
+
reliability: {
|
|
114
|
+
concurrency: 'forbid',
|
|
115
|
+
missedPolicy: 'run_once',
|
|
116
|
+
retry: { maxAttempts: 2, backoffMs: 30_000 },
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
fs.mkdirSync(manager.triggerDir(definition.id), { recursive: true });
|
|
120
|
+
atomicWriteJson(manager.definitionPath(definition.id), definition);
|
|
121
|
+
fs.rmSync(manager.activePath(definition.id), { force: true });
|
|
122
|
+
}
|
|
123
|
+
const SESSION_IDLE_CHECK_PROMPT = `本会话已闲置一段时间。请依据你的职责执行一次例行巡检:
|
|
124
|
+
1. 判断群内是否存在成员有未完成的工作却停滞(如长时间无进展、被点名未回应、承诺后失联)。有,则向该成员催促一次并说明停滞事项,随后即止;无,则不做任何动作。大家都在正常工作或无待办时保持沉默。
|
|
125
|
+
2. 巡检优先基于已有上下文判断,确需补充背景再查询历史,并遵守会话自身的发送规范。
|
|
126
|
+
3. 判定无需任何对外动作时,本轮只输出 [[NOOP]],不发送任何消息、不发布巡检结论。注意:最终对外输出必须是且仅是 [[NOOP]] 这一行——不要在输出它之前先发送任何说明文字或巡检判定,判定过程仅在内部完成。
|
|
127
|
+
(本指令为通用模板,可按具体职责增删检查项。)`;
|
|
128
|
+
export function seedSessionIdleCheckTrigger(manager, owner) {
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
const definition = normalizeTriggerDefinition({
|
|
131
|
+
$schema_version: 3.1,
|
|
132
|
+
id: '__session-idle-check',
|
|
133
|
+
agentAid: owner.aid,
|
|
134
|
+
enabled: true,
|
|
135
|
+
name: '__session-idle-check',
|
|
136
|
+
description: 'System trigger: inspect configured idle group sessions.',
|
|
137
|
+
createdAt: now,
|
|
138
|
+
updatedAt: now,
|
|
139
|
+
origin: {
|
|
140
|
+
channelKey: 'daemon',
|
|
141
|
+
channelType: 'daemon',
|
|
142
|
+
channelId: owner.originPeerId || owner.aid,
|
|
143
|
+
session: 'main',
|
|
144
|
+
peerId: owner.originPeerId || owner.aid,
|
|
145
|
+
sessionKey: `daemon#${owner.originPeerId || owner.aid}#__session-idle-check__`,
|
|
146
|
+
},
|
|
147
|
+
source: {
|
|
148
|
+
type: 'event',
|
|
149
|
+
eventPattern: 'session:idle',
|
|
150
|
+
filter: { match: { chatType: 'group' } },
|
|
151
|
+
},
|
|
152
|
+
execution: {
|
|
153
|
+
type: 'target_session',
|
|
154
|
+
prompt: SESSION_IDLE_CHECK_PROMPT,
|
|
155
|
+
onError: 'retry',
|
|
156
|
+
noopSentinel: '[[NOOP]]',
|
|
157
|
+
},
|
|
158
|
+
feedback: { strategy: 'event_session' },
|
|
159
|
+
reliability: {
|
|
160
|
+
concurrency: 'forbid',
|
|
161
|
+
missedPolicy: 'run_once',
|
|
162
|
+
retry: { maxAttempts: 2, backoffMs: 30_000 },
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
fs.mkdirSync(manager.triggerDir(definition.id), { recursive: true });
|
|
166
|
+
atomicWriteJson(manager.definitionPath(definition.id), definition);
|
|
167
|
+
fs.rmSync(manager.activePath(definition.id), { force: true });
|
|
168
|
+
}
|
|
169
|
+
export function removeLegacyAgentUpgradeCheck(manager) {
|
|
170
|
+
let existing;
|
|
171
|
+
try {
|
|
172
|
+
existing = manager.get('__upgrade-check');
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
fs.rmSync(manager.triggerDir('__upgrade-check'), { recursive: true, force: true });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!existing)
|
|
179
|
+
return;
|
|
180
|
+
const isLegacySystemOrigin = existing.origin?.channelKey === '__system__' || existing.origin?.channelKey === 'daemon';
|
|
181
|
+
const isUpgradeScript = existing.execution.type === 'script'
|
|
182
|
+
&& (existing.execution.script?.path === 'upgrade-check.sh'
|
|
183
|
+
|| existing.execution.script?.path === 'upgrade-check.js'
|
|
184
|
+
|| existing.execution.script?.path === 'upgrade-check.cjs');
|
|
185
|
+
if (!isLegacySystemOrigin || !isUpgradeScript)
|
|
186
|
+
return;
|
|
187
|
+
fs.rmSync(manager.triggerDir('__upgrade-check'), { recursive: true, force: true });
|
|
188
|
+
}
|
|
@@ -458,9 +458,6 @@ function normalizeExecution(value, _opts) {
|
|
|
458
458
|
consolidationChatDir: optionalString(raw.consolidationChatDir),
|
|
459
459
|
consolidationMemoryDir: optionalString(raw.consolidationMemoryDir),
|
|
460
460
|
};
|
|
461
|
-
if (execution.type === 'script' && execution.permissionMode === 'fullaccess') {
|
|
462
|
-
throw new Error('script Trigger already executes as the daemon OS user and cannot declare execution.permissionMode=fullaccess');
|
|
463
|
-
}
|
|
464
461
|
return execution;
|
|
465
462
|
}
|
|
466
463
|
const MEMORY_MODES = ['notes', 'generalize', 'promote', 'profile', 'forget'];
|
package/dist/utils/aid-bind.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import { loadAgent, loadDaemonConfig, saveAgent, saveDaemonConfig } from '../config-store.js';
|
|
3
3
|
import { resolveEffective } from '../config/config-manager.js';
|
|
4
|
-
import { readEvolcoreVersion } from '
|
|
4
|
+
import { readInstalledEvolcoreVersion as readEvolcoreVersion } from './evolcore-version.js';
|
|
5
5
|
export class BindService {
|
|
6
6
|
opts;
|
|
7
7
|
tasks = new Map();
|