evolcore 0.0.28 → 0.0.29

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 (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -1,68 +1,55 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { resolveRoot } from '../../paths.js';
4
+ import { isValidAid } from '../../aun/aid/identity.js';
4
5
  const H_CLASS_RELATIVE_PATTERNS = [
5
- /^daemon\.json$/,
6
- /^agents\/defaults\.json$/,
7
- /^agents\/[^/]+\/config\.json$/,
8
- /^agents\/[^/]+\/contact\.json$/,
9
- /^agents\/[^/]+\/contact-operations\.jsonl$/,
10
- /^agents\/[^/]+\/relations\/[^/]+\/config\.json$/,
11
- /^backups\/config(?:\/|$)/,
6
+ // These are true secret or daemon-owned roots. Agent-owned content is not
7
+ // H-class merely because it is persisted under the EvolCore home.
8
+ /^data(?:\/|$)/,
9
+ /^(?:AIDs|aids)(?:\/|$)/,
12
10
  /^\.snapshots(?:\/|$)/,
13
11
  /^CA(?:\/|$)/,
14
- /^(?:AIDs|aids)\/[^/]+\/(?:cert|keys|private)(?:\/|$)/,
15
12
  /^\.device_id$/,
16
13
  /^\.env$/,
17
14
  /^\.seed(?:\.|$)/,
18
15
  /^\.migrated-/,
19
- /(?:^|\/)[^/]+\.json_$/,
20
- /(?:^|\/)[^/]+\.json\.migrated$/,
21
- /^agents\/defaults_\d+\.json$/,
22
16
  /^\.lock$/,
23
17
  /^daemon\.pid$/,
24
- /^data\/causation-aun\.json(?:_|__)?$/,
25
- /^data\/triggers(?:\/|$)/,
26
- /^data\/(?:sessions(?:\/|$)|contact-book-audit\.jsonl$|feishu-seen-.*\.jsonl$|[a-z0-9_-]+-greeted-.*\.jsonl$|message-queue\.json(?:_|__|\.tmp-.*)?$|daemon\/(?:message-queue\.json(?:_|__|\.tmp-.*)?$|control(?:\/|$)|migrations(?:\/|$))|channel-state(?:\/|$))/,
27
- /^data\/instance\/control\.token$/,
28
- /^data\/outbox(?:\/|$)/,
29
- /^data\/handoff(?:\/|$)/,
30
- /^agents\/[^/]+\/sessions(?:\/|$)/,
31
- /^agents\/[^/]+\/data\/async-questions(?:\/|$)/,
32
- /^agents\/[^/]+\/data\/aun(?:\/|$)/,
33
- /^agents\/[^/]+\/data\/(?:channels(?:\/|$)|handoff(?:\/|$)|(?:outbox|activity-outbox)\.jsonl(?:_|__|\.tmp-.*)?$|contact-audit\.jsonl$)/,
18
+ /^agents\/[^/]+\/data\/codex-tokens(?:\/|$)/,
19
+ /^agents\/[^/]+\/data\/(?:.*\/)?(?:context-tokens|menu-tokens)\.json(?:_|__|\.tmp-.*)?$/,
34
20
  ];
35
21
  const H_CLASS_REFERENCE_PATTERNS = [
36
- /(?:^|[/\\\s"'=<>])agents[\\/][^/\\\s"']+[\\/]data[\\/]async-questions(?:[\\/]|$|[\s"';&|<>)},])/,
37
- /(?:^|[/\\\s"'=<>])agents[\\/][^/\\\s"']+[\\/]data[\\/]aun(?:[\\/]|$|[\s"';&|<>)},])/,
38
- /(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
39
- /(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
40
- /(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys|private))(?:[\\/]|$|[\s"';&|<>)},])/,
41
- /(?:^|[/\\\s"'=<>])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid|data[\\/](?:causation-aun\.json(?:_|__)?|sessions|contact-book-audit\.jsonl|feishu-seen-[^/\\\s"']+\.jsonl|[a-z0-9_-]+-greeted-[^/\\\s"']+\.jsonl|message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|daemon[\\/](?:message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|control|migrations)|channel-state|instance[\\/]control\.token|outbox|handoff)|agents[\\/][^/\\\s"']+[\\/](?:sessions|data[\\/](?:channels|handoff|(?:outbox|activity-outbox)\.jsonl|contact-audit\.jsonl)))(?:[\\/]|$|[\s"';&|<>)},])/,
42
- /(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
43
- /(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
22
+ /(?:^|[/\\\s"'=<>])data[\\/]/,
23
+ /(?:^|[/\\\s"'=<>])(?:AIDs|aids)[\\/]/,
24
+ /(?:^|[^A-Za-z0-9_.-])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid)(?=$|[\s"';&|<>)},])/,
25
+ /(?:^|[/\\\s"'=<>])agents[\\/][^/\\\s"']+[\\/]data[\\/](?:codex-tokens|(?:.*[\\/])?(?:context-tokens|menu-tokens)\.json)(?:[\\/]|$|[\s"';&|<>)},])/,
44
26
  ];
45
27
  // H-class directory roots are kept separately from the full path matcher so
46
28
  // an exact metadata query can distinguish a protected directory from a
47
29
  // protected file. Descendants inherit the directory classification unless an
48
30
  // existing regular file gives us a more precise answer.
49
31
  const H_CLASS_DIRECTORY_RELATIVE_PATTERNS = [
50
- /^agents\/[^/]+\/data\/aun(?:\/|$)/,
51
- /^agents\/[^/]+\/data\/async-questions(?:\/|$)/,
52
- /^backups\/config(?:\/|$)/,
32
+ /^data(?:\/|$)/,
33
+ /^(?:AIDs|aids)(?:\/|$)/,
53
34
  /^\.snapshots(?:\/|$)/,
54
35
  /^CA(?:\/|$)/,
55
- /^(?:AIDs|aids)\/[^/]+\/(?:cert|keys|private)(?:\/|$)/,
56
- /^data\/(?:triggers|sessions|outbox|handoff|channel-state)(?:\/|$)/,
57
- /^data\/daemon\/(?:control|migrations)(?:\/|$)/,
58
- /^agents\/[^/]+\/sessions(?:\/|$)/,
59
- /^agents\/[^/]+\/data\/(?:channels|handoff)(?:\/|$)/,
36
+ /^agents\/[^/]+\/data\/codex-tokens(?:\/|$)/,
60
37
  ];
61
38
  const L_CLASS_RELATIVE_PATTERNS = [
39
+ /^daemon\.json$/,
40
+ /^agents\/defaults\.json$/,
41
+ /^agents\/[^/]+\/config\.json$/,
42
+ /^agents\/[^/]+\/contact\.json$/,
43
+ /^agents\/[^/]+\/contact-operations\.jsonl$/,
44
+ /^agents\/[^/]+\/relations\/[^/]+\/config\.json$/,
45
+ /^backups\/config(?:\/|$)/,
62
46
  /^agents\/[^/]+\/triggers(?:\/|$)/,
63
47
  /^agents\/[^/]+\/roles(?:\/|$)/,
64
48
  ];
65
49
  const L_CLASS_REFERENCE_PATTERNS = [
50
+ /(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
51
+ /(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
52
+ /(?:^|[^A-Za-z0-9_.-])backups[\\/]config(?:[\\/]|$|[\s"';&|<>)},])/,
66
53
  /(?:^|[^A-Za-z0-9_.-])agents[\\/][^/\\\s"']+[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
67
54
  /(?:^|[^A-Za-z0-9_.-])agents[\\/][^/\\\s"']+[\\/]roles(?:[\\/]|$|[\s"';&|<>)},])/,
68
55
  ];
@@ -88,6 +75,60 @@ function resolveThroughExistingAncestor(value) {
88
75
  return absolute;
89
76
  }
90
77
  }
78
+ function hasExplicitSelfAid(options) {
79
+ // Several legacy helper callers construct an options object with the
80
+ // optional property spread from an absent runtime binding, which produces
81
+ // `selfAid: undefined`. Treat that the same as an omitted property so the
82
+ // compatibility projection remains available. An explicitly supplied
83
+ // empty/invalid string is still fail-closed below.
84
+ return !!options
85
+ && Object.prototype.hasOwnProperty.call(options, 'selfAid')
86
+ && options.selfAid !== undefined;
87
+ }
88
+ function normalizeAid(value) {
89
+ if (typeof value !== 'string')
90
+ return undefined;
91
+ const normalized = value.trim();
92
+ return normalized && isValidAid(normalized) ? normalized : undefined;
93
+ }
94
+ function assertValidSelfAid(options, operation) {
95
+ if (hasExplicitSelfAid(options) && !normalizeAid(options?.selfAid)) {
96
+ throw new Error(`[Permission] ${operation} requires a valid selfAid`);
97
+ }
98
+ }
99
+ function hasInvalidExplicitSelfAid(options) {
100
+ return hasExplicitSelfAid(options) && !normalizeAid(options?.selfAid);
101
+ }
102
+ function agentScopeRelative(relative, selfAid) {
103
+ const parts = relative.split('/').filter(Boolean);
104
+ if (parts[0] !== 'agents' || !parts[1])
105
+ return false;
106
+ const own = normalizeAid(selfAid);
107
+ if (/^defaults(?:_\d+)?\.json$/i.test(parts[1]))
108
+ return false;
109
+ // With no identity context, the legacy H-class projection is used instead
110
+ // of guessing which Agent is self.
111
+ return !!own && parts[1].toLowerCase() !== own.toLowerCase();
112
+ }
113
+ function matchesAgentScopeRelative(relative, selfAid) {
114
+ return agentScopeRelative(relative, selfAid);
115
+ }
116
+ function agentScopeCandidate(value, options = {}) {
117
+ if (!normalizeAid(options.selfAid))
118
+ return undefined;
119
+ const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
120
+ const lexical = lexicalCandidate(value, options.cwd);
121
+ if (isSameOrDescendant(lexical, root)) {
122
+ const relative = path.relative(root, lexical).split(path.sep).join('/');
123
+ if (matchesAgentScopeRelative(relative, options.selfAid))
124
+ return lexical;
125
+ }
126
+ const candidate = resolveProtectedCandidate(value, options.cwd);
127
+ if (!isSameOrDescendant(candidate, root))
128
+ return undefined;
129
+ const relative = path.relative(root, candidate).split(path.sep).join('/');
130
+ return matchesAgentScopeRelative(relative, options.selfAid) ? candidate : undefined;
131
+ }
91
132
  /**
92
133
  * H-class file names are normally leaves, but treating their descendants as
93
134
  * protected as well is important when a malformed or externally-created
@@ -104,6 +145,28 @@ function matchesHClassRelative(relative) {
104
145
  }
105
146
  return false;
106
147
  }
148
+ const SELF_AWARE_H_CLASS_RELATIVE_PATTERNS = [
149
+ /^data(?:\/|$)/,
150
+ /^(?:AIDs|aids)(?:\/|$)/,
151
+ /^CA(?:\/|$)/,
152
+ /^\.device_id$/,
153
+ /^\.env$/,
154
+ /^\.seed(?:\.|$)/,
155
+ /^\.lock$/,
156
+ /^daemon\.pid$/,
157
+ ];
158
+ function matchesSelfAwareHClassRelative(relative, selfAid) {
159
+ if (matchesAgentScopeRelative(relative, selfAid))
160
+ return true;
161
+ if (SELF_AWARE_H_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(relative)))
162
+ return true;
163
+ const own = normalizeAid(selfAid);
164
+ if (!own)
165
+ return false;
166
+ const escaped = own.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
167
+ return new RegExp(`^agents/${escaped}/data/codex-tokens(?:/|$)`).test(relative)
168
+ || new RegExp(`^agents/${escaped}/data/(?:.*\/)?(?:context-tokens|menu-tokens)\\.json(?:_|__|\\.tmp-.*)?$`).test(relative);
169
+ }
107
170
  export function isSameOrDescendant(candidate, parent) {
108
171
  const normalizedCandidate = normalizeForComparison(candidate);
109
172
  const normalizedParent = normalizeForComparison(parent);
@@ -119,16 +182,22 @@ export function isHClassPath(value, options = {}) {
119
182
  const lexicalCandidate = path.isAbsolute(value)
120
183
  ? path.resolve(value)
121
184
  : path.resolve(options.cwd ?? process.cwd(), value);
185
+ if (hasInvalidExplicitSelfAid(options))
186
+ return isSameOrDescendant(lexicalCandidate, root);
187
+ if (agentScopeCandidate(value, options))
188
+ return true;
122
189
  if (isSameOrDescendant(lexicalCandidate, root)) {
123
190
  const lexicalRelative = path.relative(root, lexicalCandidate).split(path.sep).join('/');
124
- if (matchesHClassRelative(lexicalRelative))
191
+ if ((options.selfAid ? matchesSelfAwareHClassRelative(lexicalRelative, options.selfAid) : matchesHClassRelative(lexicalRelative)))
125
192
  return true;
126
193
  }
127
194
  const candidate = resolveProtectedCandidate(value, options.cwd);
128
195
  if (!isSameOrDescendant(candidate, root))
129
196
  return false;
130
197
  const relative = path.relative(root, candidate).split(path.sep).join('/');
131
- return matchesHClassRelative(relative);
198
+ return options.selfAid
199
+ ? matchesSelfAwareHClassRelative(relative, options.selfAid)
200
+ : matchesHClassRelative(relative);
132
201
  }
133
202
  function hasPathSyntax(value) {
134
203
  return /[\0\r\n$`*?\[\]{}<>|;&]/.test(value);
@@ -166,7 +235,11 @@ export function classifyProtectedPath(value, options = {}) {
166
235
  const relative = relativeWithinRoot(candidate, root);
167
236
  if (relative === undefined)
168
237
  return 'ordinary';
169
- const hMatch = matchesHClassRelative(relative);
238
+ if (matchesAgentScopeRelative(relative, options.selfAid))
239
+ return 'h-directory';
240
+ const hMatch = options.selfAid
241
+ ? matchesSelfAwareHClassRelative(relative, options.selfAid)
242
+ : matchesHClassRelative(relative);
170
243
  if (hMatch) {
171
244
  let directory = H_CLASS_DIRECTORY_RELATIVE_PATTERNS.some(pattern => pattern.test(relative));
172
245
  try {
@@ -205,29 +278,77 @@ export function checkProtectedPathAccess(value, accessKind, options = {}) {
205
278
  allowed: accessKind !== 'write' && accessKind !== 'grant',
206
279
  };
207
280
  }
208
- export function containsHClassReference(value) {
281
+ export function isAgentScopePath(value, options = {}) {
282
+ return !!agentScopeCandidate(value, options);
283
+ }
284
+ export function containsAgentScopeReference(value, options = {}) {
285
+ if (hasInvalidExplicitSelfAid(options))
286
+ return true;
287
+ if (!normalizeAid(options.selfAid))
288
+ return false;
289
+ const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
290
+ return extractLiteralPathCandidates(value).some(candidate => isAgentScopePath(candidate, { ...options, root }));
291
+ }
292
+ export function containsHClassReference(value, options = {}) {
293
+ if (hasInvalidExplicitSelfAid(options))
294
+ return true;
209
295
  const normalized = value.replace(/\\/g, '/');
210
296
  const unquoted = normalized.replace(/^["']|["']$/g, '').replace(/[;,) ]+$/, '');
297
+ // Resolve literal paths before applying textual rules. A self-Agent path
298
+ // such as agents/<self>/data/outbox/... contains the shared `data/` token,
299
+ // but it is ordinary Agent-owned data and must not inherit data/** H-class.
300
+ const literalCandidates = extractLiteralPathCandidates(value);
301
+ if (literalCandidates.length) {
302
+ const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
303
+ const inRoot = literalCandidates.filter(candidate => {
304
+ const resolved = resolveProtectedCandidate(candidate, options.cwd);
305
+ return isSameOrDescendant(resolved, root);
306
+ });
307
+ if (inRoot.length) {
308
+ return inRoot.some(candidate => isHClassPath(candidate, options) || isAgentScopePath(candidate, options));
309
+ }
310
+ }
211
311
  if (H_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(unquoted)))
212
312
  return true;
213
- return H_CLASS_REFERENCE_PATTERNS.some(pattern => pattern.test(normalized));
313
+ return H_CLASS_REFERENCE_PATTERNS.some(pattern => pattern.test(normalized))
314
+ || containsAgentScopeReference(value, options);
315
+ }
316
+ function extractLiteralPathCandidates(value) {
317
+ const normalized = value.replace(/\\/g, '/');
318
+ const candidates = new Set();
319
+ const absolute = normalized.match(/(?:[A-Za-z]:\/|\/)[^\s"'`;|&<>()[\]{}]+/g) ?? [];
320
+ for (const candidate of absolute)
321
+ candidates.add(candidate.replace(/[.,:]+$/, ''));
322
+ const relative = normalized.match(/(?:^|[\s"'=<>:(])((?:\.{0,2}\/)?(?:agents|data|AIDs|aids|backups|CA|\.snapshots)(?:\/[^\s"'`;|&<>()[\]{}]*)?)/gi) ?? [];
323
+ for (const candidate of relative)
324
+ candidates.add(candidate.replace(/^[\s"'=<>:(]+/, '').replace(/[.,:]+$/, ''));
325
+ for (const candidate of normalized.split(/[\s"'=<>:();|&]+/).filter(Boolean)) {
326
+ if (/^(?:daemon\.json|\.device_id|\.env|\.seed(?:\.[^/]+)?|\.lock|daemon\.pid)$/i.test(candidate)) {
327
+ candidates.add(candidate);
328
+ }
329
+ }
330
+ return [...candidates];
214
331
  }
215
332
  /** L-class paths are readable by agents but may only be mutated by trusted daemon paths. */
216
333
  export function isLClassPath(value, options = {}) {
334
+ if (hasInvalidExplicitSelfAid(options))
335
+ return false;
217
336
  const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
218
337
  const lexicalCandidate = path.isAbsolute(value)
219
338
  ? path.resolve(value)
220
339
  : path.resolve(options.cwd ?? process.cwd(), value);
221
340
  if (isSameOrDescendant(lexicalCandidate, root)) {
222
341
  const lexicalRelative = path.relative(root, lexicalCandidate).split(path.sep).join('/');
223
- if (L_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(lexicalRelative)))
342
+ if (!matchesAgentScopeRelative(lexicalRelative, options.selfAid)
343
+ && L_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(lexicalRelative)))
224
344
  return true;
225
345
  }
226
346
  const candidate = resolveProtectedCandidate(value, options.cwd);
227
347
  if (!isSameOrDescendant(candidate, root))
228
348
  return false;
229
349
  const relative = path.relative(root, candidate).split(path.sep).join('/');
230
- return L_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(relative));
350
+ return !matchesAgentScopeRelative(relative, options.selfAid)
351
+ && L_CLASS_RELATIVE_PATTERNS.some(pattern => pattern.test(relative));
231
352
  }
232
353
  export function containsLClassReference(value) {
233
354
  const normalized = value.replace(/\\/g, '/');
@@ -236,8 +357,7 @@ export function containsLClassReference(value) {
236
357
  return true;
237
358
  return L_CLASS_REFERENCE_PATTERNS.some(pattern => pattern.test(normalized));
238
359
  }
239
- export function getHClassSandboxPatterns(root = resolveRoot()) {
240
- const absoluteRoot = resolveThroughExistingAncestor(root);
360
+ function getLegacyHClassSandboxPatterns(root) {
241
361
  const entries = [
242
362
  'daemon.json',
243
363
  path.join('agents', 'defaults.json'),
@@ -245,59 +365,60 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
245
365
  path.join('agents', '*', 'contact.json'),
246
366
  path.join('agents', '*', 'contact-operations.jsonl'),
247
367
  path.join('agents', '*', 'relations', '*', 'config.json'),
248
- path.join('backups', 'config'),
249
- path.join('backups', 'config', '**'),
250
- '.snapshots',
251
- path.join('.snapshots', '**'),
368
+ path.join('backups', 'config'), path.join('backups', 'config', '**'),
369
+ '.snapshots', path.join('.snapshots', '**'),
370
+ 'CA', path.join('CA', '**'),
371
+ path.join('AIDs', '*', 'cert'), path.join('AIDs', '*', 'cert', '**'),
372
+ path.join('AIDs', '*', 'keys'), path.join('AIDs', '*', 'keys', '**'),
373
+ path.join('AIDs', '*', 'private'), path.join('AIDs', '*', 'private', '**'),
374
+ path.join('aids', '*', 'cert'), path.join('aids', '*', 'cert', '**'),
375
+ path.join('aids', '*', 'keys'), path.join('aids', '*', 'keys', '**'),
376
+ path.join('aids', '*', 'private'), path.join('aids', '*', 'private', '**'),
377
+ '.device_id', '.env', '.seed', '.seed.*', '.migrated-*',
378
+ path.join('**', '*.json_'), path.join('**', '*.json.migrated'),
379
+ path.join('agents', 'defaults_*.json'), '.lock', 'daemon.pid',
380
+ path.join('data', 'causation-aun.json*'), path.join('data', 'contact-book-audit.jsonl'),
381
+ path.join('data', 'feishu-seen-*.jsonl'), path.join('data', '*-greeted-*.jsonl'),
382
+ path.join('data', 'message-queue.json*'), path.join('data', 'daemon', 'message-queue.json*'),
383
+ path.join('data', 'daemon', 'control') + '/**', path.join('data', 'daemon', 'migrations') + '/**',
384
+ path.join('data', 'channel-state') + '/**', path.join('data', 'instance', 'control.token'),
385
+ path.join('data', 'outbox'), path.join('data', 'outbox') + '/**',
386
+ path.join('data', 'handoff'), path.join('data', 'handoff') + '/**',
387
+ path.join('agents', '*', 'sessions', '**'), path.join('agents', '*', 'data', 'channels', '**'),
388
+ path.join('agents', '*', 'data', 'handoff', '**'), path.join('agents', '*', 'data', 'async-questions', '**'),
389
+ path.join('agents', '*', 'data', 'task-activity', '**'), path.join('agents', '*', 'data', 'aun', '**'),
390
+ path.join('agents', '*', 'data', 'outbox.jsonl*'), path.join('agents', '*', 'data', 'activity-outbox.jsonl*'),
391
+ path.join('agents', '*', 'data', 'contact-audit.jsonl'),
392
+ ];
393
+ return entries.map(entry => path.join(root, entry));
394
+ }
395
+ export function getHClassSandboxPatterns(root = resolveRoot(), options = {}) {
396
+ const absoluteRoot = resolveThroughExistingAncestor(root);
397
+ assertValidSelfAid(options, 'getHClassSandboxPatterns');
398
+ if (!normalizeAid(options.selfAid))
399
+ return getLegacyHClassSandboxPatterns(absoluteRoot);
400
+ const entries = [
401
+ 'data', path.join('data', '**'),
402
+ 'AIDs', path.join('AIDs', '**'),
403
+ 'aids', path.join('aids', '**'),
252
404
  'CA',
253
405
  path.join('CA', '**'),
254
- path.join('AIDs', '*', 'cert'),
255
- path.join('AIDs', '*', 'cert', '**'),
256
- path.join('AIDs', '*', 'keys'),
257
- path.join('AIDs', '*', 'keys', '**'),
258
- path.join('AIDs', '*', 'private'),
259
- path.join('AIDs', '*', 'private', '**'),
260
- path.join('aids', '*', 'cert'),
261
- path.join('aids', '*', 'cert', '**'),
262
- path.join('aids', '*', 'keys'),
263
- path.join('aids', '*', 'keys', '**'),
264
- path.join('aids', '*', 'private'),
265
- path.join('aids', '*', 'private', '**'),
266
- '.device_id',
267
- '.env',
268
- '.seed',
269
- '.seed.*',
270
- '.migrated-*',
271
- path.join('**', '*.json_'),
272
- path.join('**', '*.json.migrated'),
273
- path.join('agents', 'defaults_*.json'),
274
- '.lock',
275
- 'daemon.pid',
276
- path.join('data', 'causation-aun.json*'),
277
- path.join('data', 'triggers') + '/**',
278
- path.join('data', 'sessions') + '/**',
279
- path.join('data', 'contact-book-audit.jsonl'),
280
- path.join('data', 'feishu-seen-*.jsonl'),
281
- path.join('data', '*-greeted-*.jsonl'),
282
- path.join('data', 'message-queue.json*'),
283
- path.join('data', 'daemon', 'message-queue.json*'),
284
- path.join('data', 'daemon', 'control') + '/**',
285
- path.join('data', 'daemon', 'migrations') + '/**',
286
- path.join('data', 'channel-state') + '/**',
287
- path.join('data', 'instance', 'control.token'),
288
- path.join('data', 'outbox'),
289
- path.join('data', 'outbox') + '/**',
290
- path.join('data', 'handoff'),
291
- path.join('data', 'handoff') + '/**',
292
- path.join('agents', '*', 'sessions', '**'),
293
- path.join('agents', '*', 'data', 'channels', '**'),
294
- path.join('agents', '*', 'data', 'handoff', '**'),
295
- path.join('agents', '*', 'data', 'async-questions', '**'),
296
- path.join('agents', '*', 'data', 'aun', '**'),
297
- path.join('agents', '*', 'data', 'outbox.jsonl*'),
298
- path.join('agents', '*', 'data', 'activity-outbox.jsonl*'),
299
- path.join('agents', '*', 'data', 'contact-audit.jsonl'),
406
+ '.device_id', '.env', '.seed', '.seed.*', '.lock', 'daemon.pid',
407
+ path.join('agents', options.selfAid, 'data', 'codex-tokens'),
408
+ path.join('agents', options.selfAid, 'data', 'codex-tokens', '**'),
409
+ path.join('agents', options.selfAid, 'data', '**', 'context-tokens.json'),
410
+ path.join('agents', options.selfAid, 'data', '**', 'menu-tokens.json'),
300
411
  ];
412
+ // Exact roots are substantially smaller than `agents/*/**` on Windows and
413
+ // avoid relying on ACL override precedence to re-open self Agent content.
414
+ try {
415
+ for (const entry of fs.readdirSync(path.join(absoluteRoot, 'agents'), { withFileTypes: true })) {
416
+ if (entry.isDirectory() && entry.name.toLowerCase() !== options.selfAid.toLowerCase()) {
417
+ entries.push(path.join('agents', entry.name), path.join('agents', entry.name, '**'));
418
+ }
419
+ }
420
+ }
421
+ catch { }
301
422
  return entries.map(entry => path.join(absoluteRoot, entry));
302
423
  }
303
424
  export function getLClassWritePatterns(root = resolveRoot()) {
@@ -315,7 +436,7 @@ const CODEX_GLOB_MAGIC = /[*?[\]]/;
315
436
  // passing it as one CreateProcess argument. Real Codex 0.154 payloads are
316
437
  // roughly 3-5x larger than EvolCore's input map, so leave enough room below
317
438
  // Windows' 32,767-character command-line ceiling for helper metadata.
318
- const CODEX_WINDOWS_FILESYSTEM_RULE_BUDGET_BYTES = 7 * 1024;
439
+ const CODEX_WINDOWS_FILESYSTEM_RULE_BUDGET_BYTES = 10 * 1024;
319
440
  const CODEX_WINDOWS_MATERIALIZED_FILE_GLOBS = [
320
441
  '**/*.json_',
321
442
  '**/*.json.migrated',
@@ -330,13 +451,61 @@ function addExistingTarget(targets, value) {
330
451
  }
331
452
  catch { }
332
453
  }
333
- export function getExistingHClassMaskTargets(root = resolveRoot()) {
454
+ function getAgentAwareHClassMaskTargets(root, selfAid) {
455
+ const absoluteRoot = resolveThroughExistingAncestor(root);
456
+ const targets = new Map();
457
+ for (const relative of [
458
+ 'data', 'AIDs', 'aids', 'CA',
459
+ '.device_id', '.env', '.seed', '.lock', 'daemon.pid',
460
+ ])
461
+ addExistingTarget(targets, path.join(absoluteRoot, relative));
462
+ const agentsDir = path.join(absoluteRoot, 'agents');
463
+ try {
464
+ for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
465
+ if (!entry.isDirectory())
466
+ continue;
467
+ const agentRoot = path.join(agentsDir, entry.name);
468
+ if (entry.name.toLowerCase() !== selfAid.toLowerCase()) {
469
+ addExistingTarget(targets, agentRoot);
470
+ continue;
471
+ }
472
+ addExistingTarget(targets, path.join(agentRoot, 'data', 'codex-tokens'));
473
+ const walk = (directory, depth = 0) => {
474
+ if (depth > 8)
475
+ return;
476
+ let children;
477
+ try {
478
+ children = fs.readdirSync(directory, { withFileTypes: true });
479
+ }
480
+ catch {
481
+ return;
482
+ }
483
+ for (const child of children) {
484
+ const childPath = path.join(directory, child.name);
485
+ if (child.isDirectory())
486
+ walk(childPath, depth + 1);
487
+ else if (/^(?:context-tokens|menu-tokens)\.json(?:_|__|\.tmp-.*)?$/i.test(child.name)) {
488
+ addExistingTarget(targets, childPath);
489
+ }
490
+ }
491
+ };
492
+ walk(path.join(agentRoot, 'data'));
493
+ }
494
+ }
495
+ catch { }
496
+ return [...targets.values()];
497
+ }
498
+ export function getExistingHClassMaskTargets(root = resolveRoot(), options = {}) {
499
+ assertValidSelfAid(options, 'getExistingHClassMaskTargets');
500
+ const selfAid = normalizeAid(options.selfAid);
501
+ if (selfAid)
502
+ return getAgentAwareHClassMaskTargets(root, selfAid);
334
503
  const absoluteRoot = resolveThroughExistingAncestor(root);
335
504
  const targets = new Map();
336
505
  for (const relative of [
337
506
  'daemon.json', 'backups/config', '.snapshots', 'CA',
338
507
  '.device_id', '.env', '.seed', '.lock', 'daemon.pid',
339
- 'data/causation-aun.json', 'data/sessions', 'data/triggers', 'data/contact-book-audit.jsonl',
508
+ 'data/causation-aun.json', 'data/contact-book-audit.jsonl',
340
509
  'data/message-queue.json', 'data/message-queue.json_', 'data/message-queue.json__',
341
510
  'data/daemon/message-queue.json', 'data/daemon/control', 'data/daemon/migrations', 'data/channel-state',
342
511
  'data/instance/control.token',
@@ -388,6 +557,7 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
388
557
  addExistingTarget(targets, path.join(agentRoot, 'data', 'channels'));
389
558
  addExistingTarget(targets, path.join(agentRoot, 'data', 'handoff'));
390
559
  addExistingTarget(targets, path.join(agentRoot, 'data', 'async-questions'));
560
+ addExistingTarget(targets, path.join(agentRoot, 'data', 'task-activity'));
391
561
  addExistingTarget(targets, path.join(agentRoot, 'data', 'aun'));
392
562
  addExistingTarget(targets, path.join(agentRoot, 'data', 'outbox.jsonl'));
393
563
  addExistingTarget(targets, path.join(agentRoot, 'data', 'activity-outbox.jsonl'));
@@ -410,10 +580,32 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
410
580
  }
411
581
  return [...targets.values()];
412
582
  }
413
- export function getExistingLClassReadOnlyTargets(root = resolveRoot()) {
583
+ export function getExistingLClassReadOnlyTargets(root = resolveRoot(), options = {}) {
584
+ assertValidSelfAid(options, 'getExistingLClassReadOnlyTargets');
414
585
  const targets = new Map();
415
586
  const absoluteRoot = resolveThroughExistingAncestor(root);
416
587
  const agentsDir = path.join(absoluteRoot, 'agents');
588
+ const selfAid = normalizeAid(options.selfAid);
589
+ if (selfAid) {
590
+ for (const relative of ['daemon.json', 'agents/defaults.json', 'backups/config']) {
591
+ addExistingTarget(targets, path.join(absoluteRoot, relative));
592
+ }
593
+ const ownRoot = path.join(agentsDir, selfAid);
594
+ for (const relative of [
595
+ 'config.json', 'contact.json', 'contact-operations.jsonl', 'roles', 'triggers',
596
+ ])
597
+ addExistingTarget(targets, path.join(ownRoot, relative));
598
+ try {
599
+ const relationsDir = path.join(ownRoot, 'relations');
600
+ for (const relation of fs.readdirSync(relationsDir, { withFileTypes: true })) {
601
+ if (!relation.isDirectory())
602
+ continue;
603
+ addExistingTarget(targets, path.join(relationsDir, relation.name, 'config.json'));
604
+ }
605
+ }
606
+ catch { }
607
+ return [...targets.values()];
608
+ }
417
609
  try {
418
610
  for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
419
611
  if (!entry.isDirectory())
@@ -425,11 +617,49 @@ export function getExistingLClassReadOnlyTargets(root = resolveRoot()) {
425
617
  catch { }
426
618
  return [...targets.values()];
427
619
  }
620
+ export function getExistingRelationAttachmentDirs(root = resolveRoot(), options = {}) {
621
+ const absoluteRoot = resolveThroughExistingAncestor(root);
622
+ const agentsDir = path.join(absoluteRoot, 'agents');
623
+ const targets = new Set();
624
+ try {
625
+ for (const agent of fs.readdirSync(agentsDir, { withFileTypes: true })) {
626
+ if (!agent.isDirectory())
627
+ continue;
628
+ if (options.selfAid) {
629
+ const isSelf = agent.name.toLowerCase() === options.selfAid.toLowerCase();
630
+ if (options.onlyOtherAgents ? isSelf : !isSelf)
631
+ continue;
632
+ }
633
+ const relationsDir = path.join(agentsDir, agent.name, 'relations');
634
+ for (const relation of fs.readdirSync(relationsDir, { withFileTypes: true })) {
635
+ if (!relation.isDirectory())
636
+ continue;
637
+ const attachments = path.join(relationsDir, relation.name, 'attachments');
638
+ try {
639
+ if (fs.lstatSync(attachments).isDirectory())
640
+ targets.add(attachments);
641
+ }
642
+ catch { }
643
+ }
644
+ }
645
+ }
646
+ catch { }
647
+ return [...targets];
648
+ }
428
649
  export function buildClaudeProtectedFilesystem(root = resolveRoot(), options = {}) {
429
- const hClassPaths = getExistingHClassMaskTargets(root).map(target => target.path);
430
- const lClassPaths = getExistingLClassReadOnlyTargets(root).map(target => target.path);
431
- const allPaths = [...new Set([...hClassPaths, ...lClassPaths])];
432
- const denyRead = options.denyLClassRead ? allPaths : hClassPaths;
650
+ const hClassPaths = [
651
+ ...getExistingHClassMaskTargets(root, options).map(target => target.path),
652
+ ];
653
+ const relationAttachmentPaths = getExistingRelationAttachmentDirs(root, options);
654
+ const lClassPaths = getExistingLClassReadOnlyTargets(root, options).map(target => target.path);
655
+ const allPaths = [...new Set([...hClassPaths, ...lClassPaths, ...relationAttachmentPaths])];
656
+ // Self-Agent relation attachments are ordinary readable content. They stay
657
+ // in denyWrite so the nested Claude sandbox cannot mutate an uploaded asset;
658
+ // callers without identity context retain the legacy deny-read behavior.
659
+ const legacyAttachmentRead = options.selfAid ? [] : relationAttachmentPaths;
660
+ const denyRead = options.denyLClassRead
661
+ ? [...new Set([...hClassPaths, ...lClassPaths, ...legacyAttachmentRead])]
662
+ : [...new Set([...hClassPaths, ...legacyAttachmentRead])];
433
663
  if (allPaths.length > CLAUDE_PROTECTED_TARGET_LIMIT) {
434
664
  throw new Error(`[ClaudeSandbox] protected target limit exceeded: ${allPaths.length} > ${CLAUDE_PROTECTED_TARGET_LIMIT}`);
435
665
  }
@@ -446,14 +676,16 @@ export function buildClaudeProtectedFilesystem(root = resolveRoot(), options = {
446
676
  denyWrite: allPaths,
447
677
  };
448
678
  }
449
- export function buildCodexHClassFilesystemRules(root = resolveRoot(), platform = process.platform) {
679
+ export function buildCodexHClassFilesystemRules(root = resolveRoot(), platformOrOptions = process.platform) {
680
+ const options = typeof platformOrOptions === 'string' ? {} : platformOrOptions;
681
+ const platform = typeof platformOrOptions === 'string' ? platformOrOptions : (platformOrOptions.platform ?? process.platform);
450
682
  const filesystem = {
451
683
  // Codex pre-expands unbounded deny globs on Linux/WSL/Windows. Without a
452
684
  // bound, patterns such as **/*.json_ are accepted but may not be enforced.
453
685
  glob_scan_max_depth: 64,
454
- ...Object.fromEntries(filterWindowsCodexFilesystemTargets(getHClassSandboxPatterns(root), platform).map(pattern => [pattern, 'deny'])),
686
+ ...Object.fromEntries(filterWindowsCodexFilesystemTargets(getHClassSandboxPatterns(root, options), platform).map(pattern => [pattern, 'deny'])),
455
687
  };
456
- return materializeWindowsCodexRecursiveFileDenies(compactWindowsCodexFilesystemRules(filesystem, platform), root, platform);
688
+ return materializeWindowsCodexRecursiveFileDenies(compactWindowsCodexFilesystemRules(filesystem, platform), root, platform, options);
457
689
  }
458
690
  /**
459
691
  * Windows Codex sandbox setup materializes a path while applying its deny-read
@@ -565,12 +797,12 @@ export function compactWindowsCodexFilesystemRules(filesystem, platform = proces
565
797
  * remain dynamic globs, preserving protection for newly created agent/config
566
798
  * paths without recreating the unbounded recursive expansion.
567
799
  */
568
- export function materializeWindowsCodexRecursiveFileDenies(filesystem, root = resolveRoot(), platform = process.platform) {
800
+ export function materializeWindowsCodexRecursiveFileDenies(filesystem, root = resolveRoot(), platform = process.platform, options = {}) {
569
801
  if (platform !== 'win32')
570
802
  return filesystem;
571
803
  const absoluteRoot = resolveThroughExistingAncestor(root);
572
804
  const materializedPatterns = new Set(CODEX_WINDOWS_MATERIALIZED_FILE_GLOBS.map(pattern => path.join(absoluteRoot, pattern)));
573
- const deniedDirectories = getExistingHClassMaskTargets(absoluteRoot)
805
+ const deniedDirectories = getExistingHClassMaskTargets(absoluteRoot, options)
574
806
  .filter(target => target.kind === 'directory')
575
807
  .map(target => target.path);
576
808
  const projected = {};
@@ -616,7 +848,7 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
616
848
  // nested bwrap per-file projection. Callers on platforms without that
617
849
  // outer guard retain the historical expansion by default.
618
850
  const expandLClassReadDeny = options.expandLClassReadDeny ?? true;
619
- for (const target of getExistingLClassReadOnlyTargets(root)) {
851
+ for (const target of getExistingLClassReadOnlyTargets(root, options)) {
620
852
  if (target.kind === 'directory') {
621
853
  // :workspace/:read-only already grants L-class read access on Windows.
622
854
  // Omitting redundant read ACLs keeps Codex's serialized helper payload
@@ -634,8 +866,11 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
634
866
  lClassRules[target.path] = lClassAccess;
635
867
  }
636
868
  }
869
+ const hClassOptions = { platform };
870
+ if (Object.prototype.hasOwnProperty.call(options, 'selfAid'))
871
+ hClassOptions.selfAid = options.selfAid;
637
872
  const filesystem = compactWindowsCodexFilesystemRules({
638
- ...buildCodexHClassFilesystemRules(root, platform),
873
+ ...buildCodexHClassFilesystemRules(root, hClassOptions),
639
874
  ...lClassRules,
640
875
  }, platform);
641
876
  assertWindowsCodexFilesystemRuleBudget(filesystem, platform);
@@ -643,7 +878,7 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
643
878
  }
644
879
  export function hClassGrantIncludesProtectedPath(value, options = {}) {
645
880
  const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
646
- if (containsHClassReference(value))
881
+ if (containsHClassReference(value, options) || containsAgentScopeReference(value, options))
647
882
  return true;
648
883
  const normalized = value.replace(/\\/g, '/');
649
884
  const globIndex = normalized.search(/[*?[{]/);
@@ -651,12 +886,29 @@ export function hClassGrantIncludesProtectedPath(value, options = {}) {
651
886
  ? value
652
887
  : normalized.slice(0, globIndex).replace(/\/+$/, '') || '.';
653
888
  const candidate = resolveProtectedCandidate(literalGrantRoot, options.cwd);
654
- if (isHClassPath(candidate, { root }))
889
+ if (isHClassPath(candidate, { root, selfAid: options.selfAid }))
655
890
  return true;
656
891
  if (isSameOrDescendant(root, candidate))
657
892
  return true;
658
893
  if (!isSameOrDescendant(candidate, root))
659
894
  return false;
895
+ if (options.selfAid) {
896
+ const relative = path.relative(root, candidate).split(path.sep).filter(Boolean);
897
+ if (relative[0] === 'data' || relative[0] === 'AIDs' || relative[0] === 'aids')
898
+ return true;
899
+ if (relative[0] === 'agents' && relative[1]) {
900
+ if (relative[1].toLowerCase() !== options.selfAid.toLowerCase())
901
+ return true;
902
+ if (relative.length <= 2)
903
+ return true;
904
+ if (relative[2] === 'data' && (!relative[3] || ['codex-tokens', 'channels'].includes(relative[3])))
905
+ return true;
906
+ }
907
+ // A grant scoped to an ordinary self-Agent subtree does not automatically
908
+ // cover an H-class path; this is the distinction that lets sessions,
909
+ // handoff and outbox remain usable while token/data roots stay protected.
910
+ return false;
911
+ }
660
912
  // A filesystem grant is a range, not a one-file allow. Every directory
661
913
  // below the protected root can contain an H-class backup name (for example
662
914
  // *.json_), so an overlay grant there could bypass the baseline deny rules.
@@ -666,7 +918,7 @@ export function lClassGrantIncludesProtectedPath(value, options = {}) {
666
918
  if (!value.trim())
667
919
  return false;
668
920
  const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
669
- if (isLClassPath(value, { cwd: options.cwd, root }))
921
+ if (isLClassPath(value, { cwd: options.cwd, root, selfAid: options.selfAid }))
670
922
  return true;
671
923
  const normalized = value.replace(/\\/g, '/');
672
924
  const globIndex = normalized.search(/[*?[{]/);
@@ -674,31 +926,29 @@ export function lClassGrantIncludesProtectedPath(value, options = {}) {
674
926
  ? value
675
927
  : normalized.slice(0, globIndex).replace(/\/+$/, '') || '.';
676
928
  const candidate = resolveProtectedCandidate(literalGrantRoot, options.cwd);
677
- if (isSameOrDescendant(root, candidate))
929
+ if (isSameOrDescendant(root, candidate) && candidate === root)
678
930
  return true;
679
931
  if (!isSameOrDescendant(candidate, root))
680
932
  return false;
681
- const relative = path.relative(root, candidate);
682
- const segments = relative.split(path.sep).filter(Boolean);
683
- if (segments[0] === 'data') {
684
- if (segments.length === 1)
685
- return true;
686
- return segments[1] === 'triggers';
687
- }
933
+ const relative = path.relative(root, candidate).split(path.sep).filter(Boolean);
934
+ const segments = relative;
935
+ if (segments[0] === 'backups' && segments[1] === 'config')
936
+ return true;
688
937
  if (segments[0] === 'agents') {
689
- // The agents root and each individual Agent root contain a roles/ subtree.
938
+ // A grant covering an Agent root also covers its L-class configuration.
690
939
  if (segments.length <= 2)
691
940
  return true;
692
- return segments[2] === 'roles';
941
+ return ['config.json', 'contact.json', 'contact-operations.jsonl', 'relations', 'roles', 'triggers']
942
+ .includes(segments[2]);
693
943
  }
694
944
  return false;
695
945
  }
696
- export function workspaceContainsHClassPaths(projectPath, root = resolveRoot()) {
946
+ export function workspaceContainsHClassPaths(projectPath, root = resolveRoot(), options = {}) {
697
947
  const project = resolveThroughExistingAncestor(projectPath);
698
948
  const protectedRoot = resolveThroughExistingAncestor(root);
699
949
  if (isSameOrDescendant(protectedRoot, project))
700
950
  return true;
701
951
  if (!isSameOrDescendant(project, protectedRoot))
702
952
  return false;
703
- return hClassGrantIncludesProtectedPath(project, { root: protectedRoot });
953
+ return hClassGrantIncludesProtectedPath(project, { root: protectedRoot, selfAid: options.selfAid });
704
954
  }