evolcore 0.0.12 → 0.0.14
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 +37 -0
- package/bin/codex-managed-hook.mjs +4 -1
- package/bin/ec-safe-output.js +26 -7
- package/bin/install-codex-managed-hooks.mjs +201 -0
- package/dist/agents/claude-runner.js +105 -11
- package/dist/agents/codex-app-server-client.js +176 -7
- package/dist/agents/codex-runner.js +248 -84
- package/dist/agents/ecagent-runner.js +21 -2
- package/dist/agents/gemini-runner.js +9 -4
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +176 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +514 -150
- package/dist/channels/daemon.js +2 -0
- package/dist/cli/agent.js +9 -96
- package/dist/cli/aun-commands.js +1 -1
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +65 -19
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/task-context.js +172 -0
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +7 -0
- package/dist/config/config-manager.js +27 -3
- package/dist/config/contact-book-store.js +7 -2
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +30 -1
- package/dist/core/auth/authorization-audit.js +47 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +10 -1
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +8 -1
- package/dist/core/command/command-handler.js +201 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +34 -1
- package/dist/core/message/message-bridge.js +68 -13
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +242 -48
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +649 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +266 -56
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/runtime-lock.js +101 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +191 -89
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/response-system/engines/v1/proactive-flow.js +92 -8
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/history.js +42 -7
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +17 -0
- package/dist/utils/logger.js +37 -4
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +7 -0
- package/kits/templates/roles/member.json +8 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -6,8 +6,8 @@ import { resolveCodexLaunchCommand } from '../utils/codex-cli.js';
|
|
|
6
6
|
import { createHash } from 'crypto';
|
|
7
7
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
8
8
|
import fs from 'fs';
|
|
9
|
-
import os from 'os';
|
|
10
9
|
import path from 'path';
|
|
10
|
+
import { ensureProcessManagedTempDir } from '../cli/task-context.js';
|
|
11
11
|
import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
12
12
|
import { registerCodexAppServerProcess, unregisterCodexAppServerProcess, } from '../utils/codex-app-server-registry.js';
|
|
13
13
|
const BUILT_IN_CODEX_PROVIDER_IDS = new Set(['openai']);
|
|
@@ -30,6 +30,22 @@ function runtimeProviderId(baseUrl) {
|
|
|
30
30
|
const digest = createHash('sha256').update(baseUrl || 'default').digest('hex').slice(0, 12);
|
|
31
31
|
return `evolcore_runtime_${digest}`;
|
|
32
32
|
}
|
|
33
|
+
function stableJson(value) {
|
|
34
|
+
if (Array.isArray(value))
|
|
35
|
+
return `[${value.map(stableJson).join(',')}]`;
|
|
36
|
+
if (value && typeof value === 'object') {
|
|
37
|
+
return `{${Object.entries(value)
|
|
38
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
39
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
|
40
|
+
.join(',')}}`;
|
|
41
|
+
}
|
|
42
|
+
return JSON.stringify(value) ?? 'undefined';
|
|
43
|
+
}
|
|
44
|
+
function serverRequestFingerprint(request) {
|
|
45
|
+
return createHash('sha256')
|
|
46
|
+
.update(`${request.method}\n${stableJson(request.params)}`)
|
|
47
|
+
.digest('hex');
|
|
48
|
+
}
|
|
33
49
|
function hasPermissionProfile(config) {
|
|
34
50
|
if (!config || typeof config.default_permissions !== 'string' || !config.default_permissions)
|
|
35
51
|
return false;
|
|
@@ -41,6 +57,7 @@ export class CodexAppServerClient {
|
|
|
41
57
|
options;
|
|
42
58
|
proc = null;
|
|
43
59
|
pending = new Map();
|
|
60
|
+
serverRequestReplays = new Map();
|
|
44
61
|
notificationHandlers = new Set();
|
|
45
62
|
nextId = 1;
|
|
46
63
|
initialized = false;
|
|
@@ -281,6 +298,9 @@ export class CodexAppServerClient {
|
|
|
281
298
|
includeLayers: false,
|
|
282
299
|
});
|
|
283
300
|
}
|
|
301
|
+
async configRequirementsRead() {
|
|
302
|
+
return this.request('configRequirements/read');
|
|
303
|
+
}
|
|
284
304
|
async pluginInstalled(cwd) {
|
|
285
305
|
return this.request('plugin/installed', {
|
|
286
306
|
...(cwd ? { cwds: [cwd] } : {}),
|
|
@@ -305,6 +325,7 @@ export class CodexAppServerClient {
|
|
|
305
325
|
pending.reject(new Error('Codex app-server closed'));
|
|
306
326
|
}
|
|
307
327
|
this.pending.clear();
|
|
328
|
+
this.serverRequestReplays.clear();
|
|
308
329
|
if (!proc) {
|
|
309
330
|
this.cleanupManagedHookRuntime();
|
|
310
331
|
return;
|
|
@@ -361,7 +382,25 @@ export class CodexAppServerClient {
|
|
|
361
382
|
this.initializing = null;
|
|
362
383
|
}
|
|
363
384
|
}
|
|
385
|
+
async assertManagedHooksAvailable() {
|
|
386
|
+
await this.ensureStarted();
|
|
387
|
+
if (process.platform !== 'win32')
|
|
388
|
+
return;
|
|
389
|
+
try {
|
|
390
|
+
await this.verifyWindowsManagedHooks();
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
// Do not leave an unverified app-server alive after a fail-closed check.
|
|
394
|
+
await this.close();
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
364
398
|
startProcess() {
|
|
399
|
+
// Linux managed hooks need a private runtime even when the daemon was
|
|
400
|
+
// launched by a service manager that did not export TMPDIR.
|
|
401
|
+
if (process.platform === 'linux' && !process.env.TMPDIR?.trim()) {
|
|
402
|
+
ensureProcessManagedTempDir();
|
|
403
|
+
}
|
|
365
404
|
const env = this.buildProcessEnvironment();
|
|
366
405
|
const args = this.buildProcessArgs();
|
|
367
406
|
const launchCommand = resolveCodexLaunchCommand(args);
|
|
@@ -430,7 +469,8 @@ export class CodexAppServerClient {
|
|
|
430
469
|
if (this.managedHookRuntimeDir) {
|
|
431
470
|
return path.join(this.managedHookRuntimeDir, 'requirements.toml');
|
|
432
471
|
}
|
|
433
|
-
const
|
|
472
|
+
const tmpDir = ensureProcessManagedTempDir();
|
|
473
|
+
const directory = fs.mkdtempSync(path.join(tmpDir, 'evolcore-codex-hooks-'));
|
|
434
474
|
try {
|
|
435
475
|
fs.chmodSync(directory, 0o700);
|
|
436
476
|
const hookPath = path.join(directory, 'pre-tool-use.mjs');
|
|
@@ -487,7 +527,39 @@ export class CodexAppServerClient {
|
|
|
487
527
|
...process.env,
|
|
488
528
|
...this.options.env,
|
|
489
529
|
});
|
|
530
|
+
// The app-server is shared by multiple EvolCore sessions. Its process
|
|
531
|
+
// environment must therefore use the daemon's trusted private root;
|
|
532
|
+
// per-thread session roots are injected later through thread config.
|
|
533
|
+
const managedTmpDir = ensureProcessManagedTempDir();
|
|
534
|
+
env.TMPDIR = managedTmpDir;
|
|
535
|
+
delete env.EVOLCORE_RUNTIME_LOCK_DIR;
|
|
536
|
+
if (managedTmpDir) {
|
|
537
|
+
const runtimeLockDir = path.join(managedTmpDir, 'evolcore-locks');
|
|
538
|
+
try {
|
|
539
|
+
// The parent is the already-validated process root, so only create
|
|
540
|
+
// and validate the final component. Never follow a pre-existing
|
|
541
|
+
// symlink supplied through the inherited environment.
|
|
542
|
+
try {
|
|
543
|
+
fs.mkdirSync(runtimeLockDir, { mode: 0o700 });
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
if (error?.code !== 'EEXIST')
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
const stat = fs.lstatSync(runtimeLockDir);
|
|
550
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()
|
|
551
|
+
|| (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
552
|
+
|| (stat.mode & 0o077) !== 0) {
|
|
553
|
+
throw new Error('runtime lock directory is not private');
|
|
554
|
+
}
|
|
555
|
+
env.EVOLCORE_RUNTIME_LOCK_DIR = runtimeLockDir;
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
logger.warn(`[CodexAppServer] managed runtime lock directory unavailable: ${runtimeLockDir}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
490
561
|
env.CODEX_API_KEY = this.options.apiKey;
|
|
562
|
+
env.EVOLCORE_INSTANCE_SOCKET = resolvePaths().instanceSocket;
|
|
491
563
|
for (const [name, value] of Object.entries(this.options.headers ?? {})) {
|
|
492
564
|
env[codexHeaderEnvName(name)] = value;
|
|
493
565
|
}
|
|
@@ -496,6 +568,73 @@ export class CodexAppServerClient {
|
|
|
496
568
|
}
|
|
497
569
|
return env;
|
|
498
570
|
}
|
|
571
|
+
async verifyWindowsManagedHooks() {
|
|
572
|
+
const response = await this.configRequirementsRead();
|
|
573
|
+
const requirements = response.requirements;
|
|
574
|
+
const hooks = requirements?.hooks;
|
|
575
|
+
const featureRequirements = requirements?.featureRequirements ?? requirements?.feature_requirements;
|
|
576
|
+
const managedDir = hooks?.windowsManagedDir ?? hooks?.windows_managed_dir;
|
|
577
|
+
const preToolUse = hooks?.PreToolUse;
|
|
578
|
+
const expectedHookPath = typeof managedDir === 'string'
|
|
579
|
+
? path.join(managedDir, 'pre-tool-use.mjs')
|
|
580
|
+
: undefined;
|
|
581
|
+
const currentHookLauncher = !!expectedHookPath && (() => {
|
|
582
|
+
try {
|
|
583
|
+
const source = fs.readFileSync(expectedHookPath, 'utf8');
|
|
584
|
+
const expectedPackageRoot = JSON.stringify(path.resolve(getPackageRoot()));
|
|
585
|
+
return source.includes('EVOLCORE_MANAGED_HOOK_LAUNCHER')
|
|
586
|
+
&& (source.includes(expectedPackageRoot)
|
|
587
|
+
|| (process.platform === 'win32' && source.toLowerCase().includes(expectedPackageRoot.toLowerCase())));
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
})();
|
|
593
|
+
const normalizeManagedPath = (value) => path.normalize(value).toLowerCase();
|
|
594
|
+
const commandTargetsHook = (value) => {
|
|
595
|
+
const command = normalizeManagedPath(value).trim();
|
|
596
|
+
const hook = normalizeManagedPath(expectedHookPath ?? '');
|
|
597
|
+
// The managed command is intentionally a single executable invocation.
|
|
598
|
+
// Reject shell operators and trailing arguments so a modified
|
|
599
|
+
// requirements file cannot retain the expected path while running a
|
|
600
|
+
// second command or changing the hook's behavior.
|
|
601
|
+
if (!command || !hook || /[\r\n;&|<>]/.test(command))
|
|
602
|
+
return false;
|
|
603
|
+
return command.replace(/["']+$/, '').endsWith(hook);
|
|
604
|
+
};
|
|
605
|
+
const managedHookLoaded = Array.isArray(preToolUse)
|
|
606
|
+
&& preToolUse.some(group => {
|
|
607
|
+
if (!group || typeof group !== 'object' || Array.isArray(group))
|
|
608
|
+
return false;
|
|
609
|
+
if (group.matcher !== '*')
|
|
610
|
+
return false;
|
|
611
|
+
const handlers = group.hooks;
|
|
612
|
+
return Array.isArray(handlers) && handlers.some(handler => {
|
|
613
|
+
if (!handler || typeof handler !== 'object' || Array.isArray(handler))
|
|
614
|
+
return false;
|
|
615
|
+
const entry = handler;
|
|
616
|
+
const command = entry.commandWindows ?? entry.command_windows;
|
|
617
|
+
return entry.type === 'command'
|
|
618
|
+
&& entry.async !== true
|
|
619
|
+
&& typeof entry.command === 'string'
|
|
620
|
+
&& entry.command.length > 0
|
|
621
|
+
&& typeof command === 'string'
|
|
622
|
+
&& command.length > 0
|
|
623
|
+
&& !!expectedHookPath
|
|
624
|
+
&& fs.existsSync(expectedHookPath)
|
|
625
|
+
&& fs.statSync(expectedHookPath).isFile()
|
|
626
|
+
&& currentHookLauncher
|
|
627
|
+
&& commandTargetsHook(command);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
const lockdownEnabled = (requirements?.allowManagedHooksOnly ?? requirements?.allow_managed_hooks_only) === true;
|
|
631
|
+
const hooksEnabled = featureRequirements?.hooks === true;
|
|
632
|
+
if (typeof managedDir !== 'string' || !path.isAbsolute(managedDir)
|
|
633
|
+
|| !fs.existsSync(managedDir) || !managedHookLoaded || !lockdownEnabled || !hooksEnabled) {
|
|
634
|
+
throw new Error('Codex Windows proactive requires an installed and loaded EvolCore managed PreToolUse hook. '
|
|
635
|
+
+ 'Run the elevated Windows installer again to provision %ProgramData%\\OpenAI\\Codex\\requirements.toml.');
|
|
636
|
+
}
|
|
637
|
+
}
|
|
499
638
|
buildProcessArgs() {
|
|
500
639
|
const args = ['app-server', '--listen', 'stdio://'];
|
|
501
640
|
if (this.options.enableRequestUserInput !== false) {
|
|
@@ -627,15 +766,44 @@ export class CodexAppServerClient {
|
|
|
627
766
|
this.respondError(request.id, new Error('Unsupported Codex app-server request: ' + request.method));
|
|
628
767
|
return;
|
|
629
768
|
}
|
|
630
|
-
|
|
769
|
+
const replayKey = String(request.id);
|
|
770
|
+
const fingerprint = serverRequestFingerprint(request);
|
|
771
|
+
const previous = this.serverRequestReplays.get(replayKey);
|
|
772
|
+
if (previous) {
|
|
773
|
+
if (previous.fingerprint !== fingerprint) {
|
|
774
|
+
const conflict = new Error(`Conflicting Codex app-server request payload for id=${request.id}`);
|
|
775
|
+
logger.warn(`[CodexAppServer] server request ID conflict id=${request.id} method=${request.method}`);
|
|
776
|
+
this.respondError(request.id, conflict);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
// A retry must receive a response too, but must never invoke the
|
|
780
|
+
// approval handler a second time.
|
|
781
|
+
this.respondFromPromise(request.id, request.method, previous.response);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
let response;
|
|
785
|
+
try {
|
|
786
|
+
// Invoke synchronously so existing app-server latency stays unchanged,
|
|
787
|
+
// while the explicit catch still turns a synchronous handler throw into
|
|
788
|
+
// the same replayable rejected response as an async failure.
|
|
789
|
+
response = Promise.resolve(handler(request));
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
response = Promise.reject(error);
|
|
793
|
+
}
|
|
794
|
+
this.serverRequestReplays.set(replayKey, { fingerprint, response });
|
|
795
|
+
this.respondFromPromise(request.id, request.method, response);
|
|
796
|
+
}
|
|
797
|
+
respondFromPromise(id, method, response) {
|
|
798
|
+
response
|
|
631
799
|
.then(result => {
|
|
632
|
-
logger.info(`[CodexAppServer] server response id=${
|
|
633
|
-
this.respond(
|
|
800
|
+
logger.info(`[CodexAppServer] server response id=${id} method=${method} result=${JSON.stringify(result ?? null)}`);
|
|
801
|
+
this.respond(id, result ?? null);
|
|
634
802
|
})
|
|
635
803
|
.catch(error => {
|
|
636
804
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
637
|
-
logger.warn(`[CodexAppServer] server request failed id=${
|
|
638
|
-
this.respondError(
|
|
805
|
+
logger.warn(`[CodexAppServer] server request failed id=${id} method=${method}: ${err.message}`);
|
|
806
|
+
this.respondError(id, err);
|
|
639
807
|
});
|
|
640
808
|
}
|
|
641
809
|
emitNotification(notification) {
|
|
@@ -676,6 +844,7 @@ export class CodexAppServerClient {
|
|
|
676
844
|
pending.reject(error);
|
|
677
845
|
}
|
|
678
846
|
this.pending.clear();
|
|
847
|
+
this.serverRequestReplays.clear();
|
|
679
848
|
this.cleanupManagedHookRuntime();
|
|
680
849
|
}
|
|
681
850
|
}
|