evolcore 0.0.25 → 0.0.27

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 (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -5,34 +5,88 @@ import { getExistingHClassMaskTargets, getExistingLClassReadOnlyTargets, isSameO
5
5
  import { resolveRoot } from '../../paths.js';
6
6
  let cachedBubblewrapPath;
7
7
  let sandboxInitializationCircuit;
8
+ const sessionSandboxInitializationCircuits = new Map();
8
9
  let sandboxCircuitNoticeAt = 0;
10
+ let sandboxInitializationFailureCounters = emptySandboxInitializationFailureCounters();
9
11
  export const SANDBOX_INITIALIZATION_FAILED = 'sandbox_initialization_failed';
10
12
  export const SANDBOX_INITIALIZATION_COOLDOWN_MS = 5 * 60 * 1000;
11
13
  export const SANDBOX_CIRCUIT_NOTICE_INTERVAL_MS = 60 * 1000;
14
+ function emptySandboxInitializationFailureCounters() {
15
+ return {
16
+ total: 0,
17
+ applySeccomp: 0,
18
+ byKind: { uid_map: 0, gid_map: 0, seccomp: 0, user_namespace: 0, bubblewrap: 0, unknown: 0 },
19
+ };
20
+ }
21
+ /** Classify already recognized bootstrap evidence, never arbitrary tool output. */
22
+ function sandboxInitializationFailureKind(text) {
23
+ if (/\buid_map\b/i.test(text))
24
+ return 'uid_map';
25
+ if (/\bgid_map\b/i.test(text))
26
+ return 'gid_map';
27
+ if (/\bseccomp\b/i.test(text))
28
+ return 'seccomp';
29
+ if (/user namespace/i.test(text))
30
+ return 'user_namespace';
31
+ if (/\b(?:bwrap|bubblewrap)\b/i.test(text))
32
+ return 'bubblewrap';
33
+ return 'unknown';
34
+ }
35
+ export function getSandboxInitializationFailureCounters() {
36
+ return { ...sandboxInitializationFailureCounters, byKind: { ...sandboxInitializationFailureCounters.byKind } };
37
+ }
12
38
  const SANDBOX_DIAGNOSTIC_TARGET_LIMIT = 256;
13
- /** Open a process-wide fail-closed circuit after a confirmed host bootstrap failure. */
14
- export function recordSandboxInitializationFailure(error, stderr = [], now = Date.now()) {
15
- const detail = createSandboxInitializationError(error, stderr).message.slice(0, 512);
16
- sandboxInitializationCircuit = sandboxInitializationCircuit
39
+ /** Host bootstrap failures open a process circuit; tool-result failures only
40
+ * affect their originating session because tool output is not host attestation. */
41
+ export function recordSandboxInitializationFailure(error, stderr = [], now = Date.now(), sessionId) {
42
+ const wrapped = createSandboxInitializationError(error, stderr);
43
+ const evidence = [wrapped.message, ...(wrapped.stderr ?? [])].join('\n');
44
+ const kind = sandboxInitializationFailureKind(evidence);
45
+ sandboxInitializationFailureCounters.total++;
46
+ sandboxInitializationFailureCounters.byKind[kind]++;
47
+ if (/\bapply-seccomp\b/i.test(evidence))
48
+ sandboxInitializationFailureCounters.applySeccomp++;
49
+ const detail = wrapped.message.slice(0, 512);
50
+ getActiveSandboxInitializationFailure(now);
51
+ const previous = sessionId ? sessionSandboxInitializationCircuits.get(sessionId) : sandboxInitializationCircuit;
52
+ const circuit = previous
17
53
  ? {
18
- ...sandboxInitializationCircuit,
54
+ ...previous,
19
55
  lastObservedAt: now,
20
- occurrences: sandboxInitializationCircuit.occurrences + 1,
56
+ occurrences: previous.occurrences + 1,
21
57
  detail,
58
+ kind,
22
59
  }
23
- : { openedAt: now, lastObservedAt: now, occurrences: 1, detail };
24
- return { ...sandboxInitializationCircuit };
25
- }
26
- /** Return an active failure circuit, or allow one probe after the cooldown. */
27
- export function getActiveSandboxInitializationFailure(now = Date.now(), cooldownMs = SANDBOX_INITIALIZATION_COOLDOWN_MS) {
28
- if (!sandboxInitializationCircuit)
29
- return undefined;
30
- if (now - sandboxInitializationCircuit.lastObservedAt >= cooldownMs) {
60
+ : { openedAt: now, lastObservedAt: now, occurrences: 1, detail, kind };
61
+ if (sessionId)
62
+ sessionSandboxInitializationCircuits.set(sessionId, circuit);
63
+ else
64
+ sandboxInitializationCircuit = circuit;
65
+ return { ...circuit };
66
+ }
67
+ /** Expire old failures and return the process or requested session circuit. */
68
+ export function getActiveSandboxInitializationFailure(now = Date.now(), cooldownMs = SANDBOX_INITIALIZATION_COOLDOWN_MS, sessionId) {
69
+ for (const [id, circuit] of sessionSandboxInitializationCircuits) {
70
+ if (now - circuit.lastObservedAt >= cooldownMs)
71
+ sessionSandboxInitializationCircuits.delete(id);
72
+ }
73
+ if (sandboxInitializationCircuit && now - sandboxInitializationCircuit.lastObservedAt >= cooldownMs) {
31
74
  sandboxInitializationCircuit = undefined;
32
75
  sandboxCircuitNoticeAt = 0;
33
- return undefined;
34
76
  }
35
- return { ...sandboxInitializationCircuit };
77
+ const circuit = sandboxInitializationCircuit ?? (sessionId ? sessionSandboxInitializationCircuits.get(sessionId) : undefined);
78
+ return circuit ? { ...circuit } : undefined;
79
+ }
80
+ /** Only recognize the complete native bootstrap diagnostic, never a quotation
81
+ * embedded in ordinary tool output. Tool-result evidence is session-scoped. */
82
+ export function isSandboxInitializationToolResult(content) {
83
+ const text = typeof content === 'string'
84
+ ? content
85
+ : Array.isArray(content) && content.every(block => block?.type === 'text' && typeof block.text === 'string')
86
+ ? content.map(block => block.text).join('\n')
87
+ : '';
88
+ return /^(?:Exit code [1-9]\d*\r?\n)?apply-seccomp: write \/proc\/self\/(?:uid|gid)_map: (?:Operation not permitted|Permission denied)\s*$/i
89
+ .test(text.trim());
36
90
  }
37
91
  /** Rate-limit human-readable warnings; structured audit still records every blocked task. */
38
92
  export function shouldLogSandboxCircuitNotice(now = Date.now(), intervalMs = SANDBOX_CIRCUIT_NOTICE_INTERVAL_MS) {
@@ -47,11 +101,13 @@ export function isSandboxInitializationFailure(error, stderr = []) {
47
101
  .join('\n')
48
102
  .toLowerCase();
49
103
  return text.includes(SANDBOX_INITIALIZATION_FAILED)
104
+ || /fs sandbox helper failed(?: with status)?\b/.test(text)
50
105
  || /(?:apply-seccomp|seccomp)[\s\S]*(?:uid_map|gid_map|operation not permitted)/.test(text)
51
106
  || /(?:uid_map|gid_map)[\s\S]*(?:operation not permitted|permission denied)/.test(text)
52
107
  || /user namespace[\s\S]*(?:not permitted|unavailable|failed)/.test(text)
53
108
  || /bubblewrap[\s\S]*namespace[\s\S]*(?:failed|not permitted|permission denied)/.test(text)
54
- || /(?:^|\n)\s*bwrap:\s*(?:can't|cannot|unable to)[\s\S]*(?:bind|mount|mount table)/.test(text);
109
+ || /(?:^|\n)\s*bwrap:\s*(?:can't|cannot|unable to)[\s\S]*(?:bind|mount|mount table)/.test(text)
110
+ || /(?:^|\n)\s*bwrap:\s*(?:can't|cannot) create (?:file|directory) at [^\n]+: read-only file system/.test(text);
55
111
  }
56
112
  export function createSandboxInitializationError(error, stderr = []) {
57
113
  if (error instanceof Error
@@ -77,6 +133,39 @@ export function createSandboxInitializationError(error, stderr = []) {
77
133
  export function shouldFailIfClaudeSandboxUnavailable(platform = process.platform) {
78
134
  return platform !== 'win32';
79
135
  }
136
+ /**
137
+ * In an unprivileged user namespace (for example, root in an LXC container),
138
+ * dropping all capabilities before apply-seccomp creates its child namespace
139
+ * prevents that helper from writing a mapping for UID 0 (CAP_SETFCAP).
140
+ * Use the SDK's nested-sandbox compatibility mode only for confirmed remapped
141
+ * root. Unknown host state must not opt into weaker isolation.
142
+ */
143
+ export function shouldEnableClaudeWeakerNestedSandbox(platform = process.platform, effectiveUid = typeof process.geteuid === 'function' ? process.geteuid() : undefined) {
144
+ if (platform !== 'linux' || effectiveUid !== 0)
145
+ return false;
146
+ let uidMap;
147
+ try {
148
+ uidMap = fs.readFileSync('/proc/self/uid_map', 'utf8').trim();
149
+ }
150
+ catch {
151
+ return false;
152
+ }
153
+ if (!uidMap)
154
+ return false;
155
+ let remappedRoot = false;
156
+ for (const line of uidMap.split(/\r?\n/)) {
157
+ const fields = line.trim().match(/^(\d+)\s+(\d+)\s+(\d+)$/);
158
+ if (!fields)
159
+ return false;
160
+ const [inside, outside, count] = fields.slice(1).map(Number);
161
+ if (![inside, outside, count].every(Number.isSafeInteger)
162
+ || count === 0 || inside + count > 0xffffffff || outside + count > 0xffffffff)
163
+ return false;
164
+ if (inside === 0 && outside !== 0)
165
+ remappedRoot = true;
166
+ }
167
+ return remappedRoot;
168
+ }
80
169
  function isExecutableFile(candidate) {
81
170
  if (!candidate)
82
171
  return false;
@@ -561,7 +650,9 @@ export function buildBubblewrapCommand(executable, executableArgs, options) {
561
650
  return { command: bubblewrapPath, args };
562
651
  }
563
652
  export function _resetSandboxRuntimeCache() {
653
+ sessionSandboxInitializationCircuits.clear();
564
654
  cachedBubblewrapPath = undefined;
565
655
  sandboxInitializationCircuit = undefined;
566
656
  sandboxCircuitNoticeAt = 0;
657
+ sandboxInitializationFailureCounters = emptySandboxInitializationFailureCounters();
567
658
  }
@@ -20,6 +20,21 @@ export function classifyToolErrorCode(input) {
20
20
  return 'USER_DENIED';
21
21
  return 'EXECUTION_FAILED';
22
22
  }
23
+ /**
24
+ * Map the internal error taxonomy to the intentionally small AUN activity
25
+ * contract. The detailed code remains available to local audit consumers.
26
+ */
27
+ export function classifyAunToolErrorCode(input) {
28
+ const code = classifyToolErrorCode(input);
29
+ if (code === 'POLICY_DENIED'
30
+ || code === 'ROLE_DENIED'
31
+ || code === 'USER_DENIED'
32
+ || code === 'APPROVAL_TIMEOUT'
33
+ || code === 'DELEGATION_FAILED') {
34
+ return 'PERMISSION_DENIED';
35
+ }
36
+ return 'EXECUTION_FAILED';
37
+ }
23
38
  /**
24
39
  * Compatibility classifier for historical, unstructured logs only.
25
40
  * Production event producers must use classifyToolErrorCode() and explicit
@@ -5,7 +5,7 @@ import { randomUUID } from 'crypto';
5
5
  import { logger } from '../../utils/logger.js';
6
6
  import { resolveRoot } from '../../paths.js';
7
7
  import { containsHClassReference, containsLClassReference, checkProtectedPathAccess, getExistingHClassMaskTargets, hClassGrantIncludesProtectedPath, isHClassPath, isSameOrDescendant, isLClassPath, lClassGrantIncludesProtectedPath, resolveProtectedCandidate, resolveProtectedCandidateWithoutFinalSymlink, } from './protected-paths.js';
8
- import { classifyEvolcoreShellCommand, hasCodexCmdCarrierEcIntent, hasPowerShellDelegationAssignment, parseCodexToolCommand, parseBoundedOutputShellCommand, parseLiteralShellArgv, resolveCodexShellCarrierArgv, resolveCodexShellCarrierString, } from './ec-command-parser.js';
8
+ import { classifyEvolcoreShellCommand, hasCodexCmdCarrierEcIntent, hasPowerShellCredentialAssignment, parseCodexToolCommand, parseBoundedOutputShellCommand, parseLiteralShellArgv, resolveCodexShellCarrierArgv, resolveCodexShellCarrierString, } from './ec-command-parser.js';
9
9
  import { analyzeReadonlyShellQuery, } from './readonly-shell-query.js';
10
10
  /** Resolve the session-owned temporary root supplied by the execution host. */
11
11
  export function resolveManagedTempDir(env = process.env) {
@@ -2209,8 +2209,9 @@ export function evaluateToolPreflight(toolName, input, context) {
2209
2209
  }
2210
2210
  const parserOptions = {
2211
2211
  verifyShellExecutable: process.platform === 'win32',
2212
- ...(context.delegationCarrier
2213
- ? { expectedDelegationToken: context.delegationCarrier }
2212
+ managedTempDir: context.managedTempDir,
2213
+ ...(context.sessionCredential
2214
+ ? { expectedCredentialToken: context.sessionCredential }
2214
2215
  : {}),
2215
2216
  };
2216
2217
  const parsedCodex = context.sessionId ? parseCodexToolCommand(input, parserOptions) : null;
@@ -2222,12 +2223,12 @@ export function evaluateToolPreflight(toolName, input, context) {
2222
2223
  policyCode: 'ec_shell_untrusted_executable',
2223
2224
  };
2224
2225
  }
2225
- if (context.sessionId && parsedCodex?.ok && parsedCodex.delegationToken
2226
- && !context.delegationCarrier) {
2226
+ if (context.sessionId && parsedCodex?.ok && parsedCodex.credentialToken
2227
+ && !context.sessionCredential) {
2227
2228
  return {
2228
2229
  behavior: 'deny',
2229
2230
  input,
2230
- message: '🔒 PowerShell 命令中的 EVOLCORE_DELEGATION_TOKEN 必须与当前托管 thread carrier 绑定;无法确认时拒绝执行。',
2231
+ message: '🔒 PowerShell 命令中的会话凭据必须与当前托管 Codex thread 绑定;无法确认时拒绝执行。',
2231
2232
  policyCode: 'delegation_token_unbound',
2232
2233
  };
2233
2234
  }
@@ -2292,7 +2293,7 @@ export function evaluateToolPreflight(toolName, input, context) {
2292
2293
  const shellDialect = stringShellCarrier?.dialect
2293
2294
  ?? (hasCodexCmdCarrierEcIntent(input) ? 'cmd' : undefined);
2294
2295
  const hasPowerShellCarrierAssignment = stringShellCarrier?.dialect === 'powershell'
2295
- && hasPowerShellDelegationAssignment(stringShellCarrier.command);
2296
+ && hasPowerShellCredentialAssignment(stringShellCarrier.command);
2296
2297
  const ecCommand = hasPowerShellCarrierAssignment && parsedCodex
2297
2298
  ? parsedCodex.ok && parsedCodex.argv[0] === 'ec'
2298
2299
  ? { kind: 'literal' }
@@ -2320,7 +2321,7 @@ export function evaluateToolPreflight(toolName, input, context) {
2320
2321
  }
2321
2322
  if (ecCommand.issue === 'unsafe-expansion') {
2322
2323
  const message = shellDialect === 'powershell'
2323
- ? '🔒 EC 双引号正文包含未转义的 PowerShell 展开;纯文字请优先使用单引号,或用 PowerShell 反引号转义 $ 和正文内的双引号。PowerShell 传给 ec 时会还原为原文字面量'
2324
+ ? '🔒 EC 命令包含无法安全解析的 PowerShell 展开;临时路径仅支持已登记的当前会话 $env:TMPDIR 或 ${env:TMPDIR},也可使用完整路径。纯文字请使用单引号,或用 PowerShell 反引号转义 $ 和正文内的双引号。'
2324
2325
  : shellDialect === 'cmd'
2325
2326
  ? '🔒 EC 命令包含未转义的 cmd.exe 展开;请不要使用 %VAR%、!VAR! 或 ^ 转义。cmd.exe 传给 ec 时必须保持原文字面量'
2326
2327
  : '🔒 EC 双引号正文包含未转义的 Shell 展开;请将反引号写成 \\`,将 $()、${}、$变量中的 $ 写成 \\$。Shell 传给 ec 时会还原为原文字面量';
@@ -37,6 +37,10 @@ export function parsePeerKey(key) {
37
37
  export class PeerIdentityCache {
38
38
  /** 缓存最大时效:30 天 */
39
39
  static CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
40
+ static invalidationRevisions = new Map();
41
+ static nextInvalidationRevision = 0;
42
+ /** Missing agent.md is recoverable; do not retain this negative result for 30 days. */
43
+ static UNKNOWN_CACHE_MAX_AGE_MS = 5 * 60 * 1000;
40
44
  /**
41
45
  * 获取 peer-identity.json 文件路径
42
46
  */
@@ -58,6 +62,22 @@ export class PeerIdentityCache {
58
62
  return null;
59
63
  }
60
64
  }
65
+ /** Expire only the freshness check; retain the last snapshot and its trust level. */
66
+ static invalidate(channelType, peerId, agentDir) {
67
+ const cached = this.get(channelType, peerId, agentDir);
68
+ if (!cached)
69
+ return;
70
+ const filePath = this.getFilePath(channelType, peerId, agentDir);
71
+ this.invalidationRevisions.set(filePath, ++this.nextInvalidationRevision);
72
+ if (cached.lastCheckedAt === 0)
73
+ return;
74
+ try {
75
+ fs.writeFileSync(filePath, JSON.stringify({ ...cached, lastCheckedAt: 0 }, null, 2), 'utf-8');
76
+ }
77
+ catch (error) {
78
+ logger.warn(`[PeerIdentityCache] Failed to invalidate ${channelType}#${peerId}: ${error}`);
79
+ }
80
+ }
61
81
  /**
62
82
  * 检查缓存是否需要刷新
63
83
  * @param maxAgeMs 最大缓存时间(默认 30 天)
@@ -67,7 +87,10 @@ export class PeerIdentityCache {
67
87
  const cached = this.get(channelType, peerId, agentDir);
68
88
  if (!cached)
69
89
  return true;
70
- return Date.now() - cached.lastCheckedAt > maxAgeMs;
90
+ const effectiveMaxAge = cached.source === 'unknown'
91
+ ? Math.min(maxAgeMs, this.UNKNOWN_CACHE_MAX_AGE_MS)
92
+ : maxAgeMs;
93
+ return cached.lastCheckedAt === 0 || Date.now() - cached.lastCheckedAt > effectiveMaxAge;
71
94
  }
72
95
  /**
73
96
  * 从 agent.md 更新身份信息
@@ -95,7 +118,7 @@ export class PeerIdentityCache {
95
118
  };
96
119
  const filePath = this.getFilePath(channelType, peerId, agentDir);
97
120
  try {
98
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
121
+ fs.mkdirSync(path.join(path.dirname(filePath), 'shared'), { recursive: true });
99
122
  fs.writeFileSync(filePath, JSON.stringify(identity, null, 2), 'utf-8');
100
123
  logger.debug(`[PeerIdentityCache] Updated: ${channelType}#${peerId} type=${type} isAgent=${isAgent} source=${source}`);
101
124
  }
@@ -132,7 +155,7 @@ export class PeerIdentityCache {
132
155
  };
133
156
  const filePath = this.getFilePath(channelType, peerId, agentDir);
134
157
  try {
135
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
158
+ fs.mkdirSync(path.join(path.dirname(filePath), 'shared'), { recursive: true });
136
159
  fs.writeFileSync(filePath, JSON.stringify(identity, null, 2), 'utf-8');
137
160
  logger.debug(`[PeerIdentityCache] Marked unknown: ${channelType}#${peerId}`);
138
161
  }
@@ -149,8 +172,13 @@ export class PeerIdentityCache {
149
172
  * @param agentDir agent 数据根目录
150
173
  * @param store AIDStore 实例(由调用方提供,负责 checkAgentMd + downloadAgentMd)
151
174
  * @param forceRefresh 强制刷新(忽略缓存时效)
175
+ * @param aunPath AUN SDK 数据根目录;与关系缓存所在的 agentDir 分开
152
176
  */
153
- static async resolve(channelType, peerId, agentDir, store, forceRefresh = false) {
177
+ static async resolve(channelType, peerId, agentDir, store, forceRefresh = false, aunPath) {
178
+ // AIDStore is authoritative for the SDK cache root. Callers that predate
179
+ // the explicit aunPath parameter must still fall back to that same root,
180
+ // otherwise the error path could read a different agent.md tree.
181
+ const resolvedAunPath = aunPath ?? store.aunPath;
154
182
  // A malformed transport envelope must never turn into a relation key such
155
183
  // as `aun#` or trigger an agent.md lookup for an empty AID. Keep the
156
184
  // fallback in-memory so callers can safely render an unknown peer without
@@ -177,15 +205,36 @@ export class PeerIdentityCache {
177
205
  return cached;
178
206
  }
179
207
  }
208
+ const cachePath = this.getFilePath(channelType, peerId, agentDir);
209
+ const revision = this.invalidationRevisions.get(cachePath) ?? 0;
210
+ const stale = this.get(channelType, peerId, agentDir);
180
211
  // 2. 通过 agentmdSync 拉取(内部走 store.checkAgentMd → store.downloadAgentMd)
181
212
  try {
182
213
  logger.debug(`[PeerIdentityCache] Syncing agent.md: ${channelType}#${peerId}`);
183
214
  const { agentmdSync } = await import('../../aun/aid/agentmd.js');
184
- const result = await agentmdSync(peerId, { store });
215
+ const invalidated = stale?.lastCheckedAt === 0 || revision > 0;
216
+ const result = await agentmdSync(peerId, {
217
+ store,
218
+ aunPath: resolvedAunPath,
219
+ forceRefresh: invalidated,
220
+ ttlDays: stale?.source === 'unknown' ? 0 : undefined,
221
+ });
222
+ if ((this.invalidationRevisions.get(cachePath) ?? 0) !== revision) {
223
+ // A newer event arrived during this read. Its next lookup must refresh.
224
+ return this.get(channelType, peerId, agentDir) ?? {
225
+ aid: peerId, type: 'unknown', isAgent: true, agentMdHash: '', agentMdUpdatedAt: 0,
226
+ verifiedAt: 0, lastCheckedAt: 0, source: 'unknown',
227
+ };
228
+ }
229
+ // A failed refresh may return old local bytes. Keep the invalidated
230
+ // snapshot so the next lookup retries instead of caching it for 30 days.
231
+ if (invalidated && !result.verification && stale)
232
+ return stale;
185
233
  const content = result.content;
186
234
  if (!content) {
187
235
  throw new Error('agent.md content unavailable');
188
236
  }
237
+ this.invalidationRevisions.delete(cachePath);
189
238
  // 验签通过 → 可信(source=agentmd);否则 type 仍解析但标记未验证
190
239
  const verified = result.verification?.status === 'verified';
191
240
  const source = verified ? 'agentmd' : 'agentmd-unverified';
@@ -201,10 +250,12 @@ export class PeerIdentityCache {
201
250
  return this.updateFromAgentMd(channelType, peerId, agentDir, content, verified ? Date.now() : 0, source);
202
251
  }
203
252
  catch (err) {
253
+ if (stale?.lastCheckedAt === 0)
254
+ return stale;
204
255
  // 4. agentmdSync 抛错(非网络失败——网络失败时它内部已 fallback 返回本地内容;
205
256
  // 这里通常是无内容或解析异常)。兜底读本地文件,但无法重新验签,
206
257
  // 故沿用缓存里已有的可信级别,绝不凭空升级为已验签。
207
- const localPath = agentMdPath(peerId);
258
+ const localPath = agentMdPath(peerId, resolvedAunPath);
208
259
  try {
209
260
  if (fs.existsSync(localPath)) {
210
261
  const localContent = fs.readFileSync(localPath, 'utf-8');
@@ -0,0 +1,106 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+ import { agentRelationDir, agentRelationSharedDir, agentRelationsDir, resolvePaths } from '../../paths.js';
5
+ import { parsePeerKey } from './peer-identity.js';
6
+ import { logger } from '../../utils/logger.js';
7
+ export const RELATION_SHARED_FILE_ALLOWLIST = Object.freeze(['rules.md']);
8
+ function isAllowedSharedFile(relativePath) {
9
+ return RELATION_SHARED_FILE_ALLOWLIST.includes(relativePath);
10
+ }
11
+ function copyFileAtomically(source, target) {
12
+ const targetDir = path.dirname(target);
13
+ fs.mkdirSync(targetDir, { recursive: true });
14
+ const temporary = path.join(targetDir, `.${path.basename(target)}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`);
15
+ try {
16
+ fs.copyFileSync(source, temporary);
17
+ const sourceStat = fs.statSync(source);
18
+ fs.utimesSync(temporary, sourceStat.atime, sourceStat.mtime);
19
+ fs.renameSync(temporary, target);
20
+ }
21
+ finally {
22
+ try {
23
+ fs.rmSync(temporary, { force: true });
24
+ }
25
+ catch { }
26
+ }
27
+ }
28
+ export function ensureRelationSharedDirectory(selfAid, peerKey) {
29
+ parsePeerKey(peerKey);
30
+ const sharedDir = agentRelationSharedDir(selfAid, peerKey);
31
+ fs.mkdirSync(sharedDir, { recursive: true });
32
+ return sharedDir;
33
+ }
34
+ export function syncRelationSharedFile(selfAid, peerKey, relativePath) {
35
+ if (!isAllowedSharedFile(relativePath)) {
36
+ throw Object.assign(new Error(`Relation shared file is not allowlisted: ${relativePath}`), {
37
+ code: 'SHARED_FILE_NOT_ALLOWED',
38
+ });
39
+ }
40
+ parsePeerKey(peerKey);
41
+ const relationDir = agentRelationDir(selfAid, peerKey);
42
+ const source = path.join(relationDir, relativePath);
43
+ const target = path.join(ensureRelationSharedDirectory(selfAid, peerKey), relativePath);
44
+ let sourceStat;
45
+ try {
46
+ sourceStat = fs.statSync(source);
47
+ }
48
+ catch {
49
+ return { copied: false, target };
50
+ }
51
+ if (!sourceStat.isFile())
52
+ return { copied: false, target };
53
+ try {
54
+ const targetStat = fs.statSync(target);
55
+ if (targetStat.isFile()
56
+ && targetStat.size === sourceStat.size
57
+ && Math.abs(targetStat.mtimeMs - sourceStat.mtimeMs) < 1) {
58
+ return { copied: false, target };
59
+ }
60
+ }
61
+ catch { }
62
+ copyFileAtomically(source, target);
63
+ return { copied: true, target };
64
+ }
65
+ export function migrateRelationSharedDirectories() {
66
+ const agentsDir = resolvePaths().agentsDir;
67
+ let relations = 0;
68
+ let copied = 0;
69
+ if (!fs.existsSync(agentsDir))
70
+ return { relations, copied };
71
+ for (const agentEntry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
72
+ if (!agentEntry.isDirectory())
73
+ continue;
74
+ const selfAid = agentEntry.name;
75
+ const relationsDir = agentRelationsDir(selfAid);
76
+ let relationEntries;
77
+ try {
78
+ relationEntries = fs.readdirSync(relationsDir, { withFileTypes: true });
79
+ }
80
+ catch {
81
+ continue;
82
+ }
83
+ for (const relationEntry of relationEntries) {
84
+ if (!relationEntry.isDirectory() || relationEntry.name.startsWith('_'))
85
+ continue;
86
+ try {
87
+ parsePeerKey(relationEntry.name);
88
+ }
89
+ catch {
90
+ continue;
91
+ }
92
+ relations += 1;
93
+ try {
94
+ ensureRelationSharedDirectory(selfAid, relationEntry.name);
95
+ for (const file of RELATION_SHARED_FILE_ALLOWLIST) {
96
+ if (syncRelationSharedFile(selfAid, relationEntry.name, file).copied)
97
+ copied += 1;
98
+ }
99
+ }
100
+ catch (error) {
101
+ logger.warn(`[RelationShared] failed self=${selfAid} peer=${relationEntry.name}: ${error instanceof Error ? error.message : String(error)}`);
102
+ }
103
+ }
104
+ }
105
+ return { relations, copied };
106
+ }
@@ -1,5 +1,9 @@
1
1
  import { getRoleDefinition } from '../../config/roles.js';
2
2
  import { PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
3
+ import { ConfigTarget, read } from '../../config/config-manager.js';
4
+ import { resolvePrincipal } from '../../config/contact-book.js';
5
+ import { isFullAccessEnabled, loadDaemonConfig } from '../../config-store.js';
6
+ import { hasTrustedPrincipal } from '../auth/authenticated-actor.js';
3
7
  const PERMISSION_MODE_AUTHORITY = {
4
8
  readonly: 0,
5
9
  auto: 1,
@@ -139,3 +143,63 @@ export function validateRuntimeStringFieldOverride(input) {
139
143
  }
140
144
  return { ok: true };
141
145
  }
146
+ /**
147
+ * Resolve the Agent-level host execution policy for one authenticated principal.
148
+ * The Agent flag is subordinate to daemon fullaccess and independent of role permissionMode values.
149
+ */
150
+ export function agentFullAccessStatus(agentAid, principalId) {
151
+ const config = read(ConfigTarget.Agent, { self: agentAid }, { cache: false });
152
+ const daemon = loadDaemonConfig();
153
+ const enabled = config?.fullaccess?.enabled === true;
154
+ const hostEnabled = isFullAccessEnabled(daemon);
155
+ const eligible = !!principalId
156
+ && !!config?.owners?.includes(principalId)
157
+ && !!daemon.owners?.includes(principalId);
158
+ return { enabled, hostEnabled, eligible, effective: enabled && hostEnabled && eligible };
159
+ }
160
+ /** Create a trusted per-call claim for a direct private message. */
161
+ export function createAgentPolicyAuthorization(input) {
162
+ if (input.chatType !== 'private'
163
+ || (input.source !== undefined && input.source !== 'user')
164
+ || ['agent', 'ai', 'bot', 'service', 'group'].includes((input.peerType ?? '').toLowerCase())
165
+ || !hasTrustedPrincipal(input.actor))
166
+ return undefined;
167
+ if (!agentFullAccessStatus(input.agentAid, input.actor.principalId).effective)
168
+ return undefined;
169
+ return {
170
+ permissionMode: 'fullaccess',
171
+ processRole: 'fullaccess-run',
172
+ dataScope: 'daemon',
173
+ source: 'agent-policy',
174
+ agentAid: input.agentAid,
175
+ authorizedBy: input.actor.principalId,
176
+ policyActor: { ...input.actor },
177
+ };
178
+ }
179
+ /** Revalidate a claim before Runner calls, retries, and daemon IPC. */
180
+ export function assertAgentPolicyAuthorization(authorization) {
181
+ const actor = authorization.policyActor;
182
+ if (!authorization.agentAid || !hasTrustedPrincipal(actor)) {
183
+ throw new Error('Agent fullaccess policy lacks authenticated origin');
184
+ }
185
+ const current = resolvePrincipal(authorization.agentAid, actor.operatorChannelType, actor.operatorId, actor.operatorChannelKey);
186
+ if (!hasTrustedPrincipal({ ...actor, ...current }) || current.principalId !== authorization.authorizedBy) {
187
+ throw new Error('Agent fullaccess policy origin identity changed');
188
+ }
189
+ if (!agentFullAccessStatus(authorization.agentAid, authorization.authorizedBy).effective) {
190
+ throw new Error('Agent fullaccess policy disabled or current owner is no longer eligible');
191
+ }
192
+ }
193
+ /** Only the authenticated EC Web Agent settings may mutate this policy. */
194
+ export function agentFullAccessWriteError(input) {
195
+ if (!input.ecwebManagement) {
196
+ return 'Agent fullaccess can only be configured from ECWeb';
197
+ }
198
+ if (!input.daemonOwner || input.delegated) {
199
+ return 'Only authenticated ECWeb management may configure Agent fullaccess';
200
+ }
201
+ if (input.enabled && !isFullAccessEnabled()) {
202
+ return 'Host fullaccess is disabled';
203
+ }
204
+ return undefined;
205
+ }
@@ -17,28 +17,21 @@ export function resolveManagedLockPath(namespace, identity, legacyPath) {
17
17
  }
18
18
  function isTrustedRuntimeLockDir(candidate) {
19
19
  const resolved = path.resolve(candidate);
20
- if (path.basename(resolved) !== 'evolcore-locks')
21
- return false;
22
20
  const tmpRoot = process.env.TMPDIR?.trim();
23
21
  if (!tmpRoot || !path.isAbsolute(tmpRoot))
24
22
  return false;
25
23
  const resolvedTmpRoot = path.resolve(tmpRoot);
26
- const relative = path.relative(resolvedTmpRoot, resolved);
27
- const isWithinTmp = relative !== '..'
28
- && !relative.startsWith(`..${path.sep}`)
29
- && !path.isAbsolute(relative);
30
- // The daemon supplies each task either a generic evolcore-runtime-* child
31
- // or Codex's private digest directory. Both share a sibling evolcore-locks
32
- // directory so mutations from separate sessions still serialize. Do not
33
- // accept arbitrary TMPDIR siblings supplied by a child process.
34
24
  const tmpBase = path.basename(resolvedTmpRoot);
35
- const tmpParent = path.dirname(resolvedTmpRoot);
36
- const isGenericSessionRuntime = /^evolcore-runtime-[a-f0-9]{24}$/.test(tmpBase);
37
- const isCodexSessionRuntime = /^[a-f0-9]{24}$/.test(tmpBase)
38
- && /^evolcore-codex-(?:\d+|user)$/.test(path.basename(tmpParent));
39
- const isManagedSessionSibling = (isGenericSessionRuntime || isCodexSessionRuntime)
40
- && path.dirname(resolved) === tmpParent;
41
- if (!isWithinTmp && !isManagedSessionSibling)
25
+ const isSessionLockDir = /^ec-session-[a-f0-9]{24}$/.test(tmpBase)
26
+ && path.basename(resolved) === 'locks'
27
+ && path.dirname(resolved) === resolvedTmpRoot;
28
+ // The app-server process has no EvolCore session yet and retains its own
29
+ // process-lifetime lock namespace. Both process and session roots use the
30
+ // same `$TMPDIR/locks` layout.
31
+ const isProcessLockDir = /^evolcore-managed-tmp-/.test(tmpBase)
32
+ && path.basename(resolved) === 'locks'
33
+ && path.dirname(resolved) === resolvedTmpRoot;
34
+ if (!isSessionLockDir && !isProcessLockDir)
42
35
  return false;
43
36
  try {
44
37
  // Windows Stats.mode does not describe ACL privacy. The managed runtime
@@ -53,15 +46,6 @@ function isTrustedRuntimeLockDir(candidate) {
53
46
  return false;
54
47
  if (!isWindows && (tmpStat.mode & 0o077) !== 0)
55
48
  return false;
56
- if (isManagedSessionSibling) {
57
- const parentStat = fs.lstatSync(tmpParent);
58
- if (!parentStat.isDirectory() || parentStat.isSymbolicLink())
59
- return false;
60
- if (!isWindows && typeof process.getuid === 'function' && parentStat.uid !== process.getuid())
61
- return false;
62
- if (!isWindows && (parentStat.mode & 0o077) !== 0)
63
- return false;
64
- }
65
49
  const stat = fs.lstatSync(resolved);
66
50
  if (!stat.isDirectory() || stat.isSymbolicLink())
67
51
  return false;