evolcore 0.0.1 → 0.0.2

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 (371) hide show
  1. package/CHANGELOG.md +813 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION-0.5.0.md +378 -0
  4. package/README.md +318 -14
  5. package/ROLE_ACCESS_CONTROL.md +174 -0
  6. package/assets/.env.template +4 -0
  7. package/assets/brand/evolcore/README.md +19 -0
  8. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  9. package/assets/brand/evolcore/evolcore-app-icon.svg +13 -0
  10. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  11. package/assets/brand/evolcore/evolcore-brand-board.svg +126 -0
  12. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  13. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  14. package/assets/brand/evolcore/evolcore-logo-reverse.svg +14 -0
  15. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  16. package/assets/brand/evolcore/evolcore-logo.svg +14 -0
  17. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  18. package/assets/brand/evolcore/evolcore-mark.svg +10 -0
  19. package/bin/ec-safe-output.js +161 -0
  20. package/bin/ec.js +29 -0
  21. package/dist/agents/baseagent.js +163 -0
  22. package/dist/agents/claude-runner.js +2385 -0
  23. package/dist/agents/codex-app-server-client.js +448 -0
  24. package/dist/agents/codex-runner.js +2639 -0
  25. package/dist/agents/gemini-runner.js +666 -0
  26. package/dist/agents/runner-types.js +75 -0
  27. package/dist/aun/aid/agentmd.js +216 -0
  28. package/dist/aun/aid/client.js +132 -0
  29. package/dist/aun/aid/control-aid.js +91 -0
  30. package/dist/aun/aid/identity.js +518 -0
  31. package/dist/aun/aid/index.js +4 -0
  32. package/dist/aun/aid/store.js +74 -0
  33. package/dist/aun/aid/types.js +1 -0
  34. package/dist/aun/aid/validation.js +21 -0
  35. package/dist/aun/group-identity.js +10 -0
  36. package/dist/aun/msg/group-index.js +6 -0
  37. package/dist/aun/msg/group.js +1231 -0
  38. package/dist/aun/msg/history.js +123 -0
  39. package/dist/aun/msg/index.js +5 -0
  40. package/dist/aun/msg/p2p.js +393 -0
  41. package/dist/aun/msg/payload-type.js +27 -0
  42. package/dist/aun/msg/upload.js +137 -0
  43. package/dist/aun/outbox.js +160 -0
  44. package/dist/aun/rpc/caller.js +42 -0
  45. package/dist/aun/rpc/connection.js +25 -0
  46. package/dist/aun/rpc/index.js +2 -0
  47. package/dist/aun/service-proxy.js +225 -0
  48. package/dist/aun/storage/download.js +29 -0
  49. package/dist/aun/storage/index.js +3 -0
  50. package/dist/aun/storage/manage.js +10 -0
  51. package/dist/aun/storage/upload.js +68 -0
  52. package/dist/channels/aun.js +4147 -0
  53. package/dist/channels/daemon.js +422 -0
  54. package/dist/channels/dingtalk.js +649 -0
  55. package/dist/channels/feishu.js +1789 -0
  56. package/dist/channels/qqbot.js +409 -0
  57. package/dist/channels/wechat.js +817 -0
  58. package/dist/channels/wecom.js +565 -0
  59. package/dist/cli/agent-command.js +641 -0
  60. package/dist/cli/agent.js +1059 -0
  61. package/dist/cli/aun-commands.js +1948 -0
  62. package/dist/cli/bench.js +1228 -0
  63. package/dist/cli/cli-argv.js +66 -0
  64. package/dist/cli/code-stats.js +329 -0
  65. package/dist/cli/command-log.js +82 -0
  66. package/dist/cli/config-selector.js +69 -0
  67. package/dist/cli/config.js +261 -0
  68. package/dist/cli/ctl-command.js +62 -0
  69. package/dist/cli/daemon-commands.js +2887 -0
  70. package/dist/cli/fs-command.js +1447 -0
  71. package/dist/cli/handoff-command.js +302 -0
  72. package/dist/cli/help.js +31 -0
  73. package/dist/cli/index.js +358 -0
  74. package/dist/cli/init-channel.js +1377 -0
  75. package/dist/cli/init.js +553 -0
  76. package/dist/cli/link-rules.js +240 -0
  77. package/dist/cli/model.js +590 -0
  78. package/dist/cli/net-check.js +723 -0
  79. package/dist/cli/queue-command.js +126 -0
  80. package/dist/cli/response.js +345 -0
  81. package/dist/cli/restart-monitor.js +456 -0
  82. package/dist/cli/stats.js +607 -0
  83. package/dist/cli/task-context.js +80 -0
  84. package/dist/cli/trigger-command.js +505 -0
  85. package/dist/cli/version.js +88 -0
  86. package/dist/cli/watch-logs.js +33 -0
  87. package/dist/cli/watch-msg.js +673 -0
  88. package/dist/config/boot-log.js +266 -0
  89. package/dist/config/builtin-role-templates.js +30 -0
  90. package/dist/config/builtin-roles.js +85 -0
  91. package/dist/config/config-batch-get.js +11 -0
  92. package/dist/config/config-field-policy.js +261 -0
  93. package/dist/config/config-manager.js +916 -0
  94. package/dist/config/config-operation-service.js +384 -0
  95. package/dist/config/contact-book.js +371 -0
  96. package/dist/config/gateway-config.js +858 -0
  97. package/dist/config/lifecycle.js +17 -0
  98. package/dist/config/mention-mode.js +27 -0
  99. package/dist/config/merge.js +161 -0
  100. package/dist/config/owner-policy.js +4 -0
  101. package/dist/config/peer-role-resolver.js +139 -0
  102. package/dist/config/resolved-config-op.js +483 -0
  103. package/dist/config/role-config-v4-startup.js +32 -0
  104. package/dist/config/role-config-v5-startup.js +27 -0
  105. package/dist/config/role-schema.js +105 -0
  106. package/dist/config/role-service.js +159 -0
  107. package/dist/config/role-store.js +204 -0
  108. package/dist/config/roles.js +55 -0
  109. package/dist/config/schema-registry.js +154 -0
  110. package/dist/config/snapshot.js +598 -0
  111. package/dist/config-store.js +503 -0
  112. package/dist/core/auth/agent-delegation.js +111 -0
  113. package/dist/core/auth/auth-gateway.js +166 -0
  114. package/dist/core/auth/authenticated-actor.js +6 -0
  115. package/dist/core/auth/authorization-audit.js +110 -0
  116. package/dist/core/auth/operation-authorizer.js +718 -0
  117. package/dist/core/auth/operation-catalog.js +675 -0
  118. package/dist/core/baseagent-loader.js +54 -0
  119. package/dist/core/bootstrap-service.js +175 -0
  120. package/dist/core/capability/capability-manager.js +316 -0
  121. package/dist/core/capability/providers/claude-capability-provider.js +176 -0
  122. package/dist/core/capability/providers/codex-capability-provider.js +148 -0
  123. package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
  124. package/dist/core/capability/types.js +27 -0
  125. package/dist/core/causation/audit.js +103 -0
  126. package/dist/core/causation/aun-association.js +111 -0
  127. package/dist/core/causation/context.js +93 -0
  128. package/dist/core/causation/index.js +4 -0
  129. package/dist/core/causation/types.js +2 -0
  130. package/dist/core/channel-loader.js +277 -0
  131. package/dist/core/command/agent-control.js +616 -0
  132. package/dist/core/command/cli-intent-parser.js +225 -0
  133. package/dist/core/command/command-handler.js +1638 -0
  134. package/dist/core/command/menu-handler.js +3354 -0
  135. package/dist/core/command/menu-protocol.js +247 -0
  136. package/dist/core/command/role-menu.js +1524 -0
  137. package/dist/core/command/slash-gate.js +148 -0
  138. package/dist/core/command/slash-handler.js +3056 -0
  139. package/dist/core/daemon-file-cache.js +216 -0
  140. package/dist/core/event-bus.js +32 -0
  141. package/dist/core/event-catalog.js +740 -0
  142. package/dist/core/evolagent-registry.js +546 -0
  143. package/dist/core/evolagent.js +331 -0
  144. package/dist/core/handoff/dispatcher.js +229 -0
  145. package/dist/core/handoff/mutex.js +46 -0
  146. package/dist/core/handoff/runtime.js +324 -0
  147. package/dist/core/handoff/store.js +537 -0
  148. package/dist/core/handoff/types.js +12 -0
  149. package/dist/core/inference/text-inference.js +173 -0
  150. package/dist/core/interaction-registration.js +10 -0
  151. package/dist/core/interaction-router.js +278 -0
  152. package/dist/core/message/create-status.js +67 -0
  153. package/dist/core/message/im-renderer.js +657 -0
  154. package/dist/core/message/items-formatter.js +76 -0
  155. package/dist/core/message/logical-queue-bridge.js +123 -0
  156. package/dist/core/message/message-bridge.js +803 -0
  157. package/dist/core/message/message-cache.js +56 -0
  158. package/dist/core/message/message-log.js +339 -0
  159. package/dist/core/message/message-processor.js +4 -0
  160. package/dist/core/message/message-queue.js +1436 -0
  161. package/dist/core/message/message-utils.js +70 -0
  162. package/dist/core/message/peer-mode.js +105 -0
  163. package/dist/core/message/pending-hints.js +232 -0
  164. package/dist/core/message/response-depth.js +33 -0
  165. package/dist/core/message/response-engine.js +3776 -0
  166. package/dist/core/message/response-snapshot.js +83 -0
  167. package/dist/core/message/stream-debouncer.js +130 -0
  168. package/dist/core/message/stream-idle-monitor.js +124 -0
  169. package/dist/core/model/config-scope.js +162 -0
  170. package/dist/core/model/field-scope.js +78 -0
  171. package/dist/core/model/model-catalog.js +227 -0
  172. package/dist/core/model/model-diagnostics.js +182 -0
  173. package/dist/core/model/model-permission.js +90 -0
  174. package/dist/core/permission/approval-gateway.js +1017 -0
  175. package/dist/core/permission/ec-command-parser.js +347 -0
  176. package/dist/core/permission/execution-sandbox.js +16 -0
  177. package/dist/core/permission/index.js +6 -0
  178. package/dist/core/permission/mode.js +24 -0
  179. package/dist/core/permission/sandbox-runtime.js +265 -0
  180. package/dist/core/permission/tool-policy.js +987 -0
  181. package/dist/core/permission/unix-socket-policy.js +99 -0
  182. package/dist/core/protected-paths.js +330 -0
  183. package/dist/core/relation/peer-identity.js +222 -0
  184. package/dist/core/relation/peer-key.js +1 -0
  185. package/dist/core/role/runtime-policy.js +141 -0
  186. package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
  187. package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
  188. package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
  189. package/dist/core/session/session-file-adapter.js +7 -0
  190. package/dist/core/session/session-file-health.js +45 -0
  191. package/dist/core/session/session-fs-store.js +232 -0
  192. package/dist/core/session/session-key.js +24 -0
  193. package/dist/core/session/session-manager.js +1587 -0
  194. package/dist/core/session/session-mapper.js +100 -0
  195. package/dist/core/session/session-renew.js +314 -0
  196. package/dist/core/session/session-title.js +128 -0
  197. package/dist/core/session/session-turn-coordinator.js +205 -0
  198. package/dist/core/session/session-turns.js +67 -0
  199. package/dist/core/system-channels.js +29 -0
  200. package/dist/eck/baseagent-caps.js +18 -0
  201. package/dist/eck/detect.js +47 -0
  202. package/dist/eck/group-rules-sync.js +345 -0
  203. package/dist/eck/init.js +77 -0
  204. package/dist/eck/kit-renderer.js +359 -0
  205. package/dist/eck/manifest-engine.js +446 -0
  206. package/dist/eck/message-renderer.js +199 -0
  207. package/dist/eck/rules-loader.js +28 -0
  208. package/dist/index.js +2870 -4
  209. package/dist/ipc.js +748 -0
  210. package/dist/paths.js +262 -0
  211. package/dist/product.js +18 -0
  212. package/dist/response-system/context-builder.js +71 -0
  213. package/dist/response-system/coordinator.js +117 -0
  214. package/dist/response-system/decision-executor.js +86 -0
  215. package/dist/response-system/engines/v1/index.js +21 -0
  216. package/dist/response-system/engines/v1/interactive-flow.js +27 -0
  217. package/dist/response-system/engines/v1/proactive-flow.js +137 -0
  218. package/dist/response-system/engines/v1/types.js +1 -0
  219. package/dist/response-system/extensions.js +41 -0
  220. package/dist/response-system/index.js +6 -0
  221. package/dist/response-system/modes/index.js +7 -0
  222. package/dist/response-system/modes/single-session/index.js +72 -0
  223. package/dist/response-system/queues/fifo-queue.js +44 -0
  224. package/dist/response-system/queues/index.js +6 -0
  225. package/dist/response-system/queues/lifo-queue.js +42 -0
  226. package/dist/response-system/queues/priority-queue.js +63 -0
  227. package/dist/response-system/registry.js +97 -0
  228. package/dist/response-system/resolver.js +37 -0
  229. package/dist/response-system/selector.js +23 -0
  230. package/dist/response-system/types.js +7 -0
  231. package/dist/stats/billing.js +151 -0
  232. package/dist/stats/budget.js +93 -0
  233. package/dist/stats/db.js +403 -0
  234. package/dist/stats/eck-vars.js +89 -0
  235. package/dist/stats/index.js +11 -0
  236. package/dist/stats/normalizer.js +80 -0
  237. package/dist/stats/price-resolver.js +138 -0
  238. package/dist/stats/query.js +763 -0
  239. package/dist/stats/role-budget.js +168 -0
  240. package/dist/stats/writer.js +151 -0
  241. package/dist/trigger/anomaly-store.js +258 -0
  242. package/dist/trigger/audit.js +152 -0
  243. package/dist/trigger/event-source.js +119 -0
  244. package/dist/trigger/feedback.js +685 -0
  245. package/dist/trigger/history.js +290 -0
  246. package/dist/trigger/manager.js +291 -0
  247. package/dist/trigger/parser.js +520 -0
  248. package/dist/trigger/patch.js +148 -0
  249. package/dist/trigger/scheduler.js +1600 -0
  250. package/dist/trigger/script-executor.js +155 -0
  251. package/dist/trigger/state.js +145 -0
  252. package/dist/trigger/types.js +1 -0
  253. package/dist/trigger/validation.js +621 -0
  254. package/dist/types.js +12 -0
  255. package/dist/utils/aid-bind.js +299 -0
  256. package/dist/utils/atomic-write.js +95 -0
  257. package/dist/utils/avatar-upload.js +123 -0
  258. package/dist/utils/cross-platform.js +297 -0
  259. package/dist/utils/ecweb-utils.js +73 -0
  260. package/dist/utils/error-dict.json +153 -0
  261. package/dist/utils/error-utils.js +349 -0
  262. package/dist/utils/instance-registry.js +437 -0
  263. package/dist/utils/locale.js +21 -0
  264. package/dist/utils/log-writer.js +224 -0
  265. package/dist/utils/logger.js +89 -0
  266. package/dist/utils/markdown-to-plain-text.js +20 -0
  267. package/dist/utils/media-cache.js +271 -0
  268. package/dist/utils/model-prices.jsonl +17 -0
  269. package/dist/utils/npm-ops.js +210 -0
  270. package/dist/utils/process-introspect.js +133 -0
  271. package/dist/utils/process-tree-stats.js +271 -0
  272. package/dist/utils/project-path.js +74 -0
  273. package/dist/utils/stats.js +410 -0
  274. package/dist/utils/tool-summary.js +284 -0
  275. package/dist/utils/welcome.js +268 -0
  276. package/kits/docs/GUIDE.md +20 -0
  277. package/kits/docs/INDEX.md +65 -0
  278. package/kits/docs/aun/CHEATSHEET.md +19 -0
  279. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  280. package/kits/docs/channels/aun.md +65 -0
  281. package/kits/docs/channels/feishu.md +56 -0
  282. package/kits/docs/context-assembly.md +366 -0
  283. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  284. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  285. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  286. package/kits/docs/eck_templates/runtime.template.md +19 -0
  287. package/kits/docs/evolcore/INDEX.md +66 -0
  288. package/kits/docs/evolcore/agent.md +69 -0
  289. package/kits/docs/evolcore/aid.md +49 -0
  290. package/kits/docs/evolcore/config.md +149 -0
  291. package/kits/docs/evolcore/ctl.md +46 -0
  292. package/kits/docs/evolcore/event.md +216 -0
  293. package/kits/docs/evolcore/fs-architecture.md +1215 -0
  294. package/kits/docs/evolcore/fs.md +101 -0
  295. package/kits/docs/evolcore/group-fs.md +17 -0
  296. package/kits/docs/evolcore/group-rules.md +226 -0
  297. package/kits/docs/evolcore/group.md +141 -0
  298. package/kits/docs/evolcore/model.md +47 -0
  299. package/kits/docs/evolcore/msg.md +130 -0
  300. package/kits/docs/evolcore/response.md +80 -0
  301. package/kits/docs/evolcore/rpc.md +35 -0
  302. package/kits/docs/evolcore/self-summary.md +29 -0
  303. package/kits/docs/evolcore/stats.md +70 -0
  304. package/kits/docs/evolcore/storage.md +49 -0
  305. package/kits/docs/evolcore/trigger.md +524 -0
  306. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
  307. package/kits/docs/identity/PATH_OPS.md +16 -0
  308. package/kits/docs/identity/ROLE_DETAIL.md +23 -0
  309. package/kits/docs/identity/identity-tools.md +26 -0
  310. package/kits/docs/path-registry.md +43 -0
  311. package/kits/docs/prompt-loading-architecture.md +266 -0
  312. package/kits/docs/venues/aun-group.md +45 -0
  313. package/kits/docs/venues/aun-private.md +10 -0
  314. package/kits/docs/venues/client-desktop.md +10 -0
  315. package/kits/docs/venues/client-mobile.md +10 -0
  316. package/kits/docs/venues/feishu-group.md +13 -0
  317. package/kits/docs/venues/feishu-private.md +9 -0
  318. package/kits/docs/venues/group.md +25 -0
  319. package/kits/docs/venues/private.md +10 -0
  320. package/kits/eck_manifest.auxiliary.json +43 -0
  321. package/kits/eck_manifest.json +191 -0
  322. package/kits/eck_message_manifest.json +63 -0
  323. package/kits/migrations/README-role-config-v4.md +32 -0
  324. package/kits/migrations/migrate-role-config-v4.mjs +623 -0
  325. package/kits/migrations/migrate-role-config-v5.mjs +346 -0
  326. package/kits/migrations/rename-config-file.mjs +99 -0
  327. package/kits/rules/01-overview.md +142 -0
  328. package/kits/rules/02-navigation.md +76 -0
  329. package/kits/rules/03-identity.md +34 -0
  330. package/kits/rules/04-relation.md +59 -0
  331. package/kits/rules/05-venue.md +44 -0
  332. package/kits/rules/06-channel.md +59 -0
  333. package/kits/schemas/_meta.json +29 -0
  334. package/kits/schemas/agent-config.schema.1.json +177 -0
  335. package/kits/schemas/agent-config.schema.2.json +239 -0
  336. package/kits/schemas/agent-config.schema.3.json +119 -0
  337. package/kits/schemas/agent-config.schema.4.json +208 -0
  338. package/kits/schemas/agent-config.schema.5.json +326 -0
  339. package/kits/schemas/contact-book.schema.1.json +36 -0
  340. package/kits/schemas/daemon.schema.1.json +90 -0
  341. package/kits/schemas/defaults.schema.1.json +81 -0
  342. package/kits/schemas/menu-exec-schema-commands.md +208 -0
  343. package/kits/schemas/migrations/README.md +28 -0
  344. package/kits/schemas/relation-config.schema.1.json +158 -0
  345. package/kits/schemas/relation-config.schema.2.json +73 -0
  346. package/kits/schemas/relation-config.schema.3.json +50 -0
  347. package/kits/schemas/relation-config.schema.4.json +47 -0
  348. package/kits/schemas/role-config.schema.1.json +200 -0
  349. package/kits/schemas/role-registry.schema.1.json +35 -0
  350. package/kits/schemas/single-session.schema.1.json +31 -0
  351. package/kits/templates/bootstrap-welcome.md +15 -0
  352. package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
  353. package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
  354. package/kits/templates/message-fragments/inject-default.md +2 -0
  355. package/kits/templates/message-fragments/item.md +2 -0
  356. package/kits/templates/roles/admin.json +8 -0
  357. package/kits/templates/roles/member.json +40 -0
  358. package/kits/templates/roles/owner.json +8 -0
  359. package/kits/templates/roles/visitor.json +39 -0
  360. package/kits/templates/system-fragments/baseagent.md +14 -0
  361. package/kits/templates/system-fragments/bootstrap.md +16 -0
  362. package/kits/templates/system-fragments/channel.md +48 -0
  363. package/kits/templates/system-fragments/commands.md +26 -0
  364. package/kits/templates/system-fragments/identity.md +11 -0
  365. package/kits/templates/system-fragments/relation.md +19 -0
  366. package/kits/templates/system-fragments/session.md +53 -0
  367. package/kits/templates/system-fragments/venue.md +31 -0
  368. package/package.json +50 -15
  369. package/dist/index.d.ts +0 -7
  370. package/dist/index.d.ts.map +0 -1
  371. package/dist/index.js.map +0 -1
@@ -0,0 +1,347 @@
1
+ const DEFAULT_HEAD_LINES = 10;
2
+ const MAX_HEAD_LINES = 10_000;
3
+ const SHELL_UNQUOTED_META = new Set([
4
+ ';', '&', '|', '`', '<', '>', '$', '(', ')', '*', '?', '[', ']', '{', '}', '#',
5
+ ]);
6
+ const AMBIGUOUS_FILE_PATH_RE = /[\0\r\n$`*?\[\]{}<>|;&]/;
7
+ const EVOLCORE_COMMAND_POSITION_RE = /(?:^|[;&|()`])\s*ec(?=\s|[;&|()<>`]|$)/i;
8
+ function trimShellSpacing(value) {
9
+ return value.replace(/^[ \t]+|[ \t]+$/g, '');
10
+ }
11
+ function isShellSpacing(value) {
12
+ return value === ' ' || value === '\t';
13
+ }
14
+ /**
15
+ * Parse the deliberately small shell subset accepted by the privileged EC
16
+ * command path. The returned argv must describe one literal invocation: no
17
+ * expansion, glob, pipeline, command substitution, redirection, or comment.
18
+ * Anything outside this subset is never granted the privileged EC path.
19
+ */
20
+ export function parseLiteralShellArgv(command) {
21
+ const input = trimShellSpacing(command);
22
+ if (!input || input.includes('\0'))
23
+ return null;
24
+ const argv = [];
25
+ let token = '';
26
+ let tokenStarted = false;
27
+ let quote = null;
28
+ const finishToken = () => {
29
+ if (!tokenStarted)
30
+ return;
31
+ argv.push(token);
32
+ token = '';
33
+ tokenStarted = false;
34
+ };
35
+ for (let index = 0; index < input.length; index++) {
36
+ const char = input[index];
37
+ if (quote === 'single') {
38
+ if (char === "'")
39
+ quote = null;
40
+ else
41
+ token += char;
42
+ continue;
43
+ }
44
+ if (quote === 'double') {
45
+ if (char === '"') {
46
+ quote = null;
47
+ continue;
48
+ }
49
+ if (char === '$' || char === '`')
50
+ return null;
51
+ if (char === '\\') {
52
+ const next = input[++index];
53
+ if (next === undefined || next === '\r' || next === '\n')
54
+ return null;
55
+ if (next === '$' || next === '`')
56
+ return null;
57
+ token += ['"', '\\'].includes(next) ? next : `\\${next}`;
58
+ continue;
59
+ }
60
+ token += char;
61
+ continue;
62
+ }
63
+ // A physical newline is literal data only while a quote is open. Outside
64
+ // quotes it can start another shell command and must remain fail-closed.
65
+ if (char === ' ' || char === '\t') {
66
+ finishToken();
67
+ continue;
68
+ }
69
+ // Shell token separators are ASCII space/tab. JavaScript `\s` also
70
+ // includes NBSP, line/paragraph separators, VT and FF, which the shell
71
+ // keeps inside the executable name; accepting those would misclassify a
72
+ // different executable as `ec`.
73
+ if (char === '\r' || char === '\n' || /\s/u.test(char) || char.charCodeAt(0) < 0x20 || char === '\x7f')
74
+ return null;
75
+ if (char === "'") {
76
+ tokenStarted = true;
77
+ quote = 'single';
78
+ continue;
79
+ }
80
+ if (char === '"') {
81
+ tokenStarted = true;
82
+ quote = 'double';
83
+ continue;
84
+ }
85
+ if (char === '\\') {
86
+ const next = input[++index];
87
+ if (next === undefined || next === '\r' || next === '\n')
88
+ return null;
89
+ tokenStarted = true;
90
+ token += next;
91
+ continue;
92
+ }
93
+ // `#` starts a shell comment only at the beginning of a word. Channel
94
+ // peer keys such as `aun#alice` are ordinary literal arguments.
95
+ if ((SHELL_UNQUOTED_META.has(char) && (char !== '#' || !tokenStarted))
96
+ || (char === '~' && !tokenStarted))
97
+ return null;
98
+ tokenStarted = true;
99
+ token += char;
100
+ }
101
+ if (quote)
102
+ return null;
103
+ finishToken();
104
+ return argv.length > 0 ? argv : null;
105
+ }
106
+ function scanBoundedOutputOperators(command) {
107
+ const operators = [];
108
+ let quote = null;
109
+ for (let index = 0; index < command.length; index++) {
110
+ const char = command[index];
111
+ if (quote === 'single') {
112
+ if (char === "'")
113
+ quote = null;
114
+ continue;
115
+ }
116
+ if (quote === 'double') {
117
+ if (char === '"') {
118
+ quote = null;
119
+ continue;
120
+ }
121
+ if (char === '$' || char === '`')
122
+ return null;
123
+ if (char === '\\') {
124
+ const next = command[++index];
125
+ if (next === undefined || next === '\r' || next === '\n' || next === '$' || next === '`')
126
+ return null;
127
+ }
128
+ continue;
129
+ }
130
+ if (char === "'") {
131
+ quote = 'single';
132
+ continue;
133
+ }
134
+ if (char === '"') {
135
+ quote = 'double';
136
+ continue;
137
+ }
138
+ if (char === '\\') {
139
+ if (command[++index] === undefined)
140
+ return null;
141
+ continue;
142
+ }
143
+ if (command.startsWith('2>&1', index)) {
144
+ const before = command[index - 1];
145
+ const after = command[index + 4];
146
+ if ((before === undefined || isShellSpacing(before))
147
+ && (after === undefined || isShellSpacing(after) || after === '|' || after === '>')) {
148
+ operators.push({ kind: 'merge-stderr', start: index, end: index + 4 });
149
+ index += 3;
150
+ continue;
151
+ }
152
+ return null;
153
+ }
154
+ if (char === '|') {
155
+ if (command[index + 1] === '|')
156
+ return null;
157
+ operators.push({ kind: 'pipe', start: index, end: index + 1 });
158
+ continue;
159
+ }
160
+ if (char === '>') {
161
+ if (command[index + 1] === '>')
162
+ return null;
163
+ operators.push({ kind: 'redirect', start: index, end: index + 1 });
164
+ continue;
165
+ }
166
+ if (char === '$' || char === '`' || char === ';' || char === '&' || char === '<'
167
+ || char === '(' || char === ')' || char === '*' || char === '?'
168
+ || char === '[' || char === ']' || char === '{' || char === '}'
169
+ || char === '\0' || char === '\r' || char === '\n')
170
+ return null;
171
+ }
172
+ return quote ? null : operators;
173
+ }
174
+ function parseHeadLines(segment) {
175
+ const argv = parseLiteralShellArgv(segment);
176
+ if (!argv || (argv[0] !== 'head' && argv[0] !== '/usr/bin/head'))
177
+ return null;
178
+ let value;
179
+ if (argv.length === 1)
180
+ value = String(DEFAULT_HEAD_LINES);
181
+ else if (argv.length === 2 && /^-\d+$/.test(argv[1]))
182
+ value = argv[1].slice(1);
183
+ else if (argv.length === 3 && argv[1] === '-n' && /^\d+$/.test(argv[2]))
184
+ value = argv[2];
185
+ else
186
+ return null;
187
+ const lines = Number(value);
188
+ return Number.isSafeInteger(lines) && lines >= 1 && lines <= MAX_HEAD_LINES ? lines : null;
189
+ }
190
+ /**
191
+ * Parse the only shell wrapper allowed around a privileged literal process:
192
+ * optional stderr merge, one bounded `head`, and one create-only output path.
193
+ */
194
+ export function parseBoundedOutputShellCommand(command) {
195
+ const input = trimShellSpacing(command);
196
+ if (!input)
197
+ return null;
198
+ const operators = scanBoundedOutputOperators(input);
199
+ if (!operators || operators.length === 0)
200
+ return null;
201
+ const pattern = operators.map(operator => operator.kind).join(',');
202
+ if (![
203
+ 'merge-stderr',
204
+ 'pipe',
205
+ 'pipe,redirect',
206
+ 'merge-stderr,pipe',
207
+ 'merge-stderr,pipe,redirect',
208
+ 'redirect',
209
+ ].includes(pattern))
210
+ return null;
211
+ const first = operators[0];
212
+ const sourceText = trimShellSpacing(input.slice(0, first.start));
213
+ const argv = parseLiteralShellArgv(sourceText);
214
+ if (!argv)
215
+ return null;
216
+ let mergeStderr = false;
217
+ let pipe;
218
+ let redirect;
219
+ if (first.kind === 'merge-stderr') {
220
+ mergeStderr = true;
221
+ if (operators.length === 1)
222
+ return { argv, mergeStderr };
223
+ pipe = operators[1];
224
+ if (!pipe || pipe.kind !== 'pipe')
225
+ return null;
226
+ if (trimShellSpacing(input.slice(first.end, pipe.start)))
227
+ return null;
228
+ redirect = operators[2];
229
+ }
230
+ else if (first.kind === 'pipe') {
231
+ pipe = first;
232
+ redirect = operators[1];
233
+ }
234
+ else {
235
+ redirect = first;
236
+ }
237
+ let headLines;
238
+ if (pipe) {
239
+ const headEnd = redirect?.start ?? input.length;
240
+ const parsedLines = parseHeadLines(trimShellSpacing(input.slice(pipe.end, headEnd)));
241
+ if (parsedLines === null)
242
+ return null;
243
+ headLines = parsedLines;
244
+ }
245
+ let outputPath;
246
+ if (redirect) {
247
+ const outputArgv = parseLiteralShellArgv(trimShellSpacing(input.slice(redirect.end)));
248
+ if (!outputArgv || outputArgv.length !== 1)
249
+ return null;
250
+ outputPath = outputArgv[0];
251
+ }
252
+ return { argv, mergeStderr, ...(headLines ? { headLines } : {}), ...(outputPath ? { outputPath } : {}) };
253
+ }
254
+ /**
255
+ * Classify only the shell boundary around an EC process invocation. Runner
256
+ * policy must not interpret EC subcommands or arguments: a single literal
257
+ * invocation may leave the sandbox, while shell composition may not.
258
+ */
259
+ export function classifyEvolcoreShellCommand(command) {
260
+ const literalArgv = parseLiteralShellArgv(command);
261
+ if (literalArgv) {
262
+ return literalArgv[0] === 'ec'
263
+ ? { kind: 'literal' }
264
+ : { kind: 'none' };
265
+ }
266
+ const boundedOutput = parseBoundedOutputShellCommand(command);
267
+ if (boundedOutput && boundedOutput.argv[0] === 'ec') {
268
+ return { kind: 'bounded-output', command: boundedOutput };
269
+ }
270
+ return EVOLCORE_COMMAND_POSITION_RE.test(command)
271
+ ? { kind: 'composite' }
272
+ : { kind: 'none' };
273
+ }
274
+ function isDeterministicFilePath(value) {
275
+ return !!value
276
+ && value === value.trim()
277
+ && !value.startsWith('--')
278
+ && !value.startsWith('~')
279
+ && !AMBIGUOUS_FILE_PATH_RE.test(value);
280
+ }
281
+ function findSingleFileOption(argv, startIndex) {
282
+ const matches = [];
283
+ for (let index = startIndex; index < argv.length; index++) {
284
+ const value = argv[index];
285
+ if (value === '--file' || value === '--text-from-file') {
286
+ matches.push({ index, flag: value });
287
+ }
288
+ }
289
+ if (matches.length === 0)
290
+ return { invalid: false };
291
+ if (matches.length !== 1)
292
+ return { invalid: true };
293
+ const filePath = argv[matches[0].index + 1];
294
+ return isDeterministicFilePath(filePath)
295
+ ? { path: filePath, invalid: false }
296
+ : { invalid: true };
297
+ }
298
+ export function parseEvolcoreSendCommand(command) {
299
+ const argv = parseLiteralShellArgv(command);
300
+ if (!argv || argv[0] !== 'ec')
301
+ return null;
302
+ if (argv[1] === 'ctl') {
303
+ if (argv[2] === 'send')
304
+ return { scope: 'ctl', action: 'send' };
305
+ if (argv[2] !== 'file')
306
+ return null;
307
+ const operands = argv.slice(3);
308
+ if (operands.length < 1 || operands.length > 2)
309
+ return null;
310
+ const filePath = operands.at(-1);
311
+ if (!isDeterministicFilePath(filePath))
312
+ return null;
313
+ return { scope: 'ctl', action: 'file', filePath };
314
+ }
315
+ const scope = argv[1];
316
+ const subcommand = argv[2];
317
+ if ((scope !== 'msg' && scope !== 'group') || (subcommand !== 'send' && subcommand !== 'file'))
318
+ return null;
319
+ const targetId = argv[4];
320
+ if (!argv[3] || !targetId || argv[3].startsWith('--') || targetId.startsWith('--'))
321
+ return null;
322
+ // Keep the historical `msg/group file <from> <target> <path>` spelling,
323
+ // while classifying the actual CLI spelling `send ... --file <path>` (and
324
+ // msg `--text-from-file`) under the file operation as well.
325
+ if (subcommand === 'file') {
326
+ if (argv.length !== 6 || !isDeterministicFilePath(argv[5]))
327
+ return null;
328
+ return { scope, action: 'file', targetId, filePath: argv[5] };
329
+ }
330
+ const fileOption = findSingleFileOption(argv, 5);
331
+ if (fileOption.invalid)
332
+ return null;
333
+ return fileOption.path
334
+ ? { scope, action: 'file', targetId, filePath: fileOption.path }
335
+ : { scope, action: 'send', targetId };
336
+ }
337
+ export function isEvolcoreSendCommandForSession(toolName, input, channelId) {
338
+ if (toolName !== 'Bash')
339
+ return false;
340
+ const cmd = typeof input.command === 'string' ? input.command : '';
341
+ const parsed = parseEvolcoreSendCommand(cmd);
342
+ if (!parsed)
343
+ return false;
344
+ if (parsed.scope === 'ctl')
345
+ return true;
346
+ return parsed.targetId === channelId;
347
+ }
@@ -0,0 +1,16 @@
1
+ import { normalizePermissionMode } from './mode.js';
2
+ /**
3
+ * Phase one only disables the task execution sandbox for an authenticated
4
+ * owner running in bypass mode. Every other combination remains fail-closed.
5
+ */
6
+ export function resolvePhaseOneExecutionSandbox(permissionMode, role) {
7
+ const normalizedMode = normalizePermissionMode(permissionMode).mode;
8
+ const normalizedRole = role ?? undefined;
9
+ const sandboxOff = normalizedMode === 'bypass' && normalizedRole === 'owner';
10
+ return {
11
+ permissionMode: normalizedMode,
12
+ role: normalizedRole,
13
+ state: sandboxOff ? 'off' : 'active',
14
+ reason: sandboxOff ? 'owner-bypass' : 'phase-one-required',
15
+ };
16
+ }
@@ -0,0 +1,6 @@
1
+ export * from './tool-policy.js';
2
+ export * from './ec-command-parser.js';
3
+ export * from './approval-gateway.js';
4
+ export * from './execution-sandbox.js';
5
+ // Tool summaries live in utils; keep the historical permission import path compatible.
6
+ export { summarizeToolInput } from '../../utils/tool-summary.js';
@@ -0,0 +1,24 @@
1
+ export const PUBLIC_PERMISSION_MODES = ['readonly', 'auto', 'request', 'bypass'];
2
+ export function isPublicPermissionMode(value) {
3
+ return typeof value === 'string'
4
+ && PUBLIC_PERMISSION_MODES.includes(value);
5
+ }
6
+ /**
7
+ * Normalize persisted legacy values at the runtime boundary.
8
+ *
9
+ * - edit had request-like approval semantics, so it migrates to request.
10
+ * - noask historically disabled prompts and is therefore safely reduced to readonly.
11
+ * - plan is a workflow state, not an authority level; permission enforcement is readonly.
12
+ * - unknown values fail safe to readonly.
13
+ */
14
+ export function normalizePermissionMode(value) {
15
+ if (isPublicPermissionMode(value))
16
+ return { mode: value };
17
+ if (value === 'edit')
18
+ return { mode: 'request', migratedFrom: 'edit' };
19
+ if (value === 'noask')
20
+ return { mode: 'readonly', migratedFrom: 'noask' };
21
+ if (value === 'plan')
22
+ return { mode: 'readonly', workflow: 'plan', migratedFrom: 'plan' };
23
+ return { mode: 'readonly' };
24
+ }
@@ -0,0 +1,265 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { resolveCommandPath } from '../../utils/cross-platform.js';
4
+ import { getExistingHClassMaskTargets, getExistingLClassReadOnlyTargets, isSameOrDescendant, } from '../protected-paths.js';
5
+ import { resolveRoot } from '../../paths.js';
6
+ let cachedBubblewrapPath;
7
+ /**
8
+ * Claude Agent SDK's native sandbox is unavailable on Windows. WSL2 reports
9
+ * itself as Linux, so it remains fail-closed there together with macOS/Linux.
10
+ */
11
+ export function shouldFailIfClaudeSandboxUnavailable(platform = process.platform) {
12
+ return platform !== 'win32';
13
+ }
14
+ function isExecutableFile(candidate) {
15
+ if (!candidate)
16
+ return false;
17
+ try {
18
+ fs.accessSync(candidate, fs.constants.X_OK);
19
+ return fs.statSync(candidate).isFile();
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ function codexBundledBubblewrap() {
26
+ if (process.platform !== 'linux')
27
+ return undefined;
28
+ const codexPath = resolveCommandPath('codex');
29
+ if (!codexPath)
30
+ return undefined;
31
+ let resolvedCodexPath = codexPath;
32
+ try {
33
+ resolvedCodexPath = fs.realpathSync(codexPath);
34
+ }
35
+ catch { }
36
+ const packageRoot = resolvedCodexPath.endsWith(`${path.sep}bin${path.sep}codex.js`)
37
+ ? path.resolve(path.dirname(resolvedCodexPath), '..')
38
+ : undefined;
39
+ if (!packageRoot)
40
+ return undefined;
41
+ const target = process.arch === 'arm64'
42
+ ? 'aarch64-unknown-linux-musl'
43
+ : 'x86_64-unknown-linux-musl';
44
+ const platformPackage = process.arch === 'arm64' ? 'codex-linux-arm64' : 'codex-linux-x64';
45
+ return path.join(packageRoot, 'node_modules', '@openai', platformPackage, 'vendor', target, 'codex-resources', 'bwrap');
46
+ }
47
+ export function resolveBubblewrapPath() {
48
+ if (cachedBubblewrapPath !== undefined)
49
+ return cachedBubblewrapPath ?? undefined;
50
+ if (process.platform !== 'linux') {
51
+ cachedBubblewrapPath = null;
52
+ return undefined;
53
+ }
54
+ const candidates = [
55
+ process.env.EVOLCORE_BWRAP_PATH,
56
+ resolveCommandPath('bwrap') ?? undefined,
57
+ codexBundledBubblewrap(),
58
+ ];
59
+ const resolved = candidates.find(isExecutableFile);
60
+ cachedBubblewrapPath = resolved ?? null;
61
+ return resolved;
62
+ }
63
+ export function prependExecutableDirectory(env, executablePath) {
64
+ const currentPath = env.PATH ?? process.env.PATH ?? '';
65
+ const directory = path.dirname(executablePath);
66
+ return {
67
+ ...env,
68
+ PATH: currentPath ? `${directory}${path.delimiter}${currentPath}` : directory,
69
+ };
70
+ }
71
+ function resolveGitDirectory(projectPath) {
72
+ const marker = path.join(path.resolve(projectPath), '.git');
73
+ try {
74
+ if (fs.statSync(marker).isDirectory())
75
+ return marker;
76
+ const match = fs.readFileSync(marker, 'utf8').match(/^gitdir:\s*(.+?)\s*$/m);
77
+ if (!match)
78
+ return undefined;
79
+ return path.resolve(path.dirname(marker), match[1]);
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ }
85
+ function resolveCommonGitDirectory(gitDirectory) {
86
+ try {
87
+ const commonDirectory = fs.readFileSync(path.join(gitDirectory, 'commondir'), 'utf8').trim();
88
+ if (commonDirectory)
89
+ return path.resolve(gitDirectory, commonDirectory);
90
+ }
91
+ catch { }
92
+ return gitDirectory;
93
+ }
94
+ function hasWorktreeConfigExtension(config) {
95
+ let section = '';
96
+ for (const rawLine of config.split(/\r?\n/)) {
97
+ const line = rawLine.trim();
98
+ if (!line || line.startsWith('#') || line.startsWith(';'))
99
+ continue;
100
+ const sectionMatch = line.match(/^\[\s*([^\]\s]+)(?:\s+"[^"]*")?\s*\]$/);
101
+ if (sectionMatch) {
102
+ section = sectionMatch[1].toLowerCase();
103
+ continue;
104
+ }
105
+ if (section !== 'extensions')
106
+ continue;
107
+ const settingMatch = line.match(/^worktreeconfig(?:\s*=\s*(.*?))?\s*(?:[#;].*)?$/i);
108
+ if (!settingMatch)
109
+ continue;
110
+ const value = (settingMatch[1] ?? 'true').trim().toLowerCase();
111
+ return ['true', 'yes', 'on', '1'].includes(value);
112
+ }
113
+ return false;
114
+ }
115
+ export function ensureClaudeGitWorktreeConfig(projectPath) {
116
+ const gitDirectory = resolveGitDirectory(projectPath);
117
+ if (!gitDirectory)
118
+ return undefined;
119
+ const commonDirectory = resolveCommonGitDirectory(gitDirectory);
120
+ let config;
121
+ try {
122
+ config = fs.readFileSync(path.join(commonDirectory, 'config'), 'utf8');
123
+ }
124
+ catch {
125
+ return undefined;
126
+ }
127
+ if (!hasWorktreeConfigExtension(config))
128
+ return undefined;
129
+ const worktreeConfig = path.join(gitDirectory, 'config.worktree');
130
+ if (fs.existsSync(worktreeConfig))
131
+ return worktreeConfig;
132
+ try {
133
+ fs.writeFileSync(worktreeConfig, '', { flag: 'wx', mode: 0o600 });
134
+ }
135
+ catch (error) {
136
+ if (error.code !== 'EEXIST')
137
+ throw error;
138
+ }
139
+ return worktreeConfig;
140
+ }
141
+ function pushWritableBind(args, candidate) {
142
+ if (!fs.existsSync(candidate))
143
+ return;
144
+ args.push('--bind', candidate, candidate);
145
+ }
146
+ function pushMaskedFile(args, candidate) {
147
+ if (!fs.existsSync(candidate))
148
+ return;
149
+ args.push('--ro-bind', '/dev/null', candidate);
150
+ }
151
+ function pushMaskedDirectory(args, candidate) {
152
+ if (!fs.existsSync(candidate))
153
+ return;
154
+ args.push('--tmpfs', candidate);
155
+ }
156
+ function appendRuntimeBackedEtcFiles(args) {
157
+ if (process.platform !== 'linux' || !fs.existsSync('/run'))
158
+ return;
159
+ let runtimeRoot = '/run';
160
+ try {
161
+ runtimeRoot = fs.realpathSync('/run');
162
+ }
163
+ catch { }
164
+ const createdDirectories = new Set();
165
+ for (const candidate of ['/etc/resolv.conf', '/etc/hosts', '/etc/hostname']) {
166
+ let resolved;
167
+ try {
168
+ resolved = fs.realpathSync(candidate);
169
+ }
170
+ catch {
171
+ continue;
172
+ }
173
+ if (resolved === runtimeRoot || !isSameOrDescendant(resolved, runtimeRoot))
174
+ continue;
175
+ const directories = [];
176
+ let current = path.dirname(resolved);
177
+ while (current !== runtimeRoot && isSameOrDescendant(current, runtimeRoot)) {
178
+ directories.push(current);
179
+ current = path.dirname(current);
180
+ }
181
+ for (const directory of directories.reverse()) {
182
+ if (createdDirectories.has(directory))
183
+ continue;
184
+ createdDirectories.add(directory);
185
+ args.push('--dir', directory);
186
+ }
187
+ // Bubblewrap opens bind sources before applying the new mount layout. This
188
+ // preserves only the exact runtime-backed /etc file after /run is replaced,
189
+ // without re-exposing sibling service or container-runtime sockets.
190
+ args.push('--ro-bind', candidate, resolved);
191
+ }
192
+ }
193
+ function appendHClassMasks(args, root, maskContainerSockets) {
194
+ for (const target of getExistingHClassMaskTargets(root)) {
195
+ if (target.kind === 'directory')
196
+ pushMaskedDirectory(args, target.path);
197
+ else
198
+ pushMaskedFile(args, target.path);
199
+ }
200
+ if (!maskContainerSockets)
201
+ return;
202
+ // bubblewrap cannot replace an existing Unix socket with a regular-file bind
203
+ // on all kernels. A private /run hides Docker, Podman, and user runtime
204
+ // sockets without disabling ordinary network access needed by web tools.
205
+ pushMaskedDirectory(args, '/run');
206
+ appendRuntimeBackedEtcFiles(args);
207
+ }
208
+ function appendLClassReadOnlyBinds(args, root) {
209
+ for (const target of getExistingLClassReadOnlyTargets(root)) {
210
+ args.push('--ro-bind', target.path, target.path);
211
+ }
212
+ }
213
+ export function buildHClassGuardCommand(executable, executableArgs, root = resolveRoot()) {
214
+ const bubblewrapPath = resolveBubblewrapPath();
215
+ if (!bubblewrapPath)
216
+ return undefined;
217
+ const args = [
218
+ '--die-with-parent',
219
+ '--new-session',
220
+ '--unshare-pid',
221
+ '--bind', '/', '/',
222
+ '--dev-bind', '/dev', '/dev',
223
+ '--proc', '/proc',
224
+ ];
225
+ appendLClassReadOnlyBinds(args, path.resolve(root));
226
+ appendHClassMasks(args, path.resolve(root), true);
227
+ args.push('--', executable, ...executableArgs);
228
+ return { command: bubblewrapPath, args };
229
+ }
230
+ export function buildBubblewrapCommand(executable, executableArgs, options) {
231
+ const bubblewrapPath = resolveBubblewrapPath();
232
+ if (!bubblewrapPath)
233
+ return undefined;
234
+ const projectPath = path.resolve(options.projectPath);
235
+ const root = path.resolve(options.root ?? resolveRoot());
236
+ const args = [
237
+ '--die-with-parent',
238
+ '--new-session',
239
+ '--unshare-pid',
240
+ '--ro-bind', '/', '/',
241
+ '--dev-bind', '/dev', '/dev',
242
+ '--proc', '/proc',
243
+ '--tmpfs', '/tmp',
244
+ ];
245
+ pushWritableBind(args, projectPath);
246
+ for (const writablePath of options.writablePaths ?? []) {
247
+ const resolved = path.resolve(writablePath);
248
+ if (isSameOrDescendant(resolved, projectPath))
249
+ continue;
250
+ pushWritableBind(args, resolved);
251
+ }
252
+ for (const readonlyPath of options.readonlyPaths ?? []) {
253
+ const resolved = path.resolve(readonlyPath);
254
+ if (!fs.existsSync(resolved))
255
+ continue;
256
+ args.push('--ro-bind', resolved, resolved);
257
+ }
258
+ appendLClassReadOnlyBinds(args, root);
259
+ appendHClassMasks(args, root, true);
260
+ args.push('--chdir', projectPath, '--', executable, ...executableArgs);
261
+ return { command: bubblewrapPath, args };
262
+ }
263
+ export function _resetSandboxRuntimeCache() {
264
+ cachedBubblewrapPath = undefined;
265
+ }