evolcore 0.0.23 → 0.0.25

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 (245) hide show
  1. package/CHANGELOG.md +583 -516
  2. package/LICENSE +21 -21
  3. package/README.md +455 -453
  4. package/bin/codex-managed-acl.ps1 +160 -155
  5. package/bin/codex-managed-hook.mjs +209 -92
  6. package/bin/ec-safe-output.js +327 -245
  7. package/bin/ec.js +29 -29
  8. package/bin/install-codex-managed-hooks.mjs +555 -544
  9. package/dist/agents/baseagent-availability.js +4 -4
  10. package/dist/agents/baseagent.js +29 -1
  11. package/dist/agents/claude-runner.js +152 -14
  12. package/dist/agents/codex/app-server-client.js +18 -8
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +215 -0
  15. package/dist/agents/codex-runner.js +445 -63
  16. package/dist/agents/ecagent-runner.js +31 -16
  17. package/dist/agents/gemini-runner.js +82 -4
  18. package/dist/agents/runner-types.js +1 -1
  19. package/dist/aun/aid/agentmd.js +56 -4
  20. package/dist/aun/aid/index.js +1 -0
  21. package/dist/aun/aid/managed-operation.js +80 -6
  22. package/dist/aun/aid/store.js +41 -0
  23. package/dist/aun/msg/managed-operation.js +6 -1
  24. package/dist/aun/outbox.js +56 -18
  25. package/dist/aun/rpc/client.js +4 -5
  26. package/dist/aun/storage/client.js +4 -1
  27. package/dist/aun/storage/index.js +1 -1
  28. package/dist/channels/aun.js +147 -76
  29. package/dist/channels/daemon.js +7 -1
  30. package/dist/channels/dingtalk.js +1 -0
  31. package/dist/channels/feishu.js +10 -4
  32. package/dist/channels/wecom.js +2 -1
  33. package/dist/cli/agent-command.js +214 -89
  34. package/dist/cli/agent-create.js +576 -0
  35. package/dist/cli/agent.js +129 -676
  36. package/dist/cli/aun-commands.js +419 -313
  37. package/dist/cli/bench.js +20 -20
  38. package/dist/cli/config.js +20 -20
  39. package/dist/cli/contact.js +10 -10
  40. package/dist/cli/ctl-command.js +22 -20
  41. package/dist/cli/daemon-commands.js +163 -32
  42. package/dist/cli/fs-command.js +67 -67
  43. package/dist/cli/handoff-command.js +7 -7
  44. package/dist/cli/index.js +79 -79
  45. package/dist/cli/link-rules.js +21 -21
  46. package/dist/cli/managed-command-guard.js +3 -0
  47. package/dist/cli/menu-client.js +11 -11
  48. package/dist/cli/net-check.js +22 -22
  49. package/dist/cli/queue-command.js +29 -29
  50. package/dist/cli/response.js +30 -30
  51. package/dist/cli/restart-monitor.js +462 -38
  52. package/dist/cli/stats.js +161 -51
  53. package/dist/cli/trigger-command.js +56 -56
  54. package/dist/config/builtin-roles.js +3 -0
  55. package/dist/config/config-manager.js +15 -0
  56. package/dist/config/config-operation-service.js +20 -3
  57. package/dist/config/resolved-config-op.js +39 -1
  58. package/dist/core/auth/auth-gateway.js +3 -2
  59. package/dist/core/auth/authorization-audit.js +72 -19
  60. package/dist/core/auth/operation-authorizer.js +125 -7
  61. package/dist/core/auth/operation-catalog.js +26 -2
  62. package/dist/core/auth/remote-access-store.js +3 -2
  63. package/dist/core/baseagent-loader.js +4 -4
  64. package/dist/core/bootstrap-messages.js +39 -18
  65. package/dist/core/bootstrap-service.js +32 -26
  66. package/dist/core/capability/capability-manager.js +8 -4
  67. package/dist/core/capability/providers/codex-capability-provider.js +1 -1
  68. package/dist/core/command/agent-control.js +60 -26
  69. package/dist/core/command/cli-intent-parser.js +18 -0
  70. package/dist/core/command/command-handler.js +173 -36
  71. package/dist/core/command/menu-catalog.js +4 -1
  72. package/dist/core/command/menu-handler.js +315 -75
  73. package/dist/core/command/menu-protocol.js +7 -2
  74. package/dist/core/command/slash-gate.js +1 -3
  75. package/dist/core/command/slash-handler.js +166 -57
  76. package/dist/core/data-migration.js +5 -15
  77. package/dist/core/event-catalog.js +8 -0
  78. package/dist/core/evolagent.js +3 -3
  79. package/dist/core/handoff/runtime.js +3 -1
  80. package/dist/core/handoff/store.js +7 -1
  81. package/dist/core/message/im-renderer.js +13 -6
  82. package/dist/core/message/message-bridge.js +5 -36
  83. package/dist/core/message/message-utils.js +6 -3
  84. package/dist/core/message/response-engine.js +85 -27
  85. package/dist/core/model/catalog-service.js +3 -1
  86. package/dist/core/model/model-catalog.js +10 -0
  87. package/dist/core/model/model-permission.js +4 -1
  88. package/dist/core/permission/approval-gateway.js +169 -33
  89. package/dist/core/permission/tool-policy.js +56 -10
  90. package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
  91. package/dist/core/session/session-manager.js +49 -13
  92. package/dist/core/session/session-renew.js +1 -0
  93. package/dist/index.js +327 -134
  94. package/dist/ipc.js +68 -21
  95. package/dist/paths.js +23 -1
  96. package/dist/stats/billing.js +18 -4
  97. package/dist/stats/db.js +159 -159
  98. package/dist/stats/eck-vars.js +9 -9
  99. package/dist/stats/query.js +139 -139
  100. package/dist/stats/role-budget.js +6 -6
  101. package/dist/stats/writer.js +51 -51
  102. package/dist/utils/atomic-write.js +26 -2
  103. package/dist/utils/avatar-upload.js +282 -23
  104. package/dist/utils/cross-platform.js +436 -1
  105. package/dist/utils/error-dict.json +160 -160
  106. package/dist/utils/error-utils.js +11 -4
  107. package/dist/utils/macos-autostart.js +22 -22
  108. package/dist/utils/model-prices.jsonl +23 -20
  109. package/dist/utils/npm-ops.js +24 -1
  110. package/dist/utils/welcome.js +22 -22
  111. package/dist/utils/windows-autostart.js +1 -1
  112. package/ecagent/LICENSE +21 -21
  113. package/ecagent/NOTICE +11 -11
  114. package/ecagent/README.md +61 -61
  115. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  116. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  117. package/ecagent/dist/harness/messages.js +7 -7
  118. package/kits/docs/GUIDE.md +20 -20
  119. package/kits/docs/INDEX.md +66 -66
  120. package/kits/docs/aun/CHEATSHEET.md +19 -19
  121. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  122. package/kits/docs/channels/aun.md +56 -56
  123. package/kits/docs/channels/feishu.md +56 -56
  124. package/kits/docs/context-assembly.md +365 -364
  125. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  126. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  127. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  128. package/kits/docs/eck_templates/runtime.template.md +19 -19
  129. package/kits/docs/evolcore/INDEX.md +71 -69
  130. package/kits/docs/evolcore/agent-create.md +170 -156
  131. package/kits/docs/evolcore/agent.md +83 -73
  132. package/kits/docs/evolcore/aid.md +64 -62
  133. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  134. package/kits/docs/evolcore/config.md +195 -195
  135. package/kits/docs/evolcore/contact.md +68 -68
  136. package/kits/docs/evolcore/ctl.md +46 -46
  137. package/kits/docs/evolcore/event.md +216 -216
  138. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  139. package/kits/docs/evolcore/fs.md +110 -110
  140. package/kits/docs/evolcore/group-collaboration.md +243 -243
  141. package/kits/docs/evolcore/group-fs.md +17 -17
  142. package/kits/docs/evolcore/group-rules.md +251 -251
  143. package/kits/docs/evolcore/group.md +152 -152
  144. package/kits/docs/evolcore/menu-client.md +34 -34
  145. package/kits/docs/evolcore/model.md +50 -50
  146. package/kits/docs/evolcore/msg.md +165 -165
  147. package/kits/docs/evolcore/response.md +75 -75
  148. package/kits/docs/evolcore/rpc.md +37 -37
  149. package/kits/docs/evolcore/self-summary.md +29 -29
  150. package/kits/docs/evolcore/stats.md +83 -83
  151. package/kits/docs/evolcore/storage.md +50 -50
  152. package/kits/docs/evolcore/trigger.md +575 -575
  153. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  154. package/kits/docs/identity/PATH_OPS.md +16 -16
  155. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  156. package/kits/docs/identity/identity-tools.md +26 -26
  157. package/kits/docs/path-registry.md +62 -62
  158. package/kits/docs/prompt-loading-architecture.md +270 -269
  159. package/kits/docs/venues/aun-group.md +45 -45
  160. package/kits/docs/venues/aun-private.md +10 -10
  161. package/kits/docs/venues/client-desktop.md +10 -10
  162. package/kits/docs/venues/client-mobile.md +10 -10
  163. package/kits/docs/venues/feishu-group.md +13 -13
  164. package/kits/docs/venues/feishu-private.md +9 -9
  165. package/kits/docs/venues/group.md +25 -25
  166. package/kits/docs/venues/private.md +10 -10
  167. package/kits/eck_manifest.auxiliary.json +43 -43
  168. package/kits/eck_manifest.json +195 -183
  169. package/kits/eck_message_manifest.json +74 -74
  170. package/kits/migrations/README-role-config-v4.md +32 -32
  171. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  172. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  173. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  174. package/kits/migrations/rename-config-file.mjs +99 -99
  175. package/kits/rules/01-overview.md +162 -162
  176. package/kits/rules/02-navigation.md +96 -96
  177. package/kits/rules/03-identity.md +39 -37
  178. package/kits/rules/04-relation.md +75 -75
  179. package/kits/rules/05-venue.md +60 -60
  180. package/kits/rules/06-channel.md +63 -63
  181. package/kits/schemas/_meta.json +53 -53
  182. package/kits/schemas/agent-config.schema.1.json +177 -177
  183. package/kits/schemas/agent-config.schema.10.json +395 -395
  184. package/kits/schemas/agent-config.schema.11.json +421 -421
  185. package/kits/schemas/agent-config.schema.12.json +427 -427
  186. package/kits/schemas/agent-config.schema.2.json +239 -239
  187. package/kits/schemas/agent-config.schema.3.json +119 -119
  188. package/kits/schemas/agent-config.schema.4.json +208 -208
  189. package/kits/schemas/agent-config.schema.5.json +326 -326
  190. package/kits/schemas/agent-config.schema.6.json +322 -322
  191. package/kits/schemas/agent-config.schema.7.json +303 -303
  192. package/kits/schemas/agent-config.schema.8.json +304 -304
  193. package/kits/schemas/agent-config.schema.9.json +364 -364
  194. package/kits/schemas/contact-book.schema.1.json +36 -36
  195. package/kits/schemas/contact-book.schema.2.json +43 -43
  196. package/kits/schemas/contact-book.schema.3.json +67 -67
  197. package/kits/schemas/daemon.schema.1.json +91 -91
  198. package/kits/schemas/daemon.schema.2.json +101 -101
  199. package/kits/schemas/daemon.schema.3.json +123 -123
  200. package/kits/schemas/daemon.schema.4.json +136 -136
  201. package/kits/schemas/daemon.schema.5.json +135 -135
  202. package/kits/schemas/daemon.schema.6.json +131 -131
  203. package/kits/schemas/defaults.schema.1.json +81 -81
  204. package/kits/schemas/defaults.schema.2.json +85 -85
  205. package/kits/schemas/defaults.schema.3.json +73 -73
  206. package/kits/schemas/defaults.schema.4.json +107 -107
  207. package/kits/schemas/defaults.schema.5.json +119 -119
  208. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  209. package/kits/schemas/migrations/README.md +30 -30
  210. package/kits/schemas/relation-config.schema.1.json +158 -158
  211. package/kits/schemas/relation-config.schema.2.json +73 -73
  212. package/kits/schemas/relation-config.schema.3.json +50 -50
  213. package/kits/schemas/relation-config.schema.4.json +47 -47
  214. package/kits/schemas/relation-config.schema.5.json +47 -47
  215. package/kits/schemas/relation-config.schema.6.json +46 -46
  216. package/kits/schemas/relation-config.schema.7.json +59 -59
  217. package/kits/schemas/relation-config.schema.8.json +103 -103
  218. package/kits/schemas/role-config.schema.1.json +201 -201
  219. package/kits/schemas/role-registry.schema.1.json +35 -35
  220. package/kits/schemas/single-session.schema.1.json +31 -31
  221. package/kits/schemas/single-session.schema.2.json +57 -57
  222. package/kits/schemas/single-session.schema.3.json +32 -32
  223. package/kits/templates/bootstrap-welcome.md +17 -17
  224. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  225. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  226. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  227. package/kits/templates/message-fragments/inject-default.md +2 -2
  228. package/kits/templates/message-fragments/item.md +2 -2
  229. package/kits/templates/roles/admin.json +96 -82
  230. package/kits/templates/roles/member.json +97 -96
  231. package/kits/templates/roles/owner.json +9 -9
  232. package/kits/templates/roles/visitor.json +86 -85
  233. package/kits/templates/system-fragments/baseagent.md +14 -14
  234. package/kits/templates/system-fragments/bootstrap.md +38 -29
  235. package/kits/templates/system-fragments/channel.md +64 -64
  236. package/kits/templates/system-fragments/commands.md +28 -28
  237. package/kits/templates/system-fragments/identity.md +13 -13
  238. package/kits/templates/system-fragments/relation.md +19 -19
  239. package/kits/templates/system-fragments/session.md +59 -59
  240. package/kits/templates/system-fragments/venue.md +31 -31
  241. package/package.json +98 -94
  242. package/AGENTS.md +0 -62
  243. package/dist/utils/codex-cli.js +0 -43
  244. package/skills/eclink/SKILL.md +0 -84
  245. package/skills/eclink/agents/openai.yaml +0 -4
@@ -1,155 +1,160 @@
1
- param([Parameter(Mandatory = $true)][string]$Request)
2
-
3
- # Called by the Node installer. Paths and security descriptors are data, never
4
- # interpolated into PowerShell source. Use Windows PowerShell's built-in .NET ACL API.
5
- $ErrorActionPreference = 'Stop'
6
- # A parent pwsh session can export its incompatible PowerShell 7 modules.
7
- # This helper always runs in Windows PowerShell 5.1.
8
- $env:PSModulePath = Join-Path $PSHOME 'Modules'
9
- [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
10
-
11
- function Get-Descriptor([string]$Target) {
12
- return (Get-Acl -LiteralPath $Target).GetSecurityDescriptorSddlForm(
13
- [Security.AccessControl.AccessControlSections]'Access,Owner,Group')
14
- }
15
-
16
- function Set-Descriptor([string]$Target, [string]$Sddl) {
17
- $acl = Get-Acl -LiteralPath $Target
18
- $acl.SetSecurityDescriptorSddlForm($Sddl, [Security.AccessControl.AccessControlSections]'Access,Owner,Group')
19
- Set-Acl -LiteralPath $Target -AclObject $acl
20
- }
21
-
22
- function Add-Grant($Acl, [string]$Sid, [Security.AccessControl.FileSystemRights]$Rights, [bool]$Directory) {
23
- $inheritance = if ($Directory) { [Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit' } else { [Security.AccessControl.InheritanceFlags]::None }
24
- $rule = New-Object Security.AccessControl.FileSystemAccessRule(
25
- (New-Object Security.Principal.SecurityIdentifier($Sid)), $Rights, $inheritance,
26
- [Security.AccessControl.PropagationFlags]::None, [Security.AccessControl.AccessControlType]::Allow)
27
- $Acl.AddAccessRule($rule)
28
- }
29
-
30
- function Initialize-AccessProbe {
31
- # An elevated process reading the file is not evidence that a normal process
32
- # can read it. Evaluate the DACL with its UAC linked token when available.
33
- Add-Type -TypeDefinition @'
34
- using System;
35
- using System.Runtime.InteropServices;
36
- using System.Security.Principal;
37
- public static class EvolCoreAclProbe {
38
- [StructLayout(LayoutKind.Sequential)]
39
- struct Mapping { public uint Read, Write, Execute, All; }
40
- [DllImport("advapi32.dll", SetLastError=true)]
41
- static extern bool GetTokenInformation(IntPtr token, int type, out IntPtr linked, int size, out int returned);
42
- [DllImport("advapi32.dll", SetLastError=true)]
43
- static extern bool DuplicateToken(IntPtr token, int level, out IntPtr duplicate);
44
- [DllImport("advapi32.dll", SetLastError=true)]
45
- static extern bool AccessCheck(byte[] descriptor, IntPtr token, uint desired,
46
- ref Mapping mapping, byte[] privileges, ref uint length, out uint granted, out bool allowed);
47
- [DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr handle);
48
- public static string TokenKind;
49
- public static long Granted(byte[] descriptor) {
50
- using (var identity = WindowsIdentity.GetCurrent()) {
51
- var principal = new WindowsPrincipal(identity);
52
- IntPtr token = identity.Token, linked = IntPtr.Zero, duplicate = IntPtr.Zero;
53
- try {
54
- if (identity.IsSystem || principal.IsInRole(WindowsBuiltInRole.Administrator)) {
55
- int size;
56
- if (!GetTokenInformation(token, 19, out linked, IntPtr.Size, out size)) {
57
- TokenKind = "unavailable";
58
- return -1;
59
- }
60
- token = linked;
61
- TokenKind = "linked-token";
62
- } else { TokenKind = "current-token"; }
63
- if (!DuplicateToken(token, 2, out duplicate))
64
- throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
65
- var mapping = new Mapping { Read=0x120089, Write=0x120116, Execute=0x1200a0, All=0x1f01ff };
66
- uint length=1024, granted;
67
- bool allowed;
68
- if (!AccessCheck(descriptor, duplicate, 0x02000000, ref mapping,
69
- new byte[length], ref length, out granted, out allowed))
70
- throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
71
- return allowed ? granted : 0;
72
- } finally {
73
- if (duplicate != IntPtr.Zero) CloseHandle(duplicate);
74
- if (linked != IntPtr.Zero) CloseHandle(linked);
75
- }
76
- }
77
- }
78
- }
79
- '@
80
- }
81
-
82
- function Verify-Access($Entry) {
83
- $target = [string]$Entry.path
84
- $acl = Get-Acl -LiteralPath $target
85
- $read = [long][Security.AccessControl.FileSystemRights]::ReadAndExecute
86
- $write = [long][Security.AccessControl.FileSystemRights]'Write,Delete,DeleteSubdirectoriesAndFiles,ChangePermissions,TakeOwnership'
87
- if ($Entry.protected) {
88
- $grants = @{}
89
- foreach ($rule in $acl.GetAccessRules($true, $true, [Security.Principal.SecurityIdentifier])) {
90
- if (($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) -ne 0) { continue }
91
- $sid = $rule.IdentityReference.Value
92
- $rights = [long]$rule.FileSystemRights
93
- if ($sid -in @('S-1-1-0','S-1-5-11','S-1-5-32-545')) {
94
- if ($rule.AccessControlType -eq 'Allow' -and ($rights -band $write) -ne 0) {
95
- throw "Unsafe write permission on ${target}: $sid has $($rule.FileSystemRights). Existing policy was not removed."
96
- }
97
- if ($rule.AccessControlType -eq 'Deny' -and ($rights -band $read) -ne 0) {
98
- throw "Read permission denied on ${target}: $sid. Existing policy was not removed."
99
- }
100
- }
101
- if ($rule.AccessControlType -eq 'Allow') { $grants[$sid] = ([long]$grants[$sid]) -bor $rights }
102
- }
103
- foreach ($sid in @('S-1-5-18','S-1-5-32-544','S-1-5-32-545')) {
104
- $required = if ($sid -eq 'S-1-5-32-545') { $read } else { [long][Security.AccessControl.FileSystemRights]::FullControl }
105
- if ((([long]$grants[$sid]) -band $required) -ne $required) { throw "Missing required access on ${target}: $sid" }
106
- }
107
- }
108
- $granted = [EvolCoreAclProbe]::Granted($acl.GetSecurityDescriptorBinaryForm())
109
- if ($granted -ge 0) {
110
- $required = if ($Entry.execute) { $read } else { [long][Security.AccessControl.FileSystemRights]::Read }
111
- if (($granted -band $required) -ne $required) { throw "Normal-token read/execute access denied: $target" }
112
- if ($Entry.protected -and ($granted -band $write) -ne 0) { throw "Normal token can modify the protected file or its ACL: $target" }
113
- }
114
- return @{ path = $target; userProbe = [EvolCoreAclProbe]::TokenKind }
115
- }
116
-
117
- try {
118
- $payload = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Request)) | ConvertFrom-Json
119
- $result = switch ($payload.action) {
120
- 'preflight' {
121
- $principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
122
- if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
123
- throw 'Machine-wide Codex hook provisioning requires an elevated Windows installer. npm installation does not require this step.'
124
- }
125
- @{ elevated = $true }
126
- }
127
- 'snapshot' { @{ sddl = (Get-Descriptor $payload.path) } }
128
- 'prepare' {
129
- $target = [string]$payload.path
130
- if ($payload.reference) {
131
- # Same-directory staging preserves the shared configuration's owner,
132
- # inherited policy and custom administrator ACEs. Never reset its ACL.
133
- Set-Descriptor $target (Get-Descriptor $payload.reference)
134
- } else {
135
- $flags = if ($payload.directory) { 'OICI' } else { '' }
136
- Set-Descriptor $target "O:BAG:BAD:P(A;${flags};FA;;;SY)(A;${flags};FA;;;BA)(A;${flags};FRFX;;;BU)"
137
- }
138
- $acl = Get-Acl -LiteralPath $target
139
- Add-Grant $acl 'S-1-5-18' 'FullControl' ([bool]$payload.directory)
140
- Add-Grant $acl 'S-1-5-32-544' 'FullControl' ([bool]$payload.directory)
141
- Add-Grant $acl 'S-1-5-32-545' 'ReadAndExecute' ([bool]$payload.directory)
142
- Set-Acl -LiteralPath $target -AclObject $acl
143
- @{ prepared = $true }
144
- }
145
- 'verify' {
146
- Initialize-AccessProbe
147
- @($payload.entries | ForEach-Object { Verify-Access $_ })
148
- }
149
- default { throw "Unknown ACL operation: $($payload.action)" }
150
- }
151
- ConvertTo-Json -InputObject $result -Depth 8 -Compress
152
- } catch {
153
- [Console]::Error.WriteLine("[evolcore] ACL $($payload.action) failed for $($payload.path): $($_.Exception.Message)")
154
- exit 1
155
- }
1
+ param([Parameter(Mandatory = $true)][string]$Request)
2
+
3
+ # Called by the Node installer. Paths and security descriptors are data, never
4
+ # interpolated into PowerShell source. Use Windows PowerShell's built-in .NET ACL API.
5
+ $ErrorActionPreference = 'Stop'
6
+ $ProgressPreference = 'SilentlyContinue'
7
+ # A parent pwsh session can export its incompatible PowerShell 7 modules.
8
+ # This helper always runs in Windows PowerShell 5.1.
9
+ $env:PSModulePath = Join-Path $PSHOME 'Modules'
10
+ [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
11
+
12
+ function Get-Descriptor([string]$Target) {
13
+ return (Get-Acl -LiteralPath $Target).GetSecurityDescriptorSddlForm(
14
+ [Security.AccessControl.AccessControlSections]'Access,Owner,Group')
15
+ }
16
+
17
+ function Set-Descriptor([string]$Target, [string]$Sddl) {
18
+ $acl = Get-Acl -LiteralPath $Target
19
+ $acl.SetSecurityDescriptorSddlForm($Sddl, [Security.AccessControl.AccessControlSections]'Access,Owner,Group')
20
+ Set-Acl -LiteralPath $Target -AclObject $acl
21
+ }
22
+
23
+ function Add-Grant($Acl, [string]$Sid, [Security.AccessControl.FileSystemRights]$Rights, [bool]$Directory) {
24
+ $inheritance = if ($Directory) { [Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit' } else { [Security.AccessControl.InheritanceFlags]::None }
25
+ $rule = New-Object Security.AccessControl.FileSystemAccessRule(
26
+ (New-Object Security.Principal.SecurityIdentifier($Sid)), $Rights, $inheritance,
27
+ [Security.AccessControl.PropagationFlags]::None, [Security.AccessControl.AccessControlType]::Allow)
28
+ $Acl.AddAccessRule($rule)
29
+ }
30
+
31
+ function Initialize-AccessProbe {
32
+ # An elevated process reading the file is not evidence that a normal process
33
+ # can read it. Evaluate the DACL with its UAC linked token when available.
34
+ Add-Type -TypeDefinition @'
35
+ using System;
36
+ using System.Runtime.InteropServices;
37
+ using System.Security.Principal;
38
+ public static class EvolCoreAclProbe {
39
+ [StructLayout(LayoutKind.Sequential)]
40
+ struct Mapping { public uint Read, Write, Execute, All; }
41
+ [DllImport("advapi32.dll", SetLastError=true)]
42
+ static extern bool GetTokenInformation(IntPtr token, int type, out IntPtr linked, int size, out int returned);
43
+ [DllImport("advapi32.dll", SetLastError=true)]
44
+ static extern bool DuplicateTokenEx(IntPtr token, uint desiredAccess, IntPtr tokenAttributes,
45
+ int impersonationLevel, int tokenType, out IntPtr duplicate);
46
+ [DllImport("advapi32.dll", SetLastError=true)]
47
+ static extern bool AccessCheck(byte[] descriptor, IntPtr token, uint desired,
48
+ ref Mapping mapping, byte[] privileges, ref uint length, out uint granted, out bool allowed);
49
+ [DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr handle);
50
+ public static string TokenKind;
51
+ public static long Granted(byte[] descriptor) {
52
+ using (var identity = WindowsIdentity.GetCurrent()) {
53
+ var principal = new WindowsPrincipal(identity);
54
+ IntPtr token = identity.Token, linked = IntPtr.Zero, duplicate = IntPtr.Zero;
55
+ try {
56
+ if (identity.IsSystem || principal.IsInRole(WindowsBuiltInRole.Administrator)) {
57
+ int size;
58
+ if (!GetTokenInformation(token, 19, out linked, IntPtr.Size, out size)) {
59
+ TokenKind = "unavailable";
60
+ return -1;
61
+ }
62
+ token = linked;
63
+ TokenKind = "linked-token";
64
+ } else { TokenKind = "current-token"; }
65
+ // AccessCheck only needs SecurityIdentification (1). A UAC linked
66
+ // token can be limited to that level, so requesting the higher
67
+ // SecurityImpersonation level fails with ERROR_BAD_IMPERSONATION_LEVEL.
68
+ if (!DuplicateTokenEx(token, 0x0000000A, IntPtr.Zero, 1, 2, out duplicate))
69
+ throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
70
+ var mapping = new Mapping { Read=0x120089, Write=0x120116, Execute=0x1200a0, All=0x1f01ff };
71
+ uint length=1024, granted;
72
+ bool allowed;
73
+ if (!AccessCheck(descriptor, duplicate, 0x02000000, ref mapping,
74
+ new byte[length], ref length, out granted, out allowed))
75
+ throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
76
+ return allowed ? granted : 0;
77
+ } finally {
78
+ if (duplicate != IntPtr.Zero) CloseHandle(duplicate);
79
+ if (linked != IntPtr.Zero) CloseHandle(linked);
80
+ }
81
+ }
82
+ }
83
+ }
84
+ '@
85
+ }
86
+
87
+ function Verify-Access($Entry) {
88
+ $target = [string]$Entry.path
89
+ $acl = Get-Acl -LiteralPath $target
90
+ $read = [long][Security.AccessControl.FileSystemRights]::ReadAndExecute
91
+ $write = [long][Security.AccessControl.FileSystemRights]'Write,Delete,DeleteSubdirectoriesAndFiles,ChangePermissions,TakeOwnership'
92
+ if ($Entry.protected) {
93
+ $grants = @{}
94
+ foreach ($rule in $acl.GetAccessRules($true, $true, [Security.Principal.SecurityIdentifier])) {
95
+ if (($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) -ne 0) { continue }
96
+ $sid = $rule.IdentityReference.Value
97
+ $rights = [long]$rule.FileSystemRights
98
+ if ($sid -in @('S-1-1-0','S-1-5-11','S-1-5-32-545')) {
99
+ if ($rule.AccessControlType -eq 'Allow' -and ($rights -band $write) -ne 0) {
100
+ throw "Unsafe write permission on ${target}: $sid has $($rule.FileSystemRights). Existing policy was not removed."
101
+ }
102
+ if ($rule.AccessControlType -eq 'Deny' -and ($rights -band $read) -ne 0) {
103
+ throw "Read permission denied on ${target}: $sid. Existing policy was not removed."
104
+ }
105
+ }
106
+ if ($rule.AccessControlType -eq 'Allow') { $grants[$sid] = ([long]$grants[$sid]) -bor $rights }
107
+ }
108
+ foreach ($sid in @('S-1-5-18','S-1-5-32-544','S-1-5-32-545')) {
109
+ $required = if ($sid -eq 'S-1-5-32-545') { $read } else { [long][Security.AccessControl.FileSystemRights]::FullControl }
110
+ if ((([long]$grants[$sid]) -band $required) -ne $required) { throw "Missing required access on ${target}: $sid" }
111
+ }
112
+ }
113
+ $granted = [EvolCoreAclProbe]::Granted($acl.GetSecurityDescriptorBinaryForm())
114
+ if ($granted -ge 0) {
115
+ $required = if ($Entry.execute) { $read } else { [long][Security.AccessControl.FileSystemRights]::Read }
116
+ if (($granted -band $required) -ne $required) { throw "Normal-token read/execute access denied: $target" }
117
+ if ($Entry.protected -and ($granted -band $write) -ne 0) { throw "Normal token can modify the protected file or its ACL: $target" }
118
+ }
119
+ return @{ path = $target; userProbe = [EvolCoreAclProbe]::TokenKind }
120
+ }
121
+
122
+ try {
123
+ $payload = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Request)) | ConvertFrom-Json
124
+ $result = switch ($payload.action) {
125
+ 'preflight' {
126
+ $principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
127
+ if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
128
+ throw 'Machine-wide Codex hook provisioning requires an elevated Windows installer. npm installation does not require this step.'
129
+ }
130
+ @{ elevated = $true }
131
+ }
132
+ 'snapshot' { @{ sddl = (Get-Descriptor $payload.path) } }
133
+ 'prepare' {
134
+ $target = [string]$payload.path
135
+ if ($payload.reference) {
136
+ # Same-directory staging preserves the shared configuration's owner,
137
+ # inherited policy and custom administrator ACEs. Never reset its ACL.
138
+ Set-Descriptor $target (Get-Descriptor $payload.reference)
139
+ } else {
140
+ $flags = if ($payload.directory) { 'OICI' } else { '' }
141
+ Set-Descriptor $target "O:BAG:BAD:P(A;${flags};FA;;;SY)(A;${flags};FA;;;BA)(A;${flags};FRFX;;;BU)"
142
+ }
143
+ $acl = Get-Acl -LiteralPath $target
144
+ Add-Grant $acl 'S-1-5-18' 'FullControl' ([bool]$payload.directory)
145
+ Add-Grant $acl 'S-1-5-32-544' 'FullControl' ([bool]$payload.directory)
146
+ Add-Grant $acl 'S-1-5-32-545' 'ReadAndExecute' ([bool]$payload.directory)
147
+ Set-Acl -LiteralPath $target -AclObject $acl
148
+ @{ prepared = $true }
149
+ }
150
+ 'verify' {
151
+ Initialize-AccessProbe
152
+ @($payload.entries | ForEach-Object { Verify-Access $_ })
153
+ }
154
+ default { throw "Unknown ACL operation: $($payload.action)" }
155
+ }
156
+ ConvertTo-Json -InputObject $result -Depth 8 -Compress
157
+ } catch {
158
+ [Console]::Error.WriteLine("[evolcore] ACL $($payload.action) failed for $($payload.path): $($_.Exception.Message)")
159
+ exit 1
160
+ }
@@ -1,95 +1,212 @@
1
- import fs from 'node:fs';
2
- import net from 'node:net';
3
-
4
- // Only bound socket establishment. Once connected, the official Codex hook
5
- // timeout owns the lifetime of this process; an application timeout here would
6
- // turn /pause into a denial/allow race.
7
- const connectTimeoutMs = 1500;
8
-
9
- function deny(reason) {
10
- process.stdout.write(JSON.stringify({
11
- hookSpecificOutput: {
12
- hookEventName: 'PreToolUse',
13
- permissionDecision: 'deny',
14
- permissionDecisionReason: reason || 'EvolCore policy denied this tool call.',
15
- },
16
- }));
17
- // Exit 2 is the documented blocking transport. Keep the structured JSON as
18
- // well so Codex can surface the policy reason through the PreToolUse result.
19
- process.stderr.write(`${reason || 'EvolCore policy denied this tool call.'}\n`);
20
- process.exitCode = 2;
21
- }
22
-
23
- function queryDaemon(socketPath, payload) {
24
- return new Promise(resolve => {
25
- const socket = net.createConnection(socketPath);
26
- let buffer = '';
27
- let settled = false;
28
- let timer;
29
- let connected = false;
30
- const finish = value => {
31
- if (settled) return;
32
- settled = true;
33
- clearTimeout(timer);
34
- socket.destroy();
35
- resolve(value);
36
- };
37
- timer = setTimeout(() => {
38
- if (!connected) finish(null);
39
- }, connectTimeoutMs);
40
- timer.unref?.();
41
- socket.once('error', () => finish(null));
42
- socket.once('connect', () => {
43
- connected = true;
44
- clearTimeout(timer);
45
- socket.write(JSON.stringify(payload) + '\n');
46
- });
47
- socket.on('data', chunk => {
48
- buffer += String(chunk);
49
- const index = buffer.indexOf('\n');
50
- if (index < 0) return;
51
- try { finish(JSON.parse(buffer.slice(0, index))); } catch { finish(null); }
52
- });
53
- });
54
- }
55
-
56
- async function main() {
57
- // Windows managed requirements use one machine-wide command. Resolve the
58
- // per-daemon named pipe from the inherited environment so multiple EvolCore
59
- // homes can share the installed hook without rewriting requirements.toml.
60
- const socketPath = process.argv[2] || process.env.EVOLCORE_INSTANCE_SOCKET;
61
- let input;
62
- try {
63
- input = JSON.parse(fs.readFileSync(0, 'utf8'));
64
- } catch {
65
- deny('Invalid Codex PreToolUse payload.');
66
- return;
67
- }
1
+ import fs from 'node:fs';
2
+ import net from 'node:net';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
6
+
7
+ const startedAt = Date.now();
8
+ const runId = randomUUID();
9
+ let logPath;
10
+ let logFailed = false;
11
+ let context = {};
12
+
13
+ // This file is also copied into the Linux runtime; keep diagnostics self-contained.
14
+ function diagnostic(event, details = {}) {
15
+ if (process.env.EVOLCORE_CODEX_HOOK_LOG === '0' || logFailed) return;
16
+ try {
17
+ if (!logPath) {
18
+ logPath = path.resolve(process.env.EVOLCORE_CODEX_HOOK_LOG
19
+ || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'log', 'evolcore-hook.jsonl'));
20
+ fs.mkdirSync(path.dirname(logPath), { recursive: true, mode: 0o700 });
21
+ // Rotation is best-effort when concurrent tool calls start together.
22
+ try {
23
+ if (fs.statSync(logPath).size >= 2 * 1024 * 1024) fs.renameSync(logPath, `${logPath}.1`);
24
+ } catch {}
25
+ }
26
+ fs.appendFileSync(logPath, JSON.stringify({
27
+ time: new Date().toISOString(), runId, pid: process.pid,
28
+ elapsedMs: Date.now() - startedAt, event, ...context, ...details,
29
+ }) + '\n', { mode: 0o600 });
30
+ } catch (error) {
31
+ logFailed = true;
32
+ process.stderr.write(`[evolcore] Hook diagnostic log unavailable (${error?.code || 'unknown'}): ${logPath || '(unresolved)'}\n`);
33
+ }
34
+ }
35
+
36
+ function errorDetails(error) {
37
+ // JSON parser messages and policy reasons can contain tool input. Omit them.
38
+ return {
39
+ errorName: error?.name,
40
+ errorCode: error?.code,
41
+ syscall: error?.syscall,
42
+ frames: typeof error?.stack === 'string'
43
+ ? error.stack.split('\n').filter(line => /^\s+at /.test(line)).slice(0, 6) : undefined,
44
+ };
45
+ }
46
+
47
+ process.on('uncaughtExceptionMonitor', error => diagnostic('uncaught_exception', errorDetails(error)));
48
+ process.on('exit', code => diagnostic('exit', { exitCode: code }));
49
+ diagnostic('start', {
50
+ nodeVersion: process.version, execPath: process.execPath, entrypoint: process.argv[1],
51
+ argumentCount: Math.max(0, process.argv.length - 2),
52
+ socketArgumentPresent: Boolean(process.argv[2]),
53
+ socketEnvironmentPresent: Boolean(process.env.EVOLCORE_INSTANCE_SOCKET),
54
+ socketArgumentUnexpanded: /^%EVOLCORE_INSTANCE_SOCKET%$/i.test(process.argv[2] || ''),
55
+ socketSource: process.argv[2] ? 'argument' : process.env.EVOLCORE_INSTANCE_SOCKET ? 'environment' : 'missing',
56
+ });
57
+
58
+ // Only bound socket establishment. Once connected, the official Codex hook
59
+ // timeout owns the lifetime of this process; an application timeout here would
60
+ // turn /pause into a denial/allow race.
61
+ const connectTimeoutMs = 1500;
62
+
63
+ function deny(reason, category) {
64
+ diagnostic('decision', { decision: 'deny', category });
65
+ process.stdout.write(JSON.stringify({
66
+ hookSpecificOutput: {
67
+ hookEventName: 'PreToolUse',
68
+ permissionDecision: 'deny',
69
+ permissionDecisionReason: reason || 'EvolCore policy denied this tool call.',
70
+ },
71
+ }));
72
+ // Exit 2 is the documented blocking transport. Keep the structured JSON as
73
+ // well so Codex can surface the policy reason through the PreToolUse result.
74
+ process.stderr.write(`${reason || 'EvolCore policy denied this tool call.'}\n`);
75
+ process.exitCode = 2;
76
+ }
77
+
78
+ function queryDaemon(socketPath, payload) {
79
+ diagnostic('connect_start', { timeoutMs: connectTimeoutMs });
80
+ return new Promise(resolve => {
81
+ const socket = net.createConnection(socketPath);
82
+ let buffer = '';
83
+ let settled = false;
84
+ let timer;
85
+ let connected = false;
86
+ const finish = value => {
87
+ if (settled) return;
88
+ settled = true;
89
+ clearTimeout(timer);
90
+ socket.destroy();
91
+ resolve(value);
92
+ };
93
+ timer = setTimeout(() => {
94
+ if (!connected) {
95
+ diagnostic('connect_timeout');
96
+ finish(null);
97
+ }
98
+ }, connectTimeoutMs);
99
+ timer.unref?.();
100
+ socket.once('error', error => {
101
+ diagnostic('socket_error', { connected, ...errorDetails(error) });
102
+ finish(null);
103
+ });
104
+ socket.once('close', () => {
105
+ if (!settled) diagnostic('socket_closed_without_response', { connected, responseBytes: Buffer.byteLength(buffer) });
106
+ });
107
+ socket.once('connect', () => {
108
+ connected = true;
109
+ clearTimeout(timer);
110
+ diagnostic('connected');
111
+ socket.write(JSON.stringify(payload) + '\n');
112
+ });
113
+ socket.on('data', chunk => {
114
+ buffer += String(chunk);
115
+ const index = buffer.indexOf('\n');
116
+ if (index < 0) return;
117
+ try {
118
+ const result = JSON.parse(buffer.slice(0, index));
119
+ diagnostic('response', {
120
+ ok: result?.ok === true, applicable: result?.applicable === true,
121
+ decision: ['allow', 'deny'].includes(result?.decision) ? result.decision : 'other',
122
+ reasonPresent: typeof result?.reason === 'string',
123
+ });
124
+ finish(result);
125
+ } catch (error) {
126
+ diagnostic('invalid_response', { responseBytes: Buffer.byteLength(buffer), ...errorDetails(error) });
127
+ finish(null);
128
+ }
129
+ });
130
+ });
131
+ }
132
+
133
+ // The Windows managed requirements are machine-wide, so unmanaged Codex
134
+ // processes also reach this hook. EvolCore-owned threads carry a per-thread
135
+ // credential (EVOLCORE_CODEX_SESSION_TOKEN, injected through the thread's
136
+ // shell environment policy); unmanaged ones do not. Unmanaged calls are
137
+ // allowed outright and never contact the daemon — EvolCore being stopped must
138
+ // not break a standalone Codex. Credentialed calls always require an explicit
139
+ // daemon allow: a credential without its daemon is an orphaned managed thread
140
+ // and fails closed.
141
+ async function main() {
142
+ // Windows managed requirements use one machine-wide command. Resolve the
143
+ // per-daemon named pipe from the inherited environment so multiple EvolCore
144
+ // homes can share the installed hook without rewriting requirements.toml.
145
+ const socketPath = process.argv[2] || process.env.EVOLCORE_INSTANCE_SOCKET;
146
+ let input;
147
+ try {
148
+ input = JSON.parse(fs.readFileSync(0, 'utf8'));
149
+ } catch (error) {
150
+ diagnostic('invalid_input', errorDetails(error));
151
+ deny('Invalid Codex PreToolUse payload.', 'invalid_input');
152
+ return;
153
+ }
154
+ context = {
155
+ sessionId: typeof input?.session_id === 'string' ? input.session_id.slice(0, 128) : undefined,
156
+ toolName: typeof input?.tool_name === 'string' ? input.tool_name.slice(0, 128) : undefined,
157
+ callId: typeof (input?.tool_use_id ?? input?.toolUseId ?? input?.item_id ?? input?.itemId) === 'string'
158
+ ? (input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId).slice(0, 128) : undefined,
159
+ };
160
+ diagnostic('input', {
161
+ preToolUse: input?.hook_event_name === 'PreToolUse',
162
+ sessionIdPresent: typeof input?.session_id === 'string' && Boolean(input.session_id),
163
+ toolNamePresent: typeof input?.tool_name === 'string' && Boolean(input.tool_name),
164
+ toolInputPresent: input?.tool_input !== undefined,
165
+ });
68
166
  if (!socketPath
69
167
  || input?.hook_event_name !== 'PreToolUse'
70
168
  || typeof input?.session_id !== 'string'
71
- || !input.session_id
72
- || typeof input?.tool_name !== 'string'
73
- || !input.tool_name) {
74
- deny('Incomplete Codex PreToolUse payload.');
75
- return;
76
- }
77
-
78
- const result = await queryDaemon(socketPath, {
79
- type: 'codex-pre-tool-use',
80
- threadId: input.session_id,
81
- toolName: input.tool_name,
82
- toolInput: input.tool_input ?? null,
83
- ...(typeof (input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId) === 'string'
84
- ? { callId: input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId }
85
- : {}),
86
- });
87
- if (!result) {
88
- deny('EvolCore policy service is unavailable.');
89
- return;
90
- }
91
- if (result.ok === true && result.applicable === true && result.decision === 'allow') return;
92
- deny(result.reason || 'EvolCore policy did not explicitly allow this tool call.');
93
- }
94
-
95
- void main();
169
+ || !input.session_id
170
+ || typeof input?.tool_name !== 'string'
171
+ || !input.tool_name) {
172
+ deny('Incomplete Codex PreToolUse payload.', 'incomplete_input');
173
+ return;
174
+ }
175
+
176
+ const sessionToken = typeof process.env.EVOLCORE_CODEX_SESSION_TOKEN === 'string'
177
+ && process.env.EVOLCORE_CODEX_SESSION_TOKEN
178
+ ? process.env.EVOLCORE_CODEX_SESSION_TOKEN
179
+ : undefined;
180
+
181
+ if (!sessionToken) {
182
+ // Unmanaged process: nothing to enforce, and no reason to pay the daemon
183
+ // round-trip (or its outage) for a call we would allow anyway.
184
+ return;
185
+ }
186
+ if (!socketPath) {
187
+ deny('EvolCore session credential is present but the policy service address is missing.');
188
+ return;
189
+ }
190
+
191
+ const result = await queryDaemon(socketPath, {
192
+ type: 'codex-pre-tool-use',
193
+ threadId: input.session_id,
194
+ toolName: input.tool_name,
195
+ toolInput: input.tool_input ?? null,
196
+ sessionToken,
197
+ ...(typeof (input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId) === 'string'
198
+ ? { callId: input.tool_use_id ?? input.toolUseId ?? input.item_id ?? input.itemId }
199
+ : {}),
200
+ });
201
+ if (!result) {
202
+ deny('EvolCore policy service is unavailable.', 'service_unavailable');
203
+ return;
204
+ }
205
+ if (result.ok === true && result.applicable === true && result.decision === 'allow') {
206
+ diagnostic('decision', { decision: 'allow' });
207
+ return;
208
+ }
209
+ deny(result.reason || 'EvolCore policy did not explicitly allow this tool call.', 'policy_denied');
210
+ }
211
+
212
+ void main();