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.
Files changed (116) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/codex-managed-hook.mjs +4 -1
  3. package/bin/ec-safe-output.js +26 -7
  4. package/bin/install-codex-managed-hooks.mjs +201 -0
  5. package/dist/agents/claude-runner.js +105 -11
  6. package/dist/agents/codex-app-server-client.js +176 -7
  7. package/dist/agents/codex-runner.js +248 -84
  8. package/dist/agents/ecagent-runner.js +21 -2
  9. package/dist/agents/gemini-runner.js +9 -4
  10. package/dist/aun/aid/control-aid.js +36 -13
  11. package/dist/aun/aid/store.js +13 -7
  12. package/dist/aun/group-fs-download.js +56 -0
  13. package/dist/aun/group-identity.js +1 -2
  14. package/dist/aun/msg/group.js +11 -33
  15. package/dist/aun/msg/index.js +1 -2
  16. package/dist/aun/msg/managed-operation.js +176 -55
  17. package/dist/aun/msg/upload.js +16 -1
  18. package/dist/aun/outbox.js +31 -2
  19. package/dist/aun/rpc/client.js +66 -0
  20. package/dist/aun/rpc/index.js +1 -2
  21. package/dist/aun/storage/{upload.js → client.js} +11 -2
  22. package/dist/aun/storage/index.js +1 -2
  23. package/dist/channels/aun.js +514 -150
  24. package/dist/channels/daemon.js +2 -0
  25. package/dist/cli/agent.js +9 -96
  26. package/dist/cli/aun-commands.js +1 -1
  27. package/dist/cli/cli-argv.js +10 -0
  28. package/dist/cli/config.js +9 -17
  29. package/dist/cli/ctl-command.js +1 -1
  30. package/dist/cli/daemon-commands.js +39 -24
  31. package/dist/cli/fs-command.js +65 -19
  32. package/dist/cli/init.js +77 -8
  33. package/dist/cli/queue-command.js +42 -0
  34. package/dist/cli/restart-monitor.js +3 -14
  35. package/dist/cli/task-context.js +172 -0
  36. package/dist/cli/trigger-command.js +7 -0
  37. package/dist/config/access-policy.js +1 -2
  38. package/dist/config/builtin-roles.js +7 -0
  39. package/dist/config/config-manager.js +27 -3
  40. package/dist/config/contact-book-store.js +7 -2
  41. package/dist/config/peer-role-resolver.js +6 -2
  42. package/dist/config/role-migration-startup.js +53 -0
  43. package/dist/config/role-store.js +1 -1
  44. package/dist/core/auth/agent-delegation.js +1 -1
  45. package/dist/core/auth/auth-gateway.js +30 -1
  46. package/dist/core/auth/authorization-audit.js +47 -0
  47. package/dist/core/auth/operation-authorizer.js +1 -0
  48. package/dist/core/auth/operation-catalog.js +10 -1
  49. package/dist/core/bootstrap-messages.js +9 -2
  50. package/dist/core/bootstrap-service.js +8 -1
  51. package/dist/core/command/command-handler.js +201 -51
  52. package/dist/core/command/menu-catalog.js +4 -0
  53. package/dist/core/command/menu-handler.js +85 -18
  54. package/dist/core/command/menu-protocol.js +23 -2
  55. package/dist/core/command/slash-handler.js +58 -10
  56. package/dist/core/event-catalog.js +8 -0
  57. package/dist/core/handoff/runtime.js +12 -1
  58. package/dist/core/handoff/store.js +5 -1
  59. package/dist/core/interaction-router.js +10 -0
  60. package/dist/core/message/im-renderer.js +34 -1
  61. package/dist/core/message/message-bridge.js +68 -13
  62. package/dist/core/message/message-log.js +2 -0
  63. package/dist/core/message/message-queue.js +267 -39
  64. package/dist/core/message/message-utils.js +81 -4
  65. package/dist/core/message/response-engine.js +242 -48
  66. package/dist/core/message/stream-debouncer.js +10 -2
  67. package/dist/core/permission/approval-gateway.js +1 -0
  68. package/dist/core/permission/ec-command-parser.js +649 -18
  69. package/dist/core/permission/index.js +1 -1
  70. package/dist/core/permission/mode.js +15 -0
  71. package/dist/core/permission/readonly-shell-query.js +148 -8
  72. package/dist/core/permission/sandbox-runtime.js +21 -0
  73. package/dist/core/permission/tool-policy.js +266 -56
  74. package/dist/core/protected-paths.js +107 -2
  75. package/dist/core/runtime-lock.js +101 -0
  76. package/dist/core/session/session-manager.js +21 -2
  77. package/dist/core/session/session-mapper.js +20 -1
  78. package/dist/eck/detect.js +18 -1
  79. package/dist/eck/group-rules-sync.js +15 -44
  80. package/dist/index.js +191 -89
  81. package/dist/ipc.js +16 -3
  82. package/dist/paths.js +3 -0
  83. package/dist/response-system/engines/v1/proactive-flow.js +92 -8
  84. package/dist/response-system/modes/single-session/index.js +3 -0
  85. package/dist/trigger/feedback.js +64 -11
  86. package/dist/trigger/history.js +42 -7
  87. package/dist/trigger/manager.js +21 -1
  88. package/dist/trigger/parser.js +14 -2
  89. package/dist/trigger/scheduler.js +1 -0
  90. package/dist/trigger/validation.js +67 -6
  91. package/dist/utils/ecweb-supervisor.js +332 -0
  92. package/dist/utils/error-utils.js +17 -0
  93. package/dist/utils/logger.js +37 -4
  94. package/kits/docs/evolcore/trigger.md +17 -1
  95. package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
  96. package/kits/schemas/role-config.schema.1.json +1 -0
  97. package/kits/templates/roles/admin.json +7 -0
  98. package/kits/templates/roles/member.json +8 -1
  99. package/kits/templates/roles/visitor.json +2 -0
  100. package/package.json +1 -1
  101. package/dist/aun/msg/payload-type.js +0 -27
  102. package/dist/aun/rpc/caller.js +0 -42
  103. package/dist/aun/rpc/connection.js +0 -25
  104. package/dist/aun/storage/manage.js +0 -10
  105. package/dist/config/access-policy-domain.js +0 -18
  106. package/dist/config/config-batch-get.js +0 -11
  107. package/dist/config/owner-policy.js +0 -4
  108. package/dist/config/role-config-v4-startup.js +0 -32
  109. package/dist/config/role-config-v5-startup.js +0 -27
  110. package/dist/core/auth/trigger-authorization.js +0 -15
  111. package/dist/core/interaction-registration.js +0 -10
  112. package/dist/core/permission/execution-sandbox.js +0 -16
  113. package/dist/core/relation/peer-key.js +0 -1
  114. package/dist/core/session/session-key.js +0 -24
  115. package/dist/eck/baseagent-caps.js +0 -18
  116. 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 directory = fs.mkdtempSync(path.join(os.tmpdir(), 'evolcore-codex-hooks-'));
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
- Promise.resolve(handler(request))
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=${request.id} method=${request.method} result=${JSON.stringify(result ?? null)}`);
633
- this.respond(request.id, result ?? null);
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=${request.id} method=${request.method}: ${err.message}`);
638
- this.respondError(request.id, err);
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
  }