@ouro.bot/cli 0.1.0-alpha.99 → 0.1.0-alpha.991

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 (777) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -23
  3. package/RepairGuide.ouro/agent.json +5 -0
  4. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  5. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  6. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  7. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  8. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  9. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  12. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  13. package/assets/bluebubbles-host +264 -0
  14. package/assets/sanctuary-host-launcher.sh +16 -0
  15. package/changelog.json +6257 -16
  16. package/deploy/unraid/Dockerfile +71 -0
  17. package/deploy/unraid/INSTALL.md +64 -0
  18. package/deploy/unraid/README.txt +3032 -0
  19. package/deploy/unraid/audit-container-spec.sh +4 -0
  20. package/deploy/unraid/container-runtime.json +1 -0
  21. package/deploy/unraid/credit-gateway.md +95 -0
  22. package/deploy/unraid/docker-man-template-transaction.mjs +727 -0
  23. package/deploy/unraid/docker-man-template-xml.cjs +105 -0
  24. package/deploy/unraid/github_known_hosts +3 -0
  25. package/deploy/unraid/install.sh +274 -0
  26. package/deploy/unraid/migrate-sanctuary-bundle.mjs +63 -0
  27. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  28. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  29. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  30. package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
  31. package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
  32. package/deploy/unraid/ouro-sync-ssh +98 -0
  33. package/deploy/unraid/ouro-sync.sudoers +3 -0
  34. package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
  35. package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
  36. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  37. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  38. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  39. package/deploy/unraid/sanctuary-butler-upgrade.mjs +1523 -0
  40. package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
  41. package/deploy/unraid/sanctuary-replay-gate.mjs +2467 -0
  42. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  43. package/deploy/unraid/sanctuary-sync-cutover.sh +1039 -0
  44. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2088 -0
  45. package/deploy/unraid/sanctuary-unit16-run.sh +570 -0
  46. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  47. package/deploy/unraid/sanctuary.ouro/agent.json +67 -0
  48. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  49. package/deploy/unraid/sanctuary.ouro/awaiting/house-care-sweep.md +17 -0
  50. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  51. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  52. package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +2255 -0
  53. package/deploy/unraid/sanctuary.ouro/product/PRODUCT.md +13 -0
  54. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
  55. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
  56. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +7 -0
  57. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +7 -0
  58. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +15 -0
  59. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +3 -0
  60. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  61. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +234 -0
  62. package/deploy/unraid/sanctuary.xml +30 -0
  63. package/dist/a2a/card.js +57 -0
  64. package/dist/a2a/client.js +290 -0
  65. package/dist/a2a/config.js +50 -0
  66. package/dist/a2a/delegated-command-grants.js +93 -0
  67. package/dist/a2a/delegated-command.js +197 -0
  68. package/dist/a2a/delegation-stores.js +56 -0
  69. package/dist/a2a/did-resolution.js +205 -0
  70. package/dist/a2a/escalation-grants.js +84 -0
  71. package/dist/a2a/escalation-wiring.js +60 -0
  72. package/dist/a2a/identity.js +258 -0
  73. package/dist/a2a/inbound-share.js +131 -0
  74. package/dist/a2a/mission-result-wire.js +265 -0
  75. package/dist/a2a/onboarding.js +133 -0
  76. package/dist/a2a/operator-trust.js +399 -0
  77. package/dist/a2a/outbox-client.js +59 -0
  78. package/dist/a2a/outbox-store.js +188 -0
  79. package/dist/a2a/outbox-wire.js +106 -0
  80. package/dist/a2a/pin-store.js +167 -0
  81. package/dist/a2a/refusals.js +53 -0
  82. package/dist/a2a/relay-client.js +106 -0
  83. package/dist/a2a/relay-config-vault.js +64 -0
  84. package/dist/a2a/relay-contacts.js +120 -0
  85. package/dist/a2a/relay-inbox.js +360 -0
  86. package/dist/a2a/relay-retirements.js +89 -0
  87. package/dist/a2a/replay-grant-holders.js +33 -0
  88. package/dist/a2a/replay-harness.js +188 -0
  89. package/dist/a2a/resolution-proof.js +53 -0
  90. package/dist/a2a/sealed-chat.js +114 -0
  91. package/dist/a2a/seen-ledger.js +214 -0
  92. package/dist/a2a/server.js +808 -0
  93. package/dist/a2a/task-store.js +69 -0
  94. package/dist/a2a/transport.js +100 -0
  95. package/dist/a2a/trust-commands.js +203 -0
  96. package/dist/a2a/trusted-files.js +185 -0
  97. package/dist/a2a/types.js +3 -0
  98. package/dist/arc/attention-types.js +8 -0
  99. package/dist/arc/cares.js +360 -0
  100. package/dist/arc/episodes.js +118 -0
  101. package/dist/arc/evolution.js +490 -0
  102. package/dist/arc/flight-recorder.js +1034 -0
  103. package/dist/arc/intentions.js +134 -0
  104. package/dist/arc/json-store.js +117 -0
  105. package/dist/arc/obligations.js +386 -0
  106. package/dist/arc/packets.js +336 -0
  107. package/dist/arc/presence.js +185 -0
  108. package/dist/arc/task-lifecycle.js +57 -0
  109. package/dist/commerce/store.js +755 -0
  110. package/dist/commerce/types.js +3 -0
  111. package/dist/gateway/keys.js +117 -0
  112. package/dist/gateway/server.js +429 -0
  113. package/dist/heart/active-work.js +798 -40
  114. package/dist/heart/agent-entry.js +252 -3
  115. package/dist/heart/approval-files.js +113 -0
  116. package/dist/heart/approval-store.js +960 -0
  117. package/dist/heart/attachments/image-normalize.js +201 -0
  118. package/dist/heart/attachments/materialize.js +97 -0
  119. package/dist/heart/attachments/originals.js +88 -0
  120. package/dist/heart/attachments/render.js +29 -0
  121. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  122. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  123. package/dist/heart/attachments/sources/index.js +18 -0
  124. package/dist/heart/attachments/sources/telegram.js +71 -0
  125. package/dist/heart/attachments/store.js +132 -0
  126. package/dist/heart/attachments/types.js +93 -0
  127. package/dist/heart/auth/auth-flow.js +511 -0
  128. package/dist/heart/autonomy-budget.js +478 -0
  129. package/dist/heart/awaiting/a2a-await-delivery.js +109 -0
  130. package/dist/heart/awaiting/await-alert.js +152 -0
  131. package/dist/heart/awaiting/await-expiry.js +143 -0
  132. package/dist/heart/awaiting/await-loader.js +91 -0
  133. package/dist/heart/awaiting/await-parser.js +152 -0
  134. package/dist/heart/awaiting/await-runtime-state.js +130 -0
  135. package/dist/heart/awaiting/await-scheduler.js +391 -0
  136. package/dist/heart/background-operations.js +281 -0
  137. package/dist/heart/bluebubbles-host-account.js +78 -0
  138. package/dist/heart/bridges/manager.js +137 -17
  139. package/dist/heart/bridges/store.js +14 -2
  140. package/dist/heart/bundle-state.js +168 -0
  141. package/dist/heart/commitments.js +44 -18
  142. package/dist/heart/config-registry.js +331 -0
  143. package/dist/heart/config.js +179 -131
  144. package/dist/heart/context-loss-gauntlet.js +387 -0
  145. package/dist/heart/context-loss-sentinel.js +1204 -0
  146. package/dist/heart/core.js +2378 -586
  147. package/dist/heart/cross-chat-delivery.js +9 -22
  148. package/dist/heart/daemon/agent-config-check.js +474 -0
  149. package/dist/heart/daemon/agent-discovery.js +287 -3
  150. package/dist/heart/daemon/agent-home.js +355 -0
  151. package/dist/heart/daemon/agent-service.js +542 -0
  152. package/dist/heart/daemon/agentic-repair.js +547 -0
  153. package/dist/heart/daemon/await-private-wake.js +59 -0
  154. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  155. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  156. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  157. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  158. package/dist/heart/daemon/boot-sync-probe.js +223 -0
  159. package/dist/heart/daemon/cadence.js +180 -0
  160. package/dist/heart/daemon/cli-arg-redaction.js +26 -0
  161. package/dist/heart/daemon/cli-defaults.js +881 -0
  162. package/dist/heart/daemon/cli-desk.js +331 -0
  163. package/dist/heart/daemon/cli-exec.js +9339 -0
  164. package/dist/heart/daemon/cli-help.js +766 -0
  165. package/dist/heart/daemon/cli-parse.js +3343 -0
  166. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  167. package/dist/heart/daemon/cli-render.js +819 -0
  168. package/dist/heart/daemon/cli-types.js +8 -0
  169. package/dist/heart/daemon/connect-bay.js +323 -0
  170. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  171. package/dist/heart/daemon/container-healthcheck.js +112 -0
  172. package/dist/heart/daemon/container-runtime.js +97 -0
  173. package/dist/heart/daemon/container-spec-auditor-main.js +187 -0
  174. package/dist/heart/daemon/container-spec-auditor.js +257 -0
  175. package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
  176. package/dist/heart/daemon/daemon-cli.js +29 -2373
  177. package/dist/heart/daemon/daemon-entry.js +975 -11
  178. package/dist/heart/daemon/daemon-health.js +186 -0
  179. package/dist/heart/daemon/daemon-rollup.js +57 -0
  180. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  181. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  182. package/dist/heart/daemon/daemon.js +2110 -104
  183. package/dist/heart/daemon/desk-admin.js +420 -0
  184. package/dist/heart/daemon/dns-workflow.js +394 -0
  185. package/dist/heart/daemon/doctor-types.js +8 -0
  186. package/dist/heart/daemon/doctor.js +1766 -0
  187. package/dist/heart/daemon/freshness.js +345 -0
  188. package/dist/heart/daemon/habit-private-wake.js +61 -0
  189. package/dist/heart/daemon/health-monitor.js +154 -11
  190. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  191. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  192. package/dist/heart/daemon/http-health-probe.js +80 -0
  193. package/dist/heart/daemon/human-command-screens.js +234 -0
  194. package/dist/heart/daemon/human-readiness.js +114 -0
  195. package/dist/heart/daemon/inner-status.js +78 -0
  196. package/dist/heart/daemon/interactive-repair.js +394 -0
  197. package/dist/heart/daemon/launchd.js +37 -8
  198. package/dist/heart/daemon/log-tailer.js +79 -10
  199. package/dist/heart/daemon/logs-prune.js +132 -0
  200. package/dist/heart/daemon/mcp-canary.js +524 -0
  201. package/dist/heart/daemon/message-router.js +65 -5
  202. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  203. package/dist/heart/daemon/os-cron-deps.js +135 -0
  204. package/dist/heart/daemon/os-cron.js +22 -14
  205. package/dist/heart/daemon/ouro-bot-entry.js +6 -3
  206. package/dist/heart/daemon/ouro-entry.js +9 -3
  207. package/dist/heart/daemon/plugin-cli.js +432 -0
  208. package/dist/heart/daemon/process-manager.js +760 -61
  209. package/dist/heart/daemon/provider-discovery.js +137 -0
  210. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  211. package/dist/heart/daemon/prunable-bundle.js +144 -0
  212. package/dist/heart/daemon/pulse.js +511 -0
  213. package/dist/heart/daemon/readiness-repair.js +365 -0
  214. package/dist/heart/daemon/run-hooks.js +2 -0
  215. package/dist/heart/daemon/runtime-logging.js +47 -14
  216. package/dist/heart/daemon/runtime-metadata.js +2 -30
  217. package/dist/heart/daemon/runtime-mode.js +13 -2
  218. package/dist/heart/daemon/safe-mode.js +161 -0
  219. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2662 -0
  220. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1832 -0
  221. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  222. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +956 -0
  223. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  224. package/dist/heart/daemon/sanctuary-authority-epoch.js +291 -0
  225. package/dist/heart/daemon/sanctuary-authority-installation.js +161 -0
  226. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  227. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +1332 -0
  228. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  229. package/dist/heart/daemon/sanctuary-bundle-migration.js +974 -0
  230. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  231. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +493 -0
  232. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  233. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  234. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  235. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  236. package/dist/heart/daemon/sanctuary-package-management.js +113 -0
  237. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  238. package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
  239. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +475 -0
  240. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  241. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +676 -0
  242. package/dist/heart/daemon/sense-manager.js +786 -48
  243. package/dist/heart/daemon/session-id-resolver.js +131 -0
  244. package/dist/heart/daemon/skill-management-installer.js +1 -1
  245. package/dist/heart/daemon/socket-client.js +180 -12
  246. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  247. package/dist/heart/daemon/startup-tui.js +339 -0
  248. package/dist/heart/daemon/supercronic-supervisor.js +282 -0
  249. package/dist/heart/daemon/task-scheduler.js +117 -39
  250. package/dist/heart/daemon/terminal-ui.js +503 -0
  251. package/dist/heart/daemon/thoughts.js +138 -54
  252. package/dist/heart/daemon/up-progress.js +366 -0
  253. package/dist/heart/daemon/vault-items.js +56 -0
  254. package/dist/heart/delegation.js +1 -4
  255. package/dist/heart/external-events/router.js +1531 -0
  256. package/dist/heart/failure-reports.js +336 -0
  257. package/dist/heart/file-lock.js +109 -0
  258. package/dist/heart/frontend-approval-runtime.js +506 -0
  259. package/dist/heart/frontend-journal.js +215 -0
  260. package/dist/heart/frontend-session-service.js +271 -0
  261. package/dist/heart/frontend-socket-client.js +173 -0
  262. package/dist/heart/frontend-socket.js +434 -0
  263. package/dist/heart/frontend-turn-errors.js +20 -0
  264. package/dist/heart/habits/habit-cancel.js +810 -0
  265. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  266. package/dist/heart/habits/habit-migration.js +194 -0
  267. package/dist/heart/habits/habit-parser.js +278 -0
  268. package/dist/heart/habits/habit-runtime-state.js +93 -0
  269. package/dist/heart/habits/habit-scheduler.js +653 -0
  270. package/dist/heart/habits/habit-session-summary.js +318 -0
  271. package/dist/heart/habits/habit-session.js +636 -0
  272. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  273. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  274. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  275. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  276. package/dist/heart/identity.js +239 -59
  277. package/dist/heart/kept-notes.js +289 -0
  278. package/dist/heart/kicks.js +1 -1
  279. package/dist/heart/machine-identity.js +175 -0
  280. package/dist/heart/mail-import-discovery.js +356 -0
  281. package/dist/heart/mailbox/mailbox-http-hooks.js +99 -0
  282. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  283. package/dist/heart/mailbox/mailbox-http-routes.js +406 -0
  284. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  285. package/dist/heart/mailbox/mailbox-http-transport.js +272 -0
  286. package/dist/heart/mailbox/mailbox-http.js +99 -0
  287. package/dist/heart/mailbox/mailbox-read.js +41 -0
  288. package/dist/heart/mailbox/mailbox-supervision.js +131 -0
  289. package/dist/heart/mailbox/mailbox-types.js +27 -0
  290. package/dist/heart/mailbox/mailbox-view.js +197 -0
  291. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  292. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  293. package/dist/heart/mailbox/readers/mail.js +375 -0
  294. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  295. package/dist/heart/mailbox/readers/sessions.js +231 -0
  296. package/dist/heart/mailbox/readers/shared.js +111 -0
  297. package/dist/heart/mcp/mcp-server.js +760 -0
  298. package/dist/heart/migrate-config.js +100 -0
  299. package/dist/heart/model-capabilities.js +83 -16
  300. package/dist/heart/orientation-frame.js +376 -0
  301. package/dist/heart/platform.js +81 -0
  302. package/dist/heart/primitives.js +1 -1
  303. package/dist/heart/private-runtime/decision-renderer.js +158 -0
  304. package/dist/heart/private-runtime/index.js +18 -0
  305. package/dist/heart/private-runtime/ledger.js +221 -0
  306. package/dist/heart/private-runtime/policy.js +260 -0
  307. package/dist/heart/private-runtime/types.js +2 -0
  308. package/dist/heart/progress-story.js +1 -1
  309. package/dist/heart/provider-attempt.js +137 -0
  310. package/dist/heart/provider-binding-resolver.js +273 -0
  311. package/dist/heart/provider-credentials.js +449 -0
  312. package/dist/heart/provider-failover.js +311 -0
  313. package/dist/heart/provider-models.js +96 -0
  314. package/dist/heart/provider-ping.js +266 -0
  315. package/dist/heart/provider-readiness-cache.js +119 -0
  316. package/dist/heart/provider-visibility.js +188 -0
  317. package/dist/heart/providers/anthropic-token.js +131 -0
  318. package/dist/heart/providers/anthropic.js +248 -67
  319. package/dist/heart/providers/azure.js +23 -11
  320. package/dist/heart/providers/error-classification.js +129 -0
  321. package/dist/heart/providers/github-copilot.js +38 -39
  322. package/dist/heart/providers/minimax-vlm.js +189 -0
  323. package/dist/heart/providers/minimax.js +26 -8
  324. package/dist/heart/providers/openai-codex-token.js +349 -0
  325. package/dist/heart/providers/openai-codex.js +55 -40
  326. package/dist/heart/providers/openai-compatible.js +177 -0
  327. package/dist/heart/reach-channel.js +85 -0
  328. package/dist/heart/reply-style.js +52 -0
  329. package/dist/heart/run-ledger.js +247 -0
  330. package/dist/heart/runtime-capability-check.js +170 -0
  331. package/dist/heart/runtime-credentials.js +608 -0
  332. package/dist/heart/runtime-cwd.js +87 -0
  333. package/dist/heart/sense-truth.js +24 -4
  334. package/dist/heart/session-activity.js +94 -24
  335. package/dist/heart/session-events.js +1521 -0
  336. package/dist/heart/session-playback-cli-main.js +5 -0
  337. package/dist/heart/session-playback-cli.js +36 -0
  338. package/dist/heart/session-playback.js +231 -0
  339. package/dist/heart/session-redaction-repair-cli-main.js +31 -0
  340. package/dist/heart/session-redaction-repair.js +552 -0
  341. package/dist/heart/session-stats-cli-main.js +5 -0
  342. package/dist/heart/session-stats.js +183 -0
  343. package/dist/heart/session-transcript.js +133 -0
  344. package/dist/heart/shelf-claims.js +78 -0
  345. package/dist/heart/shepherd/custody-types.js +2 -0
  346. package/dist/heart/shepherd/mandates.js +352 -0
  347. package/dist/heart/shepherd/operation-commands.js +19 -0
  348. package/dist/heart/shepherd/operations.js +135 -0
  349. package/dist/heart/shepherd/supervision.js +114 -0
  350. package/dist/heart/shepherd/types.js +2 -0
  351. package/dist/heart/shepherd-config.js +123 -0
  352. package/dist/heart/source-grounding.js +313 -0
  353. package/dist/heart/start-of-turn-packet.js +372 -0
  354. package/dist/heart/steward-policy.js +656 -0
  355. package/dist/heart/streaming.js +631 -207
  356. package/dist/heart/structured-output.js +196 -0
  357. package/dist/heart/sync-classification.js +176 -0
  358. package/dist/heart/sync-health.js +152 -0
  359. package/dist/heart/sync-lock.js +431 -0
  360. package/dist/heart/sync.js +1579 -0
  361. package/dist/heart/target-resolution.js +11 -7
  362. package/dist/heart/tempo.js +93 -0
  363. package/dist/heart/temporal-view.js +41 -0
  364. package/dist/heart/timeouts.js +101 -0
  365. package/dist/heart/tool-activity-callbacks.js +59 -0
  366. package/dist/heart/tool-approval.js +382 -0
  367. package/dist/heart/tool-description.js +155 -0
  368. package/dist/heart/tool-friction.js +55 -0
  369. package/dist/heart/tool-loop.js +206 -0
  370. package/dist/heart/tool-output-budget.js +41 -0
  371. package/dist/heart/turn-context.js +496 -0
  372. package/dist/heart/turn-execution-lease.js +30 -0
  373. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  374. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +3 -2
  375. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  376. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  377. package/dist/heart/versioning/ouro-version-manager.js +432 -0
  378. package/dist/heart/versioning/release-sources.js +173 -0
  379. package/dist/heart/versioning/self-update-defaults.js +294 -0
  380. package/dist/heart/versioning/self-update.js +196 -0
  381. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  382. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  383. package/dist/heart/versioning/update-hooks.js +154 -0
  384. package/dist/heart/versioning/update-state.js +126 -0
  385. package/dist/heart/versioning/updater-launchd.js +150 -0
  386. package/dist/heart/versioning/version-intent.js +114 -0
  387. package/dist/heart/work-card.js +380 -0
  388. package/dist/mailbox-ui/assets/index-Dbska1tZ.js +1 -0
  389. package/dist/mailbox-ui/assets/index-DsiKaPyH.css +1 -0
  390. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  391. package/dist/mailbox-ui/index.html +16 -0
  392. package/dist/mailroom/attention.js +167 -0
  393. package/dist/mailroom/autonomy.js +209 -0
  394. package/dist/mailroom/blob-store.js +889 -0
  395. package/dist/mailroom/body-cache.js +61 -0
  396. package/dist/mailroom/cache-sync-cli.js +58 -0
  397. package/dist/mailroom/core.js +803 -0
  398. package/dist/mailroom/entry.js +160 -0
  399. package/dist/mailroom/file-store.js +568 -0
  400. package/dist/mailroom/hosted-cache-sync.js +384 -0
  401. package/dist/mailroom/mbox-import.js +393 -0
  402. package/dist/mailroom/migration.js +164 -0
  403. package/dist/mailroom/outbound.js +380 -0
  404. package/dist/mailroom/policy.js +263 -0
  405. package/dist/mailroom/reader.js +292 -0
  406. package/dist/mailroom/search-cache.js +513 -0
  407. package/dist/mailroom/search-relevance.js +319 -0
  408. package/dist/mailroom/smtp-ingress.js +176 -0
  409. package/dist/mailroom/source-state.js +176 -0
  410. package/dist/mailroom/thread.js +109 -0
  411. package/dist/mailroom/travel-extract.js +89 -0
  412. package/dist/mind/bundle-manifest.js +25 -2
  413. package/dist/mind/context.js +328 -139
  414. package/dist/mind/desk-section.js +362 -0
  415. package/dist/mind/diary-integrity.js +60 -0
  416. package/dist/mind/{memory.js → diary.js} +68 -77
  417. package/dist/mind/embedding-provider.js +60 -0
  418. package/dist/mind/file-state.js +179 -0
  419. package/dist/mind/friend-link-codes.js +209 -0
  420. package/dist/mind/friend-merge.js +336 -0
  421. package/dist/mind/house-names.js +43 -0
  422. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  423. package/dist/mind/obligation-steering.js +199 -3
  424. package/dist/mind/outbound-record.js +168 -0
  425. package/dist/mind/pending.js +36 -10
  426. package/dist/mind/prompt-budget.js +479 -0
  427. package/dist/mind/prompt-refresh.js +3 -2
  428. package/dist/mind/prompt.js +1222 -203
  429. package/dist/mind/provenance-trust.js +26 -0
  430. package/dist/mind/record-paths.js +312 -0
  431. package/dist/mind/scrutiny.js +173 -0
  432. package/dist/mind/session-transaction.js +622 -0
  433. package/dist/nerves/cli-logging.js +7 -1
  434. package/dist/nerves/coverage/audit-rules.js +15 -6
  435. package/dist/nerves/coverage/audit.js +28 -2
  436. package/dist/nerves/coverage/cli.js +1 -1
  437. package/dist/nerves/coverage/contract.js +5 -5
  438. package/dist/nerves/coverage/file-completeness.js +143 -5
  439. package/dist/nerves/coverage/run-artifacts.js +55 -35
  440. package/dist/nerves/coverage/source-scanner.js +4 -4
  441. package/dist/nerves/event-buffer.js +111 -0
  442. package/dist/nerves/index.js +293 -9
  443. package/dist/nerves/observation.js +20 -0
  444. package/dist/nerves/redact.js +79 -0
  445. package/dist/nerves/review/cli-main.js +5 -0
  446. package/dist/nerves/review/cli.js +156 -0
  447. package/dist/nerves/review/core.js +159 -0
  448. package/dist/nerves/runtime.js +17 -1
  449. package/dist/repertoire/ado-client.js +15 -56
  450. package/dist/repertoire/ado-semantic.js +16 -10
  451. package/dist/repertoire/api-client.js +97 -0
  452. package/dist/repertoire/bitwarden-store.js +1199 -0
  453. package/dist/repertoire/bundle-templates.js +71 -0
  454. package/dist/repertoire/bw-installer.js +231 -0
  455. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  456. package/dist/repertoire/coding/context-pack.js +331 -0
  457. package/dist/repertoire/coding/feedback.js +146 -34
  458. package/dist/repertoire/coding/index.js +15 -3
  459. package/dist/repertoire/coding/manager.js +159 -10
  460. package/dist/repertoire/coding/spawner.js +40 -11
  461. package/dist/repertoire/coding/tools.js +182 -18
  462. package/dist/repertoire/coding/workbench-client.js +272 -0
  463. package/dist/repertoire/coding/workbench-manager.js +346 -0
  464. package/dist/repertoire/commerce-errors.js +109 -0
  465. package/dist/repertoire/commerce-self-test.js +156 -0
  466. package/dist/repertoire/credential-access.js +183 -0
  467. package/dist/repertoire/desk/classifier.js +362 -0
  468. package/dist/repertoire/duffel-client.js +185 -0
  469. package/dist/repertoire/github-client.js +14 -55
  470. package/dist/repertoire/graph-client.js +11 -52
  471. package/dist/repertoire/guardrails.js +1480 -22
  472. package/dist/repertoire/house-sweep.js +471 -0
  473. package/dist/repertoire/mcp-client.js +58 -5
  474. package/dist/repertoire/mcp-manager.js +427 -139
  475. package/dist/repertoire/mcp-tools.js +132 -0
  476. package/dist/repertoire/mcp-write-guard.js +46 -0
  477. package/dist/repertoire/plugin-mcp.js +175 -0
  478. package/dist/repertoire/plugins.js +253 -0
  479. package/dist/repertoire/relationship-authorization.js +322 -0
  480. package/dist/repertoire/sanctuary-container-policy.js +50 -0
  481. package/dist/repertoire/shell-sessions.js +134 -0
  482. package/dist/repertoire/skills.js +48 -4
  483. package/dist/repertoire/stripe-client.js +131 -0
  484. package/dist/repertoire/tool-arguments.js +182 -0
  485. package/dist/repertoire/tool-results.js +29 -0
  486. package/dist/repertoire/tools-a2a.js +742 -0
  487. package/dist/repertoire/tools-attachments.js +322 -0
  488. package/dist/repertoire/tools-awaiting.js +897 -0
  489. package/dist/repertoire/tools-base.js +83 -1168
  490. package/dist/repertoire/tools-bluebubbles.js +2 -0
  491. package/dist/repertoire/tools-bridge.js +144 -0
  492. package/dist/repertoire/tools-bundle.js +917 -0
  493. package/dist/repertoire/tools-commerce.js +253 -0
  494. package/dist/repertoire/tools-config.js +186 -0
  495. package/dist/repertoire/tools-continuity.js +588 -0
  496. package/dist/repertoire/tools-credential.js +383 -0
  497. package/dist/repertoire/tools-escalation.js +84 -0
  498. package/dist/repertoire/tools-evolution.js +527 -0
  499. package/dist/repertoire/tools-files.js +362 -0
  500. package/dist/repertoire/tools-flight.js +290 -0
  501. package/dist/repertoire/tools-flow.js +119 -0
  502. package/dist/repertoire/tools-friend-link.js +57 -0
  503. package/dist/repertoire/tools-github.js +3 -8
  504. package/dist/repertoire/tools-habits.js +103 -0
  505. package/dist/repertoire/tools-house-care.js +174 -0
  506. package/dist/repertoire/tools-mail.js +1828 -0
  507. package/dist/repertoire/tools-notes.js +501 -0
  508. package/dist/repertoire/tools-obligations.js +182 -0
  509. package/dist/repertoire/tools-orientation.js +31 -0
  510. package/dist/repertoire/tools-record.js +482 -0
  511. package/dist/repertoire/tools-rsvp.js +139 -0
  512. package/dist/repertoire/tools-runtime.js +238 -0
  513. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  514. package/dist/repertoire/tools-session.js +1141 -0
  515. package/dist/repertoire/tools-shell.js +154 -0
  516. package/dist/repertoire/tools-shepherd-guidance.js +171 -0
  517. package/dist/repertoire/tools-shepherd.js +325 -0
  518. package/dist/repertoire/tools-steward-policy.js +147 -0
  519. package/dist/repertoire/tools-stripe.js +224 -0
  520. package/dist/repertoire/tools-surface.js +369 -0
  521. package/dist/repertoire/tools-teams.js +50 -39
  522. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  523. package/dist/repertoire/tools-travel.js +125 -0
  524. package/dist/repertoire/tools-trip.js +982 -0
  525. package/dist/repertoire/tools-unraid.js +556 -0
  526. package/dist/repertoire/tools-user-profile.js +146 -0
  527. package/dist/repertoire/tools-vault.js +40 -0
  528. package/dist/repertoire/tools-voice-local.js +198 -0
  529. package/dist/repertoire/tools-voice.js +145 -0
  530. package/dist/repertoire/tools.js +523 -156
  531. package/dist/repertoire/travel-api-client.js +360 -0
  532. package/dist/repertoire/unraid-client.js +155 -0
  533. package/dist/repertoire/unraid-restart.js +339 -0
  534. package/dist/repertoire/user-profile.js +131 -0
  535. package/dist/repertoire/vault-setup.js +246 -0
  536. package/dist/repertoire/vault-unlock.js +686 -0
  537. package/dist/rsvp/aisleplanner-client.js +354 -0
  538. package/dist/rsvp/cli.js +936 -0
  539. package/dist/rsvp/config.js +327 -0
  540. package/dist/rsvp/cutover.js +453 -0
  541. package/dist/rsvp/diagnostics.js +232 -0
  542. package/dist/rsvp/diff-renderer.js +176 -0
  543. package/dist/rsvp/habit-policy.js +110 -0
  544. package/dist/rsvp/habit-stage.js +123 -0
  545. package/dist/rsvp/incident-bundle.js +97 -0
  546. package/dist/rsvp/migration.js +204 -0
  547. package/dist/rsvp/native-habit-runner.js +630 -0
  548. package/dist/rsvp/outbound-state.js +628 -0
  549. package/dist/rsvp/query.js +118 -0
  550. package/dist/rsvp/replay.js +233 -0
  551. package/dist/rsvp/snapshot.js +232 -0
  552. package/dist/rsvp/spend-ledger.js +175 -0
  553. package/dist/scripts/claude-code-hook.js +41 -0
  554. package/dist/scripts/claude-code-stop-hook.js +47 -0
  555. package/dist/senses/a2a-entry.js +140 -0
  556. package/dist/senses/acp-server.js +386 -0
  557. package/dist/senses/attention-queue.js +186 -0
  558. package/dist/senses/await-turn-message.js +65 -0
  559. package/dist/senses/bluebubbles/active-turns.js +216 -0
  560. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  561. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  562. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +393 -43
  563. package/dist/senses/bluebubbles/context-packet.js +207 -0
  564. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  565. package/dist/senses/bluebubbles/entry.js +90 -0
  566. package/dist/senses/bluebubbles/host-account-verifier.js +88 -0
  567. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  568. package/dist/senses/bluebubbles/index.js +4054 -0
  569. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  570. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  571. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  572. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  573. package/dist/senses/bluebubbles/outbound-state.js +335 -0
  574. package/dist/senses/bluebubbles/processed-log.js +133 -0
  575. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  576. package/dist/senses/bluebubbles/replay.js +284 -0
  577. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  578. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  579. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  580. package/dist/senses/bluebubbles/webhook-registration.js +257 -0
  581. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  582. package/dist/senses/cli/bracketed-paste.js +82 -0
  583. package/dist/senses/cli/image-paste.js +287 -0
  584. package/dist/senses/cli/image-ref-navigation.js +75 -0
  585. package/dist/senses/cli/ink-app.js +156 -0
  586. package/dist/senses/cli/inline-diff.js +64 -0
  587. package/dist/senses/cli/input-keys.js +174 -0
  588. package/dist/senses/cli/kill-ring.js +86 -0
  589. package/dist/senses/cli/message-list.js +51 -0
  590. package/dist/senses/cli/ouro-tui.js +607 -0
  591. package/dist/senses/cli/spinner-imperative.js +135 -0
  592. package/dist/senses/cli/spinner.js +101 -0
  593. package/dist/senses/cli/status-line.js +60 -0
  594. package/dist/senses/cli/streaming-markdown.js +526 -0
  595. package/dist/senses/cli/tool-display.js +85 -0
  596. package/dist/senses/cli/tool-render.js +85 -0
  597. package/dist/senses/cli/tui-store.js +241 -0
  598. package/dist/senses/cli/virtual-list.js +35 -0
  599. package/dist/senses/cli-entry.js +60 -8
  600. package/dist/senses/cli.js +570 -309
  601. package/dist/senses/commands.js +66 -3
  602. package/dist/senses/context-packet-ledger.js +371 -0
  603. package/dist/senses/context-packets.js +154 -0
  604. package/dist/senses/habit-lifecycle-guard.js +24 -0
  605. package/dist/senses/habit-turn-message.js +160 -0
  606. package/dist/senses/ingress-evidence.js +2 -0
  607. package/dist/senses/mail-entry.js +66 -0
  608. package/dist/senses/mail.js +524 -0
  609. package/dist/senses/pipeline.js +1192 -204
  610. package/dist/senses/private-runtime-worker.js +857 -0
  611. package/dist/senses/private-runtime.js +1622 -0
  612. package/dist/senses/proactive-content-guard.js +51 -0
  613. package/dist/senses/root-host-approval-port.js +345 -0
  614. package/dist/senses/root-host-approval-runtime.js +393 -0
  615. package/dist/senses/sanctuary-authority-resident.js +102 -0
  616. package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
  617. package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
  618. package/dist/senses/sanctuary-grounding.js +118 -0
  619. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  620. package/dist/senses/sanctuary-health-acceptance-probe.js +786 -0
  621. package/dist/senses/sanctuary-health-runner.js +106 -0
  622. package/dist/senses/sanctuary-health.js +442 -0
  623. package/dist/senses/sanctuary-house.js +97 -0
  624. package/dist/senses/sanctuary-install-state-contract.js +169 -0
  625. package/dist/senses/sanctuary-interactive-control.js +410 -0
  626. package/dist/senses/sanctuary-media-catalog-contract.js +268 -0
  627. package/dist/senses/sanctuary-media-optimization.js +517 -0
  628. package/dist/senses/sanctuary-runtime.js +390 -0
  629. package/dist/senses/sanctuary-sab.js +85 -0
  630. package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
  631. package/dist/senses/shared-turn.js +832 -0
  632. package/dist/senses/shepherd/agent-presentation.js +88 -0
  633. package/dist/senses/shepherd/attention-page.js +166 -0
  634. package/dist/senses/shepherd/attention-sidebar.js +76 -0
  635. package/dist/senses/shepherd/attention.js +295 -0
  636. package/dist/senses/shepherd/checkin-config.js +76 -0
  637. package/dist/senses/shepherd/checkin-store.js +386 -0
  638. package/dist/senses/shepherd/client.js +196 -0
  639. package/dist/senses/shepherd/cmux.js +483 -0
  640. package/dist/senses/shepherd/composer.js +336 -0
  641. package/dist/senses/shepherd/copilot-app.js +648 -0
  642. package/dist/senses/shepherd/feedback-reply.js +108 -0
  643. package/dist/senses/shepherd/feedback-source.js +20 -0
  644. package/dist/senses/shepherd/feedback-store.js +331 -0
  645. package/dist/senses/shepherd/feedback-submit.js +182 -0
  646. package/dist/senses/shepherd/feedback-worker.js +208 -0
  647. package/dist/senses/shepherd/guidance.js +273 -0
  648. package/dist/senses/shepherd/herdr.js +427 -0
  649. package/dist/senses/shepherd/host.js +17 -0
  650. package/dist/senses/shepherd/human-decision-guard.js +29 -0
  651. package/dist/senses/shepherd/judge-evaluation-command.js +183 -0
  652. package/dist/senses/shepherd/judge-evaluation-entry.js +13 -0
  653. package/dist/senses/shepherd/judge-evaluation.js +155 -0
  654. package/dist/senses/shepherd/judge.js +210 -0
  655. package/dist/senses/shepherd/notice-outbox.js +262 -0
  656. package/dist/senses/shepherd/observation-publisher.js +320 -0
  657. package/dist/senses/shepherd/operations-defaults.js +86 -0
  658. package/dist/senses/shepherd/operations.js +159 -0
  659. package/dist/senses/shepherd/owner-notice.js +489 -0
  660. package/dist/senses/shepherd/prompt-state.js +60 -0
  661. package/dist/senses/shepherd/recovery.js +543 -0
  662. package/dist/senses/shepherd/redact.js +66 -0
  663. package/dist/senses/shepherd/returns.js +79 -0
  664. package/dist/senses/shepherd/sense.js +849 -0
  665. package/dist/senses/shepherd/shepherd-judge-corpus-v1.json +170 -0
  666. package/dist/senses/shepherd/surface.js +123 -0
  667. package/dist/senses/shepherd/target-binding.js +13 -0
  668. package/dist/senses/shepherd/terminal-process.js +147 -0
  669. package/dist/senses/shepherd-entry.js +98 -0
  670. package/dist/senses/surface-tool.js +109 -0
  671. package/dist/senses/teams-entry.js +68 -8
  672. package/dist/senses/teams-image.js +119 -0
  673. package/dist/senses/teams.js +998 -337
  674. package/dist/senses/telegram-admission.js +718 -0
  675. package/dist/senses/telegram-approval-runtime.js +793 -0
  676. package/dist/senses/telegram-attachments.js +186 -0
  677. package/dist/senses/telegram-audit-ledger.js +220 -0
  678. package/dist/senses/telegram-authority-transport.js +231 -0
  679. package/dist/senses/telegram-client.js +1755 -0
  680. package/dist/senses/telegram-effect-adapter.js +904 -0
  681. package/dist/senses/telegram-entry.js +83 -0
  682. package/dist/senses/telegram-handoff.js +154 -0
  683. package/dist/senses/telegram.js +2474 -0
  684. package/dist/senses/trust-gate.js +104 -9
  685. package/dist/senses/voice/audio-playback.js +237 -0
  686. package/dist/senses/voice/audio-routing.js +119 -0
  687. package/dist/senses/voice/call-auth.js +250 -0
  688. package/dist/senses/voice/elevenlabs.js +202 -0
  689. package/dist/senses/voice/floor-control.js +431 -0
  690. package/dist/senses/voice/floor-controller.js +115 -0
  691. package/dist/senses/voice/golden-path.js +116 -0
  692. package/dist/senses/voice/index.js +29 -0
  693. package/dist/senses/voice/local-audio-control.js +344 -0
  694. package/dist/senses/voice/local-audio-devices.js +516 -0
  695. package/dist/senses/voice/local-audio-disclosure.js +174 -0
  696. package/dist/senses/voice/local-audio-identity.js +103 -0
  697. package/dist/senses/voice/local-audio-join.js +325 -0
  698. package/dist/senses/voice/local-audio-mute.js +199 -0
  699. package/dist/senses/voice/local-audio-notify.js +124 -0
  700. package/dist/senses/voice/local-audio-prompts.js +116 -0
  701. package/dist/senses/voice/local-audio-transport.js +427 -0
  702. package/dist/senses/voice/local-audio.js +281 -0
  703. package/dist/senses/voice/local-media-stream.js +145 -0
  704. package/dist/senses/voice/meeting.js +113 -0
  705. package/dist/senses/voice/outbound.js +190 -0
  706. package/dist/senses/voice/phone.js +33 -0
  707. package/dist/senses/voice/playback.js +139 -0
  708. package/dist/senses/voice/process-guards.js +51 -0
  709. package/dist/senses/voice/realtime-eval.js +496 -0
  710. package/dist/senses/voice/realtime-tool-schema.js +195 -0
  711. package/dist/senses/voice/realtime-trace.js +531 -0
  712. package/dist/senses/voice/transcript.js +70 -0
  713. package/dist/senses/voice/turn.js +191 -0
  714. package/dist/senses/voice/twilio-phone-runtime.js +836 -0
  715. package/dist/senses/voice/twilio-phone.js +5454 -0
  716. package/dist/senses/voice/types.js +2 -0
  717. package/dist/senses/voice/whisper.js +161 -0
  718. package/dist/senses/voice-entry.js +83 -0
  719. package/dist/senses/voice-realtime-eval-command.js +99 -0
  720. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  721. package/dist/senses/voice-twilio-entry.js +87 -0
  722. package/dist/trips/core.js +138 -0
  723. package/dist/trips/store.js +265 -0
  724. package/dist/util/frontmatter.js +69 -0
  725. package/npm-shrinkwrap.json +8342 -0
  726. package/package.json +76 -13
  727. package/skills/agent-commerce.md +113 -0
  728. package/skills/browser-navigation.md +117 -0
  729. package/skills/commerce-setup-guide.md +116 -0
  730. package/skills/commerce-setup.md +84 -0
  731. package/skills/configure-dev-tools.md +99 -0
  732. package/skills/travel-planning.md +138 -0
  733. package/dist/heart/daemon/auth-flow.js +0 -430
  734. package/dist/heart/daemon/ouro-path-installer.js +0 -260
  735. package/dist/heart/daemon/ouro-version-manager.js +0 -171
  736. package/dist/heart/daemon/update-hooks.js +0 -138
  737. package/dist/heart/obligations.js +0 -191
  738. package/dist/heart/safe-workspace.js +0 -381
  739. package/dist/heart/session-recall.js +0 -116
  740. package/dist/mind/friends/channel.js +0 -84
  741. package/dist/mind/friends/group-context.js +0 -144
  742. package/dist/mind/friends/resolver.js +0 -84
  743. package/dist/mind/friends/store-file.js +0 -190
  744. package/dist/mind/friends/store.js +0 -4
  745. package/dist/mind/friends/tokens.js +0 -26
  746. package/dist/mind/friends/trust-explanation.js +0 -74
  747. package/dist/mind/friends/types.js +0 -29
  748. package/dist/repertoire/tasks/board.js +0 -134
  749. package/dist/repertoire/tasks/index.js +0 -224
  750. package/dist/repertoire/tasks/lifecycle.js +0 -80
  751. package/dist/repertoire/tasks/middleware.js +0 -65
  752. package/dist/repertoire/tasks/parser.js +0 -173
  753. package/dist/repertoire/tasks/scanner.js +0 -132
  754. package/dist/repertoire/tasks/transitions.js +0 -144
  755. package/dist/senses/bluebubbles-entry.js +0 -13
  756. package/dist/senses/bluebubbles.js +0 -1181
  757. package/dist/senses/debug-activity.js +0 -154
  758. package/dist/senses/inner-dialog-worker.js +0 -90
  759. package/dist/senses/inner-dialog.js +0 -525
  760. package/subagents/README.md +0 -7
  761. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  762. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  763. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  764. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  765. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  766. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  767. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  768. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  769. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  770. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  771. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  772. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  773. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  774. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  775. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  776. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  777. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -33,12 +33,23 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.OURO_CLI_TRUST_MANIFEST = void 0;
36
+ exports.BUNDLE_GIT_REFUSAL = exports.OURO_CLI_TRUST_MANIFEST = exports.PSYCHE_REFUSAL = void 0;
37
+ exports.canonicalTarget = canonicalTarget;
38
+ exports.insideAgentFolder = insideAgentFolder;
39
+ exports.psycheFileClass = psycheFileClass;
40
+ exports.psycheWriteAllowed = psycheWriteAllowed;
41
+ exports.psycheEditNote = psycheEditNote;
42
+ exports.bundleGitRefused = bundleGitRefused;
37
43
  exports.guardInvocation = guardInvocation;
38
44
  const fs = __importStar(require("node:fs"));
39
45
  const os = __importStar(require("node:os"));
40
- const types_1 = require("../mind/friends/types");
46
+ const path = __importStar(require("node:path"));
47
+ const friends_1 = require("@ouro.bot/friends");
48
+ const shell_quote_1 = require("shell-quote");
41
49
  const runtime_1 = require("../nerves/runtime");
50
+ const store_1 = require("../commerce/store");
51
+ const identity_1 = require("../heart/identity");
52
+ const sync_1 = require("../heart/sync");
42
53
  const deny = (reason) => ({ allowed: false, reason });
43
54
  const allow = { allowed: true };
44
55
  // --- reason templates ---
@@ -47,6 +58,7 @@ const REASONS = {
47
58
  readBeforeEdit: "i need to read that file first before i can edit it.",
48
59
  readBeforeOverwrite: "i need to read that file first before i can overwrite it.",
49
60
  protectedPath: "that path is protected — i can read it but not modify it.",
61
+ unreadablePath: "that path is protected — i can't read it.",
50
62
  destructiveCommand: "that command is too dangerous to run — it could cause irreversible damage.",
51
63
  // Trust reasons (vary by relationship)
52
64
  needsTrust: "i'd need a closer friend to vouch for you before i can do that.",
@@ -55,17 +67,165 @@ const REASONS = {
55
67
  // --- read-only tools that never need guardrails ---
56
68
  const READ_ONLY_TOOLS = new Set(["read_file", "glob", "grep"]);
57
69
  // --- protected path detection ---
58
- const PROTECTED_PATH_SEGMENTS = [".git/"];
59
- function getProtectedAbsolutePrefixes() {
60
- return [`${os.homedir()}/.agentsecrets/`];
70
+ const PROTECTED_PATH_SEGMENTS = [
71
+ ".git/",
72
+ ".ouro-cli/vault-unlock/",
73
+ ".ouro-cli/vault-unlock-dpapi/",
74
+ "state/policy/",
75
+ // The pre-trust-directory home of the escalation grants; the file is no longer read, and a model must not recreate it.
76
+ "state/a2a/escalation-grants.json",
77
+ // Where to queue mail for offline peers; a model that rewrote a contact could send a peer's mail to its own relay.
78
+ "state/a2a/relay-contacts/",
79
+ // Relay retirements awaiting their vault write; a forged marker would skip draining an old relay inbox.
80
+ "state/a2a/relay-retired/",
81
+ "state/outbox/",
82
+ "state/reports/",
83
+ ];
84
+ const PROTECTED_FILENAMES = ["agent.json", "tool-profiles.json"];
85
+ /** How many symlinks a dangling-link walk follows before refusing (the Linux kernel's own limit is 40). */
86
+ const MAX_SYMLINK_HOPS = 40;
87
+ /** A lookup that failed only because part of the path does not exist yet; any other failure leaves the target unknown. */
88
+ function rethrowUnlessMissing(error) {
89
+ const code = error.code;
90
+ if (code !== "ENOENT" && code !== "ENOTDIR")
91
+ throw error;
92
+ }
93
+ /**
94
+ * The real location of `lexical`: `realpathSync.native` of the deepest part that exists, the final file included, with the
95
+ * missing remainder appended. A dangling symlink is followed, from its real parent, to where a write through it would land.
96
+ * Throws when the filesystem cannot say where the path lands (a denied lookup, a symlink loop, too many links), so the
97
+ * guard can refuse rather than fall back to the lexical path.
98
+ */
99
+ function canonicalize(lexical) {
100
+ let current = lexical;
101
+ const missing = [];
102
+ let hops = 0;
103
+ for (;;) {
104
+ try {
105
+ return path.join(fs.realpathSync.native(current), ...missing);
106
+ }
107
+ catch (error) {
108
+ rethrowUnlessMissing(error);
109
+ }
110
+ let stat;
111
+ try {
112
+ stat = fs.lstatSync(current);
113
+ }
114
+ catch (error) {
115
+ rethrowUnlessMissing(error);
116
+ }
117
+ if (stat?.isSymbolicLink()) {
118
+ if (hops >= MAX_SYMLINK_HOPS)
119
+ throw new Error(`too many symbolic links resolving ${lexical}`);
120
+ hops += 1;
121
+ current = path.resolve(fs.realpathSync.native(path.dirname(current)), fs.readlinkSync(current));
122
+ continue;
123
+ }
124
+ const parent = path.dirname(current);
125
+ if (parent === current)
126
+ return path.join(current, ...missing);
127
+ missing.unshift(path.basename(current));
128
+ current = parent;
129
+ }
130
+ }
131
+ /**
132
+ * Every place a file tool could write `p`: resolved against the agent root and against the repo root (which is what
133
+ * `write_file` and `edit_file` use), each in its lexical and canonical form. An absolute `p` is resolved only as itself.
134
+ */
135
+ function canonicalTarget(p, agentRoot) {
136
+ const lexicals = path.isAbsolute(p) ? [p] : [path.resolve(agentRoot, p), path.resolve((0, identity_1.getRepoRoot)(), p)];
137
+ return lexicals.flatMap((lexical) => [lexical, canonicalize(lexical)]);
138
+ }
139
+ /** True when any form of `p` is the agent's `folder` (in either its lexical or its canonical form) or under it. */
140
+ function insideAgentFolder(p, agentRoot, folder) {
141
+ const lexicalFolder = path.resolve(agentRoot, folder);
142
+ const folders = [lexicalFolder, canonicalize(lexicalFolder)];
143
+ return canonicalTarget(p, agentRoot).some((target) => folders.some((dir) => target === dir || target.startsWith(`${dir}${path.sep}`)));
144
+ }
145
+ // --- the psyche belongs to the agent ---
146
+ // Every agent writes its own TACIT.md: what it learns about how its house works. The rest of its psyche (who it is) changes only
147
+ // when a family human asks it directly, one to one, on a channel that proves who they are.
148
+ exports.PSYCHE_REFUSAL = "that file is part of me; I change it when my owner asks me in our own chat.";
149
+ const PSYCHE_IDENTITY_CHANNELS = new Set(["telegram", "bluebubbles", "teams"]);
150
+ /** `tacit` for the agent's psyche/TACIT.md, `identity` for anything else in psyche/ (the folder and new files included), `null` outside it. */
151
+ function psycheFileClass(filePath, agentRoot) {
152
+ if (!filePath || !agentRoot || !insideAgentFolder(filePath, agentRoot, "psyche"))
153
+ return null;
154
+ const lexicalFolder = path.resolve(agentRoot, "psyche");
155
+ const folders = [lexicalFolder, canonicalize(lexicalFolder)];
156
+ const tacits = folders.map((folder) => path.join(folder, "TACIT.md"));
157
+ // Every form of the path that lands in psyche/ must be TACIT: a link that sends one form of it to SOUL makes it identity.
158
+ const inPsyche = canonicalTarget(filePath, agentRoot).filter((target) => folders.some((dir) => target === dir || target.startsWith(`${dir}${path.sep}`)));
159
+ return inPsyche.every((target) => tacits.includes(target)) ? "tacit" : "identity";
160
+ }
161
+ /** The lstat of `p`, or null when it does not exist; throws on any other lookup failure. */
162
+ function lstatIfPresent(p) {
163
+ try {
164
+ return fs.lstatSync(p);
165
+ }
166
+ catch (error) {
167
+ if (error.code === "ENOENT")
168
+ return null;
169
+ throw error;
170
+ }
171
+ }
172
+ /** Whether the person in this conversation may have the agent write this psyche file. */
173
+ function psycheWriteAllowed(fileClass, _filePath, context) {
174
+ const agentRoot = context.agentRoot;
175
+ /* v8 ignore next -- a file is only classed as psyche when there is an agent root @preserve */
176
+ if (!agentRoot)
177
+ return false;
178
+ try {
179
+ if (lstatIfPresent(path.join(agentRoot, "psyche"))?.isSymbolicLink())
180
+ return false;
181
+ if (fileClass === "tacit") {
182
+ // A symlink or a hard link would carry the write to some other file.
183
+ const tacit = lstatIfPresent(path.join(agentRoot, "psyche", "TACIT.md"));
184
+ if (tacit && (tacit.isSymbolicLink() || tacit.nlink > 1))
185
+ return false;
186
+ if (context.friendId === undefined)
187
+ return true;
188
+ return context.trustLevel === undefined || context.trustLevel === "friend" || context.trustLevel === "family";
189
+ }
190
+ }
191
+ catch {
192
+ return false;
193
+ }
194
+ return context.friendKind === "human"
195
+ && context.trustLevel === "family"
196
+ && context.channel !== undefined && PSYCHE_IDENTITY_CHANNELS.has(context.channel)
197
+ && context.isGroupChat !== true
198
+ // Teams does not yet tell the turn whether it is a group chat, so there only an explicit one-to-one counts.
199
+ && (context.channel !== "teams" || context.isGroupChat === false);
200
+ }
201
+ /** The line a successful write to an identity file carries back to the agent. */
202
+ function psycheEditNote(file) {
203
+ return `psyche edit: I changed psyche/${file}. I tell my owner in one line which file changed and why.`;
204
+ }
205
+ /** The agent's psyche folder, in the lexical form the shell check uses; the file tools classify with `psycheFileClass`. */
206
+ function isPsychePath(filePath, agentRoot) {
207
+ const psyche = path.resolve(agentRoot, "psyche");
208
+ const target = path.resolve(agentRoot, filePath);
209
+ return target === psyche || target.startsWith(`${psyche}${path.sep}`);
210
+ }
211
+ /**
212
+ * The friends store (`friends/` under the agent root) is written by the harness's own code, which the model's file tools and
213
+ * shell redirects must not reach: a model talked into editing a record could rewrite trust, admission or a legacy grant.
214
+ * Scoped to this agent's root, so a project directory that happens to be named `friends` stays editable. Defence in depth
215
+ * only: a shell can still write anywhere its uid can, which is why delegated-command authority lives outside the bundle.
216
+ */
217
+ function isFriendsStorePath(filePath, agentRoot) {
218
+ const store = path.resolve(agentRoot, "friends");
219
+ const target = path.resolve(agentRoot, filePath);
220
+ return target === store || target.startsWith(`${store}${path.sep}`);
61
221
  }
62
222
  function isProtectedPath(filePath) {
63
223
  for (const segment of PROTECTED_PATH_SEGMENTS) {
64
224
  if (filePath.includes(`/${segment}`) || filePath.startsWith(segment))
65
225
  return true;
66
226
  }
67
- for (const prefix of getProtectedAbsolutePrefixes()) {
68
- if (filePath.startsWith(prefix))
227
+ for (const name of PROTECTED_FILENAMES) {
228
+ if (path.basename(filePath) === name)
69
229
  return true;
70
230
  }
71
231
  return false;
@@ -99,6 +259,196 @@ function shellWritesToProtectedPath(command) {
99
259
  return true;
100
260
  return false;
101
261
  }
262
+ // --- the psyche folder in shell commands ---
263
+ // The agent changes its own psyche through write_file and edit_file, where the trust gate below decides who may ask for it.
264
+ // The shell never writes there, for anyone: a shell command that mentions the psyche folder at all runs only if every part of
265
+ // it is a read-only command, because enumerating the ways to write a file (>, >>, tee, cp, mv, sed -i, node -e, python, cd then
266
+ // redirect) is a list the model can always step around.
267
+ const PSYCHE_READ_ONLY_COMMANDS = new Set(["cat", "ls", "head", "tail", "grep", "wc", "sha256sum", "stat", "cd", "pwd"]);
268
+ const SEGMENT_OPERATORS = new Set(["&&", "||", ";", "|"]);
269
+ // --- globs and expansions that could land inside a protected folder ---
270
+ // A word such as /bundle/fr*nds/x.json, /bundle/friend?/x.json or ${F}ends/x.json names no protected folder literally, but the
271
+ // shell could resolve it into one. A word like that counts as mentioning the folder when its directory part can match the folder's
272
+ // path, so the read-only rule below applies to the command.
273
+ const EXPANSION = "\u0001";
274
+ const STABLE_VARIABLES = ["HOME", "TMPDIR", "USER"];
275
+ function neutraliseExpansions(command) {
276
+ return command.replace(/\$\([^)]*\)|`[^`]*`|\$\{([A-Za-z_]\w*)\}|\$([A-Za-z_]\w*)|\$\{[^}]*\}/g, (_match, braced, plain) => {
277
+ const name = braced ?? plain;
278
+ if (name !== undefined && STABLE_VARIABLES.includes(name) && !new RegExp(`\\b${name}=`).test(command) && process.env[name])
279
+ return process.env[name];
280
+ return EXPANSION;
281
+ });
282
+ }
283
+ function wordPattern(word) {
284
+ const generalised = word.replace(/\{[^}]*\}/g, EXPANSION).replace(/\[[^\]]*\]/g, "?");
285
+ let source = "";
286
+ for (const char of generalised)
287
+ source += char === EXPANSION ? ".*" : char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[.+^$()|\\\]{}[]/g, "\\$&");
288
+ return new RegExp(`^${source}$`);
289
+ }
290
+ function globOrExpansionCouldReach(command, folder, agentRoot) {
291
+ const tokens = neutraliseExpansions(command).split(/[\s<>|;&()'"]+/).filter((token) => /[*?[{\u0001]/.test(token));
292
+ return tokens.some((token) => {
293
+ const word = token.startsWith("~") ? expandHome(token) : token;
294
+ const absolute = word.startsWith("/") || word.startsWith(EXPANSION) ? word : `${agentRoot}/${word}`;
295
+ const dir = absolute.slice(0, Math.max(absolute.lastIndexOf("/"), 0));
296
+ return wordPattern(absolute).test(folder) || wordPattern(dir).test(folder);
297
+ });
298
+ }
299
+ function commandMentionsPsyche(command, words, agentRoot) {
300
+ const psyche = path.resolve(agentRoot, "psyche");
301
+ if (globOrExpansionCouldReach(command, psyche, agentRoot))
302
+ return true;
303
+ if (command.includes(psyche) || words.some((word) => word.includes(psyche)))
304
+ return true;
305
+ if (words.some((word) => word === "psyche" || isPsychePath(word, agentRoot)))
306
+ return true;
307
+ // A relative "psyche/..." inside code or a quoted argument, when this agent has such a folder.
308
+ if (/(?:^|[\s'"=(])psyche\//.test(command) && fs.existsSync(psyche))
309
+ return true;
310
+ // ~/..., $HOME/... and paths relative to a cd or -C target that land in this agent's psyche/.
311
+ return commandMentionsAgentFolder(command, words, agentRoot, "psyche");
312
+ }
313
+ /**
314
+ * The shared rule: when a shell command mentions a protected folder at all, it runs only if every part of it is a read-only
315
+ * command. The mention test is the only thing that differs between folders.
316
+ */
317
+ function protectedFolderShellWriteRefused(command, mentions) {
318
+ const entries = (0, shell_quote_1.parse)(command);
319
+ const words = entries.filter((entry) => typeof entry === "string");
320
+ if (!mentions(words))
321
+ return false;
322
+ // Substitution, heredocs and any $VAR or ${VAR} expansion: the shell could build a protected path or a command word we cannot see.
323
+ if (/\$[({A-Za-z_]|`|<\(|<</.test(command))
324
+ return true;
325
+ let expectCommandWord = true;
326
+ for (const entry of entries) {
327
+ if (typeof entry === "string") {
328
+ if (expectCommandWord && !/^\w+=/.test(entry)) {
329
+ if (!PSYCHE_READ_ONLY_COMMANDS.has(entry))
330
+ return true;
331
+ expectCommandWord = false;
332
+ }
333
+ continue;
334
+ }
335
+ const op = "op" in entry ? entry.op : "comment";
336
+ if (SEGMENT_OPERATORS.has(op))
337
+ expectCommandWord = true;
338
+ else if (op !== "glob")
339
+ return true;
340
+ }
341
+ return false;
342
+ }
343
+ function psycheShellWriteRefused(command, agentRoot) {
344
+ if (!agentRoot)
345
+ return false;
346
+ try {
347
+ return protectedFolderShellWriteRefused(command, (words) => commandMentionsPsyche(command, words, agentRoot));
348
+ }
349
+ catch {
350
+ // The filesystem could not say where a path that names psyche/ lands: refuse rather than guess.
351
+ return true;
352
+ }
353
+ }
354
+ // --- the friends store in shell commands ---
355
+ // Same stance as the psyche folder: a command that names this agent's friends store runs only if it is read-only, because a list of
356
+ // the ways to write a file (>, >>, tee, cp, mv, sed -i, node -e, python, cd then redirect, ~ paths) is one the model can step around.
357
+ function expandHome(word) {
358
+ return word === "~" || word.startsWith("~/") ? path.join(os.homedir(), word.slice(1)) : word;
359
+ }
360
+ function commandMentionsFriendsStore(command, words, agentRoot) {
361
+ if (globOrExpansionCouldReach(command, path.resolve(agentRoot, "friends"), agentRoot))
362
+ return true;
363
+ // Only path-shaped words resolve against the store: an ordinary word such as "friends" in prose is not a path.
364
+ if (words.some((word) => word.includes("/") && isFriendsStorePath(expandHome(word), agentRoot)))
365
+ return true;
366
+ // A bare `friends` names the store only as the target of a cd; elsewhere it is an ordinary word ("my friends are kind").
367
+ if (words.some((word, index) => word.startsWith("friends/") || (word === "friends" && words[index - 1] === "cd")))
368
+ return true;
369
+ // A variable or substitution in front of the path ($BUNDLE/friends/x.json): the folder name is still in the command.
370
+ if (/\$[({A-Za-z_]|`/.test(command) && /(?:^|[\/\s'"=])friends(?:$|[\/\s'"])/.test(command))
371
+ return true;
372
+ // An absolute or relative path inside code or a quoted argument, such as node -e "...'/bundle/friends/x.json'...".
373
+ const store = path.resolve(agentRoot, "friends");
374
+ if (command.includes(store) || /(?:^|[\s'"=(])friends\//.test(command))
375
+ return true;
376
+ // Paths relative to a cd or -C target that land in this agent's friends/.
377
+ return commandMentionsAgentFolder(command, words, agentRoot, "friends");
378
+ }
379
+ function friendsShellWriteRefused(command, agentRoot) {
380
+ if (!agentRoot)
381
+ return false;
382
+ try {
383
+ return protectedFolderShellWriteRefused(command, (words) => commandMentionsFriendsStore(command, words, agentRoot));
384
+ }
385
+ catch {
386
+ // The filesystem could not say where a path that names friends/ lands: refuse rather than guess.
387
+ return true;
388
+ }
389
+ }
390
+ // --- where a path in a shell command lands ---
391
+ // A command can name one of this agent's folders by an absolute path, by ~/ or $HOME/, or relative to wherever it cd's (or
392
+ // git -C's) first. Each word whose path has a part exactly `<name>` is resolved against every directory the command could be
393
+ // working in, lexically and canonically, and counts when it lands in this agent's <name>/.
394
+ const UNFOLLOWABLE_WORKING_DIRECTORY = /(?:^|[\s;&|(`])(?:pushd|popd)(?:\s|$)|(?:^|[\s;&|(`])cd\s+(?:--|["']?\$)|\s-C\s*["']?\$|(?:^|[\s;&|(`/])(?:sh|bash|zsh|dash|ksh)\s+(?:-\w+\s+)*-\w*c\b/;
395
+ /** Words of a shell command that name a path whose part is exactly `name`, wherever they sit (code and quotes included). */
396
+ function namedFolderWords(command, name) {
397
+ const part = new RegExp(`(?:^|/)${name}(?:/|$)`);
398
+ return command.split(/[\s'"=();|&<>`]+/).filter((token) => part.test(token));
399
+ }
400
+ function commandMentionsAgentFolder(command, words, agentRoot, name) {
401
+ const lexicalFolder = path.resolve(agentRoot, name);
402
+ if (globOrExpansionCouldReach(command, lexicalFolder, agentRoot))
403
+ return true;
404
+ const candidates = namedFolderWords(command, name);
405
+ if (candidates.length === 0)
406
+ return false;
407
+ // A variable in front of the path ($HOME/.../<name>/x): where it lands is the shell's to say, so it counts.
408
+ if (candidates.some((candidate) => candidate.includes("$")))
409
+ return true;
410
+ // A working directory this guard cannot follow (pushd, cd --, a cd or -C to a variable, or a nested shell's -c string):
411
+ // a relative path could land anywhere, so it counts.
412
+ if (UNFOLLOWABLE_WORKING_DIRECTORY.test(command))
413
+ return true;
414
+ // A path that names this bundle's own folder (x.ouro/psyche) or a glob just above the folder (*/psyche) is this agent's
415
+ // wherever the command works from, so eval, a piped shell, env --chdir, find -path and code that chdir's all count.
416
+ const bundle = path.basename(path.resolve(agentRoot)).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
417
+ const anchored = new RegExp(`(?:^|/)(?:${bundle}|[^/]*[*?[][^/]*)/${name}(?:/|$)`);
418
+ if (candidates.some((candidate) => anchored.test(candidate)))
419
+ return true;
420
+ const folders = [lexicalFolder, path.join(canonicalize(path.resolve(agentRoot)), name)];
421
+ // Every directory the command could be working in: where the shell starts, and each cd target, from each earlier one.
422
+ const cwds = [process.cwd()];
423
+ for (const [index, word] of words.entries()) {
424
+ // `git -C <dir>` works in <dir> just as `cd <dir>` does.
425
+ if (words[index - 1] !== "cd" && words[index - 1] !== "-C")
426
+ continue;
427
+ const target = expandHome(word);
428
+ cwds.push(...(path.isAbsolute(target) ? [target] : cwds.map((cwd) => path.resolve(cwd, target))));
429
+ }
430
+ return candidates.some((candidate) => {
431
+ const word = expandHome(candidate);
432
+ const lexicals = path.isAbsolute(word) ? [path.resolve(word)] : cwds.map((cwd) => path.resolve(cwd, word));
433
+ return lexicals.flatMap((lexical) => [lexical, canonicalize(lexical)])
434
+ .some((target) => folders.some((dir) => target === dir || target.startsWith(`${dir}${path.sep}`)));
435
+ });
436
+ }
437
+ // --- the product rules in shell commands ---
438
+ // product/PRODUCT.md is the package's standing rules for this agent; releases replace it, and nothing in a conversation does.
439
+ // Unlike the friends store, a bare relative "product/" is an ordinary project folder almost everywhere, so it counts as this
440
+ // agent's only when the command is plainly working in the agent root.
441
+ function productShellWriteRefused(command, agentRoot) {
442
+ if (!agentRoot)
443
+ return false;
444
+ try {
445
+ return protectedFolderShellWriteRefused(command, (words) => commandMentionsAgentFolder(command, words, agentRoot, "product"));
446
+ }
447
+ catch {
448
+ // The filesystem could not say where a path that names product/ lands: refuse rather than guess.
449
+ return true;
450
+ }
451
+ }
102
452
  // --- structural guardrail checks (always on, all trust levels) ---
103
453
  function checkReadBeforeWrite(toolName, args, context) {
104
454
  if (toolName === "edit_file") {
@@ -127,21 +477,51 @@ function checkDestructiveShellPatterns(toolName, args) {
127
477
  }
128
478
  return allow;
129
479
  }
130
- function checkProtectedPaths(toolName, args) {
480
+ function checkProtectedPaths(toolName, args, context) {
131
481
  if (toolName === "write_file" || toolName === "edit_file") {
132
482
  const filePath = args.path || "";
483
+ // The file tools resolve a relative path against the repo root and follow symlinks, so the guard checks every form.
484
+ // A path whose landing place the filesystem cannot report is refused rather than checked lexically.
485
+ const agentRoot = filePath ? context.agentRoot : undefined;
486
+ if (agentRoot) {
487
+ try {
488
+ const psycheClass = psycheFileClass(filePath, agentRoot);
489
+ if (psycheClass && !psycheWriteAllowed(psycheClass, filePath, context))
490
+ return deny(exports.PSYCHE_REFUSAL);
491
+ if (insideAgentFolder(filePath, agentRoot, "friends"))
492
+ return deny(REASONS.protectedPath);
493
+ if (insideAgentFolder(filePath, agentRoot, "product"))
494
+ return deny(REASONS.protectedPath);
495
+ // A link can carry a write that reads as harmless to a protected file (state/policy, agent.json).
496
+ if (canonicalTarget(filePath, agentRoot).some(isProtectedPath))
497
+ return deny(REASONS.protectedPath);
498
+ }
499
+ catch {
500
+ return deny(REASONS.protectedPath);
501
+ }
502
+ }
133
503
  if (isProtectedPath(filePath))
134
504
  return deny(REASONS.protectedPath);
135
505
  }
136
506
  if (toolName === "shell") {
137
507
  const command = args.command || "";
508
+ if (psycheShellWriteRefused(command, context.agentRoot))
509
+ return deny(exports.PSYCHE_REFUSAL);
510
+ if (friendsShellWriteRefused(command, context.agentRoot))
511
+ return deny(REASONS.protectedPath);
512
+ if (productShellWriteRefused(command, context.agentRoot))
513
+ return deny(REASONS.protectedPath);
514
+ if (bundleGitRefused(command, context.agentRoot))
515
+ return deny(exports.BUNDLE_GIT_REFUSAL);
516
+ if (syncSecretShellRefused(command))
517
+ return deny(REASONS.unreadablePath);
138
518
  if (shellWritesToProtectedPath(command))
139
519
  return deny(REASONS.protectedPath);
140
520
  }
141
521
  return allow;
142
522
  }
143
523
  function checkStructuralGuardrails(toolName, args, context) {
144
- const protectedResult = checkProtectedPaths(toolName, args);
524
+ const protectedResult = checkProtectedPaths(toolName, args, context);
145
525
  if (!protectedResult.allowed)
146
526
  return protectedResult;
147
527
  const destructiveResult = checkDestructiveShellPatterns(toolName, args);
@@ -155,18 +535,15 @@ exports.OURO_CLI_TRUST_MANIFEST = {
155
535
  whoami: "acquaintance",
156
536
  changelog: "acquaintance",
157
537
  "session list": "acquaintance",
158
- "task board": "friend",
159
- "task create": "friend",
160
- "task update": "friend",
161
- "task show": "friend",
162
- "task actionable": "friend",
163
- "task deps": "friend",
164
- "task sessions": "friend",
165
538
  "friend list": "friend",
166
539
  "friend show": "friend",
167
540
  "friend create": "friend",
168
541
  "friend update": "family",
169
- "reminder create": "friend",
542
+ // Linking and merging change who the agent believes someone is: family only.
543
+ "friend link": "family",
544
+ "friend unlink": "family",
545
+ "friend merge": "family",
546
+ "friend dedupe": "family",
170
547
  "config model": "friend",
171
548
  "config models": "friend",
172
549
  "mcp list": "acquaintance",
@@ -201,7 +578,8 @@ function resolveOuroSubcommand(command) {
201
578
  /* v8 ignore next -- bare "ouro" is caught upstream by checkShellTrustGuardrails @preserve */
202
579
  if (!afterOuro)
203
580
  return null;
204
- const tokens = afterOuro.split(/\s+/);
581
+ // `--agent <name>` may come before the subcommand; it never changes which subcommand runs.
582
+ const tokens = afterOuro.split(/\s+/).filter((token, index, all) => token !== "--agent" && all[index - 1] !== "--agent");
205
583
  const twoWord = tokens.length >= 2 ? `${tokens[0]} ${tokens[1]}` : null;
206
584
  // Two-word match first (e.g. "task board"), then one-word (e.g. "whoami")
207
585
  if (twoWord && exports.OURO_CLI_TRUST_MANIFEST[twoWord])
@@ -210,6 +588,26 @@ function resolveOuroSubcommand(command) {
210
588
  return tokens[0];
211
589
  return null;
212
590
  }
591
+ // --- MCP server-specific trust rules ---
592
+ const MCP_SERVER_TRUST = {
593
+ browser: { minTrust: "friend", blockGroupChat: true },
594
+ };
595
+ function checkMcpServerTrust(command, context) {
596
+ const match = command.match(/^ouro\s+mcp\s+call\s+(\S+)/);
597
+ if (!match)
598
+ return allow;
599
+ const serverName = match[1];
600
+ const rules = MCP_SERVER_TRUST[serverName];
601
+ if (!rules)
602
+ return allow; // no special rules for this server
603
+ if (!trustLevelSatisfied(rules.minTrust, context.trustLevel ?? "friend")) {
604
+ return deny(REASONS.needsTrust);
605
+ }
606
+ if (rules.blockGroupChat && context.isGroupChat) {
607
+ return deny("browser tools are only available in 1:1 conversations, not group chats.");
608
+ }
609
+ return allow;
610
+ }
213
611
  function checkSingleShellCommandTrust(command, trustLevel) {
214
612
  const trimmed = command.trim();
215
613
  const tokens = trimmed.split(/\s+/);
@@ -261,15 +659,1075 @@ function checkWriteTrustGuardrails(toolName, args, context) {
261
659
  return allow;
262
660
  return deny(REASONS.needsTrustForWrite);
263
661
  }
662
+ // --- credential tool trust gating ---
663
+ // Credential write tools: family only
664
+ const CREDENTIAL_FAMILY_TOOLS = new Set([
665
+ "credential_generate_password", "credential_store", "credential_delete", "vault_setup",
666
+ // User profile tools: family only
667
+ "user_profile_store", "user_profile_get", "user_profile_delete",
668
+ // Payment tools: family only
669
+ "stripe_create_card", "stripe_deactivate_card", "stripe_list_cards",
670
+ // Booking tools that involve payment: family only
671
+ "flight_book", "flight_hold", "flight_cancel",
672
+ ]);
673
+ // Credential read tools: friend+
674
+ const CREDENTIAL_TRUSTED_TOOLS = new Set(["credential_get", "credential_list"]);
675
+ // Travel tools: friend+ (weather_lookup accesses vault credentials indirectly;
676
+ // advisory and geocode are public APIs but gated for consistency)
677
+ // Flight search is also friend+ (read-only, no payment)
678
+ const TRAVEL_TRUSTED_TOOLS = new Set(["weather_lookup", "travel_advisory", "geocode_search", "flight_search"]);
679
+ const A2A_TRUSTED_TOOLS = new Set(["a2a_list_peers", "a2a_send_message", "a2a_get_task"]);
680
+ const COMMERCE_FAMILY_TOOLS = new Set(["commerce_checkout_preview", "commerce_checkout_commit", "commerce_receipt_get", "commerce_access_log"]);
681
+ const COMMERCE_AUTHORITY_TOOLS = new Set(["stripe_create_card", "flight_hold", "flight_book"]);
682
+ // The Shepherd tools read the owner's terminals and speak to them through their desktop, so only family may use them.
683
+ // A call with no known trust level fails closed.
684
+ const SHEPHERD_FAMILY_TOOLS = new Set(["shepherd_overview", "shepherd_read", "shepherd_signal", "shepherd_send"]);
685
+ const MAIL_FAMILY_TOOLS = new Set(["mail_screener", "mail_decide", "mail_access_log", "mail_send", "mail_index_refresh"]);
686
+ const MAIL_DELEGATED_READ_TOOLS = new Set(["mail_recent", "mail_search"]);
687
+ function opaqueAnsiShellAtoms(command) {
688
+ const atoms = [];
689
+ let inAnsiQuote = false;
690
+ for (let index = 0; index < command.length;) {
691
+ if (!inAnsiQuote && command.startsWith("$'", index)) {
692
+ inAnsiQuote = true;
693
+ index += 2;
694
+ continue;
695
+ }
696
+ const character = command[index];
697
+ if (inAnsiQuote && character === "'") {
698
+ inAnsiQuote = false;
699
+ index += 1;
700
+ continue;
701
+ }
702
+ if (inAnsiQuote && character === "\\") {
703
+ const escape = command.slice(index).match(/^\\(?:x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{1,4}|U[0-9a-fA-F]{1,8}|[0-7]{1,3}|c.|.)?/s)[0];
704
+ atoms.push(null);
705
+ index += escape.length;
706
+ continue;
707
+ }
708
+ if (!inAnsiQuote && (character === "'" || character === '"')) {
709
+ index += 1;
710
+ continue;
711
+ }
712
+ if (!inAnsiQuote && character === "\\" && index + 1 < command.length) {
713
+ atoms.push(command[index + 1]);
714
+ index += 2;
715
+ continue;
716
+ }
717
+ atoms.push(character);
718
+ index += 1;
719
+ }
720
+ return atoms;
721
+ }
722
+ function shellContainsAmbiguousAnsiMailCacheSync(command) {
723
+ if (!command.includes("$'"))
724
+ return false;
725
+ const expected = ["ouro", "mail", "sync-cache"];
726
+ let states = new Set(["0:0"]);
727
+ const crossTokenBoundary = (current) => {
728
+ const next = new Set(["0:0"]);
729
+ for (const state of current) {
730
+ const [tokenIndex, characterIndex] = state.split(":").map(Number);
731
+ if (characterIndex !== expected[tokenIndex].length)
732
+ continue;
733
+ if (tokenIndex === expected.length - 1)
734
+ return { matched: true, states: next };
735
+ next.add(`${tokenIndex + 1}:0`);
736
+ }
737
+ return { matched: false, states: next };
738
+ };
739
+ for (const atom of opaqueAnsiShellAtoms(command)) {
740
+ if (atom === null) {
741
+ const next = new Set(states);
742
+ for (const state of states) {
743
+ const [tokenIndex, characterIndex] = state.split(":").map(Number);
744
+ if (characterIndex >= 0 && characterIndex < expected[tokenIndex].length) {
745
+ next.add(`${tokenIndex}:${characterIndex + 1}`);
746
+ }
747
+ }
748
+ const boundary = crossTokenBoundary(states);
749
+ if (boundary.matched)
750
+ return true;
751
+ states = new Set([...next, ...boundary.states]);
752
+ continue;
753
+ }
754
+ if (/\s|[;&|()`]/.test(atom)) {
755
+ const boundary = crossTokenBoundary(states);
756
+ if (boundary.matched)
757
+ return true;
758
+ states = boundary.states;
759
+ continue;
760
+ }
761
+ const next = new Set();
762
+ for (const state of states) {
763
+ const [tokenIndex, characterIndex] = state.split(":").map(Number);
764
+ const wanted = expected[tokenIndex]?.[characterIndex];
765
+ next.add(wanted?.toLowerCase() === atom.toLowerCase() ? `${tokenIndex}:${characterIndex + 1}` : `${tokenIndex}:-1`);
766
+ }
767
+ states = next;
768
+ }
769
+ return crossTokenBoundary(states).matched;
770
+ }
771
+ const SHELL_COMMAND_BOUNDARIES = new Set(["||", "&&", ";;", "|&", "&", ";", "(", ")", "|", "<("]);
772
+ const SHELL_INTERPRETERS = new Set(["sh", "bash", "zsh"]);
773
+ const SHELL_TOKEN_BOUNDARIES = new Set(["{", "}", "then", "elif", "else", "do"]);
774
+ const SHELL_GRAMMAR_PREFIXES = new Set(["!", "if", "until", "while"]);
775
+ const SHELL_PARAMETER_MARKER = "__OURO_SHELL_PARAMETER_";
776
+ const SHELL_ARRAY_EXPANSION = "__OURO_ARRAY_EXPANSION__";
777
+ function hasUnescapedShellSubstitution(value) {
778
+ for (let index = 0; index < value.length; index += 1) {
779
+ if (value[index] === "\\") {
780
+ index += 1;
781
+ continue;
782
+ }
783
+ if (value.startsWith("$(", index) && !value.startsWith("$((", index))
784
+ return true;
785
+ if (value[index] === "`")
786
+ return true;
787
+ }
788
+ return false;
789
+ }
790
+ function stripHereDocBodies(command) {
791
+ const pending = [];
792
+ const lines = command.split("\n");
793
+ return lines.map((sourceLine) => {
794
+ if (pending.length > 0) {
795
+ const active = pending[0];
796
+ const candidate = active.stripTabs ? sourceLine.replace(/^\t+/, "") : sourceLine;
797
+ if (candidate === active.delimiter) {
798
+ pending.shift();
799
+ return "";
800
+ }
801
+ if (active.quoted || !hasUnescapedShellSubstitution(sourceLine))
802
+ return "";
803
+ return exposeShellSubstitutions(sourceLine).split("\n").slice(1).join("\n");
804
+ }
805
+ const pattern = /<<(?!<)(-)?\s*(?:'([^']*)'|"([^"]*)"|\\([^\s;&|]+)|([^\s;&|]+))/g;
806
+ for (const match of stripShellComments(sourceLine).matchAll(pattern)) {
807
+ pending.push({
808
+ stripTabs: match[1] !== undefined,
809
+ delimiter: match[2] ?? match[3] ?? match[4] ?? match[5],
810
+ quoted: match[2] !== undefined || match[3] !== undefined || match[4] !== undefined,
811
+ });
812
+ }
813
+ return sourceLine;
814
+ }).join("\n");
815
+ }
816
+ function stripShellComments(command) {
817
+ let result = "";
818
+ let quote = null;
819
+ for (let index = 0; index < command.length; index += 1) {
820
+ const character = command[index];
821
+ if (character === "\\" && quote !== "single") {
822
+ result += character + (command[index + 1] ?? "");
823
+ index += 1;
824
+ continue;
825
+ }
826
+ if (character === "'" && quote !== "double")
827
+ quote = quote === "single" ? null : "single";
828
+ else if (character === '"' && quote !== "single")
829
+ quote = quote === "double" ? null : "double";
830
+ if (character === "#" && quote === null && (index === 0 || /[\s;&|()]/.test(command[index - 1]))) {
831
+ const newline = command.indexOf("\n", index);
832
+ if (newline < 0)
833
+ break;
834
+ result += " ".repeat(newline - index) + "\n";
835
+ index = newline;
836
+ continue;
837
+ }
838
+ result += character;
839
+ }
840
+ return result;
841
+ }
842
+ const SHELL_ARRAY_ASSIGNMENT = /([A-Za-z_][A-Za-z0-9_]*)(\+?=)\(([^()\n]*)\)/y;
843
+ const SHELL_INDEXED_ASSIGNMENT = /([A-Za-z_][A-Za-z0-9_]*)\[(\d+)\]=("[^"]*"|'[^']*'|[^\s;&|]+)/y;
844
+ const SHELL_SCALAR_ASSIGNMENT = /([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|'[^']*'|[^\s;&|()]+)/y;
845
+ const SHELL_ARRAY_PARAMETER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\[(@|\*)\]\}/y;
846
+ const SHELL_SCALAR_PARAMETER = /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/y;
847
+ function shellMatchAt(pattern, command, index) {
848
+ pattern.lastIndex = index;
849
+ return pattern.exec(command);
850
+ }
851
+ function shellAssignmentPosition(command, index) {
852
+ let previous = index - 1;
853
+ while (previous >= 0 && /[ \t]/.test(command[previous]))
854
+ previous -= 1;
855
+ return previous < 0 || /[;&|()\n]/.test(command[previous]);
856
+ }
857
+ function parseShellWords(value) {
858
+ if (hasUnescapedShellSubstitution(value) || /(^|[^\\])\$(?!['"])/.test(value))
859
+ return undefined;
860
+ try {
861
+ const entries = (0, shell_quote_1.parse)(value);
862
+ if (entries.some((entry) => typeof entry !== "string"))
863
+ return undefined;
864
+ return entries;
865
+ }
866
+ catch {
867
+ return undefined;
868
+ }
869
+ }
870
+ function shellAssignmentPersists(command, endIndex) {
871
+ let next = endIndex;
872
+ while (next < command.length && /[ \t]/.test(command[next]))
873
+ next += 1;
874
+ return next === command.length || /[;&|\n]/.test(command[next]);
875
+ }
876
+ function shellUnquotedFields(values) {
877
+ const fields = values.flatMap((value) => value.split(/\s+/).filter(Boolean));
878
+ return fields.some((field) => /[*?[]/.test(field)) ? SHELL_ARRAY_EXPANSION : (0, shell_quote_1.quote)(fields);
879
+ }
880
+ function doubleQuotedFragment(value) {
881
+ if (hasUnescapedShellSubstitution(value))
882
+ return undefined;
883
+ try {
884
+ const parsed = (0, shell_quote_1.parse)(`"${value}"`);
885
+ return parsed.length === 1 && typeof parsed[0] === "string" ? parsed[0] : undefined;
886
+ }
887
+ catch {
888
+ return undefined;
889
+ }
890
+ }
891
+ function closingDoubleQuote(command, startIndex) {
892
+ for (let index = startIndex; index < command.length; index += 1) {
893
+ if (command[index] === "\\")
894
+ index += 1;
895
+ else if (command[index] === '"')
896
+ return index;
897
+ }
898
+ return -1;
899
+ }
900
+ function transformOrderedShellVariables(command) {
901
+ const variables = new Map();
902
+ const result = [];
903
+ let quote = null;
904
+ let doubleQuoteStart = -1;
905
+ let doubleQuoteResultIndex = -1;
906
+ for (let index = 0; index < command.length; index += 1) {
907
+ const character = command[index];
908
+ if (character === "\\" && quote !== "single") {
909
+ result.push(character + (command[index + 1] ?? ""));
910
+ index += 1;
911
+ continue;
912
+ }
913
+ if (character === "'") {
914
+ if (quote !== "double")
915
+ quote = quote === "single" ? null : "single";
916
+ result.push(character);
917
+ continue;
918
+ }
919
+ if (character === '"') {
920
+ if (quote !== "single") {
921
+ if (quote === "double") {
922
+ quote = null;
923
+ doubleQuoteStart = -1;
924
+ doubleQuoteResultIndex = -1;
925
+ }
926
+ else {
927
+ quote = "double";
928
+ doubleQuoteStart = index;
929
+ doubleQuoteResultIndex = result.length;
930
+ }
931
+ }
932
+ result.push(character);
933
+ continue;
934
+ }
935
+ if (quote === null && /[A-Za-z_]/.test(character) && shellAssignmentPosition(command, index)) {
936
+ const arrayAssignment = shellMatchAt(SHELL_ARRAY_ASSIGNMENT, command, index);
937
+ if (arrayAssignment) {
938
+ const [, name, operator, body] = arrayAssignment;
939
+ const elements = parseShellWords(body);
940
+ if (elements === undefined)
941
+ variables.delete(name);
942
+ else
943
+ variables.set(name, { array: operator === "+=" ? [...(variables.get(name)?.array ?? []), ...elements] : elements });
944
+ result.push(`${name}=__OURO_ARRAY_LITERAL`);
945
+ index += arrayAssignment[0].length - 1;
946
+ continue;
947
+ }
948
+ const indexedAssignment = shellMatchAt(SHELL_INDEXED_ASSIGNMENT, command, index);
949
+ if (indexedAssignment) {
950
+ const [, name, elementIndex, rawValue] = indexedAssignment;
951
+ const element = parseShellWords(rawValue);
952
+ if (element?.length !== 1)
953
+ variables.delete(name);
954
+ else {
955
+ const array = [...(variables.get(name)?.array ?? [])];
956
+ array[Number(elementIndex)] = element[0];
957
+ variables.set(name, { array });
958
+ }
959
+ result.push(`${name}=__OURO_ARRAY_LITERAL`);
960
+ index += indexedAssignment[0].length - 1;
961
+ continue;
962
+ }
963
+ const scalarAssignment = shellMatchAt(SHELL_SCALAR_ASSIGNMENT, command, index);
964
+ if (scalarAssignment) {
965
+ const [, name, rawValue] = scalarAssignment;
966
+ const scalar = parseShellWords(rawValue);
967
+ if (shellAssignmentPersists(command, index + scalarAssignment[0].length)) {
968
+ if (scalar?.length === 1)
969
+ variables.set(name, { scalar: scalar[0] });
970
+ else
971
+ variables.delete(name);
972
+ }
973
+ result.push(scalarAssignment[0]);
974
+ index += scalarAssignment[0].length - 1;
975
+ continue;
976
+ }
977
+ }
978
+ if (quote !== "single" && character === "$") {
979
+ const arrayExpansion = shellMatchAt(SHELL_ARRAY_PARAMETER, command, index);
980
+ if (arrayExpansion) {
981
+ const [, name, mode] = arrayExpansion;
982
+ const elements = variables.get(name)?.array;
983
+ if (elements === undefined)
984
+ result.push(SHELL_ARRAY_EXPANSION);
985
+ else if (quote === "double" && mode === "@") {
986
+ const closing = closingDoubleQuote(command, index + arrayExpansion[0].length);
987
+ const prefix = doubleQuotedFragment(command.slice(doubleQuoteStart + 1, index));
988
+ const suffix = closing < 0 ? undefined : doubleQuotedFragment(command.slice(index + arrayExpansion[0].length, closing));
989
+ if (closing < 0 || prefix === undefined || suffix === undefined)
990
+ result.push(SHELL_ARRAY_EXPANSION);
991
+ else {
992
+ const presentElements = elements.filter((element) => element !== undefined);
993
+ const expanded = presentElements.length === 0
994
+ ? (prefix.length + suffix.length === 0 ? [] : [`${prefix}${suffix}`])
995
+ : presentElements.map((element, elementIndex) => `${elementIndex === 0 ? prefix : ""}${element}${elementIndex === presentElements.length - 1 ? suffix : ""}`);
996
+ result.splice(doubleQuoteResultIndex);
997
+ result.push((0, shell_quote_1.quote)(expanded));
998
+ quote = null;
999
+ doubleQuoteStart = -1;
1000
+ doubleQuoteResultIndex = -1;
1001
+ index = closing;
1002
+ continue;
1003
+ }
1004
+ }
1005
+ else if (quote === "double")
1006
+ result.push(elements.join(" "));
1007
+ else
1008
+ result.push(shellUnquotedFields(elements));
1009
+ index += arrayExpansion[0].length - 1;
1010
+ continue;
1011
+ }
1012
+ const scalarExpansion = shellMatchAt(SHELL_SCALAR_PARAMETER, command, index);
1013
+ if (scalarExpansion) {
1014
+ const name = scalarExpansion[1] ?? scalarExpansion[2];
1015
+ const scalar = variables.get(name)?.scalar;
1016
+ if (scalar !== undefined) {
1017
+ if (quote === "double")
1018
+ result.push(scalar.replace(/["\\$`]/g, "\\$&"));
1019
+ else
1020
+ result.push(shellUnquotedFields([scalar]));
1021
+ index += scalarExpansion[0].length - 1;
1022
+ continue;
1023
+ }
1024
+ if (quote === null) {
1025
+ result.push(SHELL_ARRAY_EXPANSION);
1026
+ index += scalarExpansion[0].length - 1;
1027
+ continue;
1028
+ }
1029
+ }
1030
+ }
1031
+ result.push(character);
1032
+ }
1033
+ return result.join("");
1034
+ }
1035
+ function findCommandSubstitutionEnd(command, start) {
1036
+ let depth = 1;
1037
+ let quote = null;
1038
+ for (let index = start; index < command.length; index += 1) {
1039
+ const character = command[index];
1040
+ if (character === "\\" && quote !== "single") {
1041
+ index += 1;
1042
+ continue;
1043
+ }
1044
+ if (character === "'" && quote !== "double") {
1045
+ quote = quote === "single" ? null : "single";
1046
+ continue;
1047
+ }
1048
+ if (character === '"' && quote !== "single") {
1049
+ quote = quote === "double" ? null : "double";
1050
+ continue;
1051
+ }
1052
+ if (quote !== null)
1053
+ continue;
1054
+ if (character === "(")
1055
+ depth += 1;
1056
+ else if (character === ")") {
1057
+ depth -= 1;
1058
+ if (depth === 0)
1059
+ return index;
1060
+ }
1061
+ }
1062
+ return -1;
1063
+ }
1064
+ function exposeShellSubstitutions(command, depth = 0) {
1065
+ if (depth >= 32)
1066
+ return "ouro mail sync-cache";
1067
+ let outer = "";
1068
+ const nested = [];
1069
+ let quote = null;
1070
+ for (let index = 0; index < command.length; index += 1) {
1071
+ const character = command[index];
1072
+ if (character === "\\" && quote !== "single") {
1073
+ outer += character + (command[index + 1] ?? "");
1074
+ index += 1;
1075
+ continue;
1076
+ }
1077
+ if (character === "'" && quote !== "double") {
1078
+ quote = quote === "single" ? null : "single";
1079
+ outer += character;
1080
+ continue;
1081
+ }
1082
+ if (character === '"' && quote !== "single") {
1083
+ quote = quote === "double" ? null : "double";
1084
+ outer += character;
1085
+ continue;
1086
+ }
1087
+ if (quote !== "single" && command.startsWith("$(", index) && !command.startsWith("$((", index)) {
1088
+ const end = findCommandSubstitutionEnd(command, index + 2);
1089
+ if (end >= 0) {
1090
+ nested.push(exposeShellSubstitutions(command.slice(index + 2, end), depth + 1));
1091
+ outer += "${__OURO_COMMAND_SUBSTITUTION}";
1092
+ index = end;
1093
+ continue;
1094
+ }
1095
+ }
1096
+ if (quote !== "single" && character === "`") {
1097
+ let end = index + 1;
1098
+ while (end < command.length && command[end] !== "`") {
1099
+ if (command[end] === "\\")
1100
+ end += 1;
1101
+ end += 1;
1102
+ }
1103
+ if (end < command.length) {
1104
+ nested.push(exposeShellSubstitutions(command.slice(index + 1, end), depth + 1));
1105
+ outer += "${__OURO_COMMAND_SUBSTITUTION}";
1106
+ index = end;
1107
+ continue;
1108
+ }
1109
+ }
1110
+ outer += character;
1111
+ }
1112
+ return [outer, ...nested].join("\n");
1113
+ }
1114
+ function normalizeShellNewlines(command) {
1115
+ let result = "";
1116
+ let quote = null;
1117
+ for (let index = 0; index < command.length; index += 1) {
1118
+ const character = command[index];
1119
+ if (character === "\\" && quote !== "single") {
1120
+ result += character + (command[index + 1] ?? "");
1121
+ index += 1;
1122
+ continue;
1123
+ }
1124
+ if (character === "'" && quote !== "double")
1125
+ quote = quote === "single" ? null : "single";
1126
+ else if (character === '"' && quote !== "single")
1127
+ quote = quote === "double" ? null : "double";
1128
+ result += character === "\n" && quote === null ? " ; " : character;
1129
+ }
1130
+ return result;
1131
+ }
1132
+ function executableShellView(command) {
1133
+ const withoutData = stripShellComments(stripHereDocBodies(command));
1134
+ return normalizeShellNewlines(transformOrderedShellVariables(exposeShellSubstitutions(withoutData)));
1135
+ }
1136
+ function parseShellCommands(command) {
1137
+ let parameterIndex = 0;
1138
+ const parameterNames = new Map();
1139
+ const entries = (0, shell_quote_1.parse)(command, (name) => {
1140
+ const marker = `${SHELL_PARAMETER_MARKER}${parameterIndex}__`;
1141
+ parameterIndex += 1;
1142
+ parameterNames.set(marker, name);
1143
+ return marker;
1144
+ });
1145
+ const commands = [];
1146
+ let current = [];
1147
+ let skipRedirectionTarget = false;
1148
+ const flush = () => {
1149
+ if (current.length > 0)
1150
+ commands.push(current);
1151
+ current = [];
1152
+ skipRedirectionTarget = false;
1153
+ };
1154
+ for (const entry of entries) {
1155
+ if (typeof entry === "string") {
1156
+ if (skipRedirectionTarget)
1157
+ skipRedirectionTarget = false;
1158
+ else if (SHELL_TOKEN_BOUNDARIES.has(entry.toLowerCase()))
1159
+ flush();
1160
+ else
1161
+ current.push(entry);
1162
+ continue;
1163
+ }
1164
+ const operation = entry;
1165
+ if (operation.op === "glob") {
1166
+ current.push(operation.pattern);
1167
+ continue;
1168
+ }
1169
+ if (SHELL_COMMAND_BOUNDARIES.has(operation.op)) {
1170
+ flush();
1171
+ continue;
1172
+ }
1173
+ // Every remaining control operator is a redirection. Its next string is
1174
+ // a path or descriptor, not an argv token.
1175
+ skipRedirectionTarget = true;
1176
+ }
1177
+ flush();
1178
+ return { commands, parameterNames };
1179
+ }
1180
+ function resolveKnownShellParameters(token, knownVariables, parameterNames) {
1181
+ let resolved = token;
1182
+ for (const [marker, name] of parameterNames) {
1183
+ const known = knownVariables.get(name);
1184
+ if (known !== undefined)
1185
+ resolved = resolved.replaceAll(marker, known);
1186
+ }
1187
+ return resolved;
1188
+ }
1189
+ function shellTokenCanEqual(token, expected) {
1190
+ const escaped = token
1191
+ .split(new RegExp(`(?:${SHELL_PARAMETER_MARKER}\\d+__|${SHELL_ARRAY_EXPANSION})`, "g"))
1192
+ .map((literal) => literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
1193
+ .join(".*");
1194
+ return new RegExp(`^${escaped}$`, "i").test(expected);
1195
+ }
1196
+ function shellExecutableCanEqual(token, expected) {
1197
+ const basenameToken = token.slice(token.lastIndexOf("/") + 1);
1198
+ return shellTokenCanEqual(basenameToken, expected);
1199
+ }
1200
+ function exactShellExecutable(token) {
1201
+ if (token === undefined || token.includes(SHELL_PARAMETER_MARKER))
1202
+ return undefined;
1203
+ return token.slice(token.lastIndexOf("/") + 1).toLowerCase();
1204
+ }
1205
+ const SUDO_SHORT_OPERANDS = new Set(["-C", "-D", "-g", "-h", "-p", "-R", "-r", "-T", "-t", "-U", "-u"]);
1206
+ const SUDO_LONG_OPERANDS = new Set(["--chdir", "--chroot", "--close-from", "--command-timeout", "--group", "--host", "--other-user", "--prompt", "--role", "--type", "--user"]);
1207
+ const TIME_SHORT_OPERANDS = new Set(["-f", "-o"]);
1208
+ const TIME_LONG_OPERANDS = new Set(["--format", "--output"]);
1209
+ function wrapperOptionAdvance(option, shortOperands, longOperands) {
1210
+ if (shortOperands.has(option) || longOperands.has(option))
1211
+ return 2;
1212
+ if ([...shortOperands].some((candidate) => option.startsWith(candidate) && option.length > candidate.length))
1213
+ return 1;
1214
+ if ([...longOperands].some((candidate) => option.startsWith(`${candidate}=`)))
1215
+ return 1;
1216
+ return 0;
1217
+ }
1218
+ function resolveShellCommand(tokens, startIndex) {
1219
+ let index = startIndex;
1220
+ while (index < tokens.length) {
1221
+ const executable = exactShellExecutable(tokens[index]);
1222
+ if (SHELL_GRAMMAR_PREFIXES.has(executable ?? "")) {
1223
+ index += 1;
1224
+ continue;
1225
+ }
1226
+ if (executable === "coproc") {
1227
+ index += 1;
1228
+ const candidate = tokens[index] ?? "";
1229
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(candidate) && !shellExecutableCanEqual(candidate, "ouro") && tokens[index + 1] !== undefined)
1230
+ index += 1;
1231
+ continue;
1232
+ }
1233
+ if (executable === "command" || executable === "nohup") {
1234
+ index += 1;
1235
+ if (executable === "command" && (tokens[index] === "-v" || tokens[index] === "-V"))
1236
+ return { index: tokens.length };
1237
+ while (tokens[index]?.startsWith("-"))
1238
+ index += 1;
1239
+ continue;
1240
+ }
1241
+ if (executable === "env") {
1242
+ index += 1;
1243
+ while (index < tokens.length) {
1244
+ const option = tokens[index];
1245
+ if (option === "-S" || option === "--split-string")
1246
+ return { index, script: `env ${tokens.slice(index + 1).join(" ")}` };
1247
+ if (option.startsWith("--split-string="))
1248
+ return { index, script: `env ${[option.slice("--split-string=".length), ...tokens.slice(index + 1)].join(" ")}` };
1249
+ if (["-u", "-C", "--unset", "--chdir"].includes(option))
1250
+ index += 2;
1251
+ else if (option.startsWith("-") || /^[A-Za-z_][A-Za-z0-9_]*=/.test(option))
1252
+ index += 1;
1253
+ else
1254
+ break;
1255
+ }
1256
+ continue;
1257
+ }
1258
+ if (executable === "sudo") {
1259
+ index += 1;
1260
+ while (tokens[index]?.startsWith("-")) {
1261
+ const advance = wrapperOptionAdvance(tokens[index], SUDO_SHORT_OPERANDS, SUDO_LONG_OPERANDS);
1262
+ index += advance === 0 ? 1 : advance;
1263
+ }
1264
+ continue;
1265
+ }
1266
+ if (executable === "time") {
1267
+ index += 1;
1268
+ while (tokens[index]?.startsWith("-")) {
1269
+ const advance = wrapperOptionAdvance(tokens[index], TIME_SHORT_OPERANDS, TIME_LONG_OPERANDS);
1270
+ index += advance === 0 ? 1 : advance;
1271
+ }
1272
+ continue;
1273
+ }
1274
+ if (executable === "exec") {
1275
+ index += 1;
1276
+ while (tokens[index]?.startsWith("-"))
1277
+ index += tokens[index] === "-a" ? 2 : 1;
1278
+ continue;
1279
+ }
1280
+ break;
1281
+ }
1282
+ return { index };
1283
+ }
1284
+ function shellScriptRequiresFamily(script, variables, depth) {
1285
+ if (script.includes(SHELL_PARAMETER_MARKER) || script.includes(SHELL_ARRAY_EXPANSION))
1286
+ return true;
1287
+ return shellCommandsContainMailCacheSync(script, variables, depth + 1);
1288
+ }
1289
+ function shellTokensContainMailCacheSync(tokens, startIndex, variables, depth) {
1290
+ const resolved = resolveShellCommand(tokens, startIndex);
1291
+ if (resolved.script !== undefined)
1292
+ return shellScriptRequiresFamily(resolved.script, variables, depth);
1293
+ const executableToken = tokens[resolved.index] ?? "";
1294
+ const executable = exactShellExecutable(executableToken);
1295
+ if (executableToken.includes(SHELL_ARRAY_EXPANSION))
1296
+ return true;
1297
+ if (SHELL_INTERPRETERS.has(executable ?? "")) {
1298
+ const commandOption = tokens.findIndex((token, index) => index > resolved.index && /^-[^-]*c/.test(token));
1299
+ return commandOption >= 0
1300
+ && shellScriptRequiresFamily(tokens[commandOption + 1] ?? "", variables, depth);
1301
+ }
1302
+ if (executable === "eval")
1303
+ return shellScriptRequiresFamily(tokens.slice(resolved.index + 1).join(" "), variables, depth);
1304
+ return shellExecutableCanEqual(executableToken, "ouro")
1305
+ && shellTokenCanEqual(tokens[resolved.index + 1] ?? "", "mail")
1306
+ && shellTokenCanEqual(tokens[resolved.index + 2] ?? "", "sync-cache");
1307
+ }
1308
+ function shellCommandsContainMailCacheSync(command, inheritedVariables = new Map(), depth = 0) {
1309
+ if (depth >= 32)
1310
+ return true;
1311
+ let parsed;
1312
+ try {
1313
+ parsed = parseShellCommands(command);
1314
+ }
1315
+ catch {
1316
+ return false;
1317
+ }
1318
+ const knownVariables = new Map(inheritedVariables);
1319
+ for (const rawTokens of parsed.commands) {
1320
+ const tokens = rawTokens.map((token) => resolveKnownShellParameters(token, knownVariables, parsed.parameterNames));
1321
+ const invocationVariables = new Map(knownVariables);
1322
+ let commandIndex = 0;
1323
+ while (commandIndex < tokens.length) {
1324
+ const assignment = tokens[commandIndex].match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s);
1325
+ if (!assignment)
1326
+ break;
1327
+ if (assignment[2].includes(SHELL_PARAMETER_MARKER))
1328
+ invocationVariables.delete(assignment[1]);
1329
+ else
1330
+ invocationVariables.set(assignment[1], assignment[2]);
1331
+ commandIndex += 1;
1332
+ }
1333
+ if (commandIndex === tokens.length) {
1334
+ knownVariables.clear();
1335
+ for (const [name, value] of invocationVariables)
1336
+ knownVariables.set(name, value);
1337
+ continue;
1338
+ }
1339
+ if (shellTokensContainMailCacheSync(tokens, commandIndex, invocationVariables, depth))
1340
+ return true;
1341
+ }
1342
+ return false;
1343
+ }
1344
+ function shellContainsMailCacheSync(command) {
1345
+ const executableCommand = executableShellView(command);
1346
+ if (shellContainsAmbiguousAnsiMailCacheSync(executableCommand))
1347
+ return true;
1348
+ return shellCommandsContainMailCacheSync(executableCommand);
1349
+ }
1350
+ function mailCacheSyncShellGuardrail(toolName, args, context) {
1351
+ if (toolName !== "shell" || !shellContainsMailCacheSync(args.command ?? ""))
1352
+ return allow;
1353
+ if (context.trustLevel === undefined || context.trustLevel === "family")
1354
+ return allow;
1355
+ return deny("hosted mail cache convergence requires family trust.");
1356
+ }
1357
+ function mailTrustGuardrail(toolName, args, context) {
1358
+ if (MAIL_FAMILY_TOOLS.has(toolName)) {
1359
+ if (context.trustLevel === undefined || context.trustLevel === "family")
1360
+ return allow;
1361
+ if (toolName === "mail_send")
1362
+ return deny("outbound mail sends require family trust.");
1363
+ return deny(toolName === "mail_decide"
1364
+ ? "mail screener decisions require family trust."
1365
+ : "delegated human mail requires family trust.");
1366
+ }
1367
+ if (MAIL_DELEGATED_READ_TOOLS.has(toolName)) {
1368
+ const scope = (args.scope ?? "").trim().toLowerCase();
1369
+ if (scope === "delegated" || scope === "all") {
1370
+ if (context.trustLevel === undefined || context.trustLevel === "family")
1371
+ return allow;
1372
+ return deny("delegated human mail requires family trust.");
1373
+ }
1374
+ }
1375
+ return allow;
1376
+ }
1377
+ function checkCredentialTrustGuardrails(toolName, context) {
1378
+ if (CREDENTIAL_FAMILY_TOOLS.has(toolName) || COMMERCE_FAMILY_TOOLS.has(toolName)) {
1379
+ if (context.trustLevel === "family")
1380
+ return allow;
1381
+ return deny(REASONS.needsTrust);
1382
+ }
1383
+ if (CREDENTIAL_TRUSTED_TOOLS.has(toolName) || TRAVEL_TRUSTED_TOOLS.has(toolName) || A2A_TRUSTED_TOOLS.has(toolName)) {
1384
+ if ((0, friends_1.isTrustedLevel)(context.trustLevel))
1385
+ return allow;
1386
+ return deny(REASONS.needsTrust);
1387
+ }
1388
+ return allow;
1389
+ }
1390
+ function checkCommerceAuthorityGuardrails(toolName, args, context) {
1391
+ if (!COMMERCE_AUTHORITY_TOOLS.has(toolName))
1392
+ return allow;
1393
+ if (!context.agentRoot)
1394
+ return deny("commerce authority unavailable: agent root could not be resolved.");
1395
+ const result = (0, store_1.validateCommerceAuthority)({
1396
+ agentRoot: context.agentRoot,
1397
+ token: args.commerce_authority,
1398
+ toolName,
1399
+ args,
1400
+ friendId: context.friendId,
1401
+ });
1402
+ if (result.ok)
1403
+ return allow;
1404
+ return deny(`commerce authority required: ${result.reason}`);
1405
+ }
1406
+ function checkFirstClassMcpTrust(context) {
1407
+ if (!context.mcpServerName)
1408
+ return allow;
1409
+ const rules = MCP_SERVER_TRUST[context.mcpServerName] ?? { minTrust: "friend", blockGroupChat: false };
1410
+ if (!trustLevelSatisfied(rules.minTrust, context.trustLevel ?? "friend")) {
1411
+ return deny(REASONS.needsTrust);
1412
+ }
1413
+ if (rules.blockGroupChat && context.isGroupChat) {
1414
+ return deny("browser tools are only available in 1:1 conversations, not group chats.");
1415
+ }
1416
+ return allow;
1417
+ }
264
1418
  function checkTrustLevelGuardrails(toolName, args, context) {
265
- // Trusted levels (family/friend) — no trust guardrails. Undefined defaults to friend.
266
- if ((0, types_1.isTrustedLevel)(context.trustLevel))
1419
+ if (toolName === "teams_send_image" && context.trustLevel !== "family") {
1420
+ return deny("sending local images in Teams requires family trust.");
1421
+ }
1422
+ const cacheSyncResult = mailCacheSyncShellGuardrail(toolName, args, context);
1423
+ if (!cacheSyncResult.allowed)
1424
+ return cacheSyncResult;
1425
+ const mailResult = mailTrustGuardrail(toolName, args, context);
1426
+ if (!mailResult.allowed)
1427
+ return mailResult;
1428
+ if (SHEPHERD_FAMILY_TOOLS.has(toolName) && context.trustLevel !== "family") {
1429
+ return deny("the owner's terminals require family trust.");
1430
+ }
1431
+ // Credential tools have their own trust rules that apply at all levels
1432
+ const credentialResult = checkCredentialTrustGuardrails(toolName, context);
1433
+ if (!credentialResult.allowed)
1434
+ return credentialResult;
1435
+ const commerceAuthorityResult = checkCommerceAuthorityGuardrails(toolName, args, context);
1436
+ if (!commerceAuthorityResult.allowed)
1437
+ return commerceAuthorityResult;
1438
+ // First-class MCP tool trust (e.g. browser_navigate) — applies at all trust levels
1439
+ const firstClassMcpResult = checkFirstClassMcpTrust(context);
1440
+ if (!firstClassMcpResult.allowed)
1441
+ return firstClassMcpResult;
1442
+ // MCP server-specific trust via shell (e.g. ouro mcp call browser) — applies at all trust levels
1443
+ if (toolName === "shell") {
1444
+ const mcpResult = checkMcpServerTrust(args.command || "", context);
1445
+ if (!mcpResult.allowed)
1446
+ return mcpResult;
1447
+ }
1448
+ // Trusted levels (family/friend) — no further trust guardrails. Undefined defaults to friend.
1449
+ if ((0, friends_1.isTrustedLevel)(context.trustLevel))
267
1450
  return allow;
268
1451
  if (toolName === "shell") {
269
1452
  return checkShellTrustGuardrails(args.command || "", context.trustLevel);
270
1453
  }
271
1454
  return checkWriteTrustGuardrails(toolName, args, context);
272
1455
  }
1456
+ // --- git aimed at this agent's own bundle ---
1457
+ // Sync owns the bundle's repository: it commits, merges and pushes under its own lock. A git command from the shell that
1458
+ // works in the bundle, or points git at it, is refused. Every directory the command could be working in counts: where the
1459
+ // shell starts, each cd, pushd, env -C and git -C, and the --git-dir, --work-tree, GIT_DIR and GIT_WORK_TREE it names.
1460
+ // A git call whose directory the guard cannot work out (a variable, a glob, cd -) is refused too.
1461
+ exports.BUNDLE_GIT_REFUSAL = "my bundle syncs itself; I don't run git in it.";
1462
+ const GIT_PATH_VARIABLES = new Set(["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR"]);
1463
+ const GIT_GUARD_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
1464
+ /** The commands a runner (xargs, find -exec, timeout) can start that the scan follows into. */
1465
+ const RUNNER_GIT_TARGETS = new Set(["git", "env", "eval", ...GIT_GUARD_SHELLS]);
1466
+ const DECLARATION_BUILTINS = new Set(["export", "declare", "typeset", "local", "readonly"]);
1467
+ // Commands that run another command given as their arguments; the guard looks for git (or a shell) among those arguments.
1468
+ const COMMAND_RUNNERS = new Set(["xargs", "find", "timeout", "nice", "ionice", "stdbuf", "setsid", "flock", "watch", "parallel", "doas", "chronic", "unbuffer", "chroot"]);
1469
+ /** git global options that take their value as the next word. */
1470
+ const GIT_VALUE_OPTIONS = new Set(["--namespace", "--exec-path", "--super-prefix", "--attr-source"]);
1471
+ function unknownShellWord(word) {
1472
+ return word.includes(SHELL_PARAMETER_MARKER) || word.includes(SHELL_ARRAY_EXPANSION) || /[*?[\]{}]/.test(word);
1473
+ }
1474
+ function shellPathWord(word) {
1475
+ return word === undefined || unknownShellWord(word) ? null : expandHome(word);
1476
+ }
1477
+ function noteGitAssignment(token, scan, known) {
1478
+ const assignment = token.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s);
1479
+ if (!assignment)
1480
+ return false;
1481
+ const [, name, value] = assignment;
1482
+ if (GIT_PATH_VARIABLES.has(name))
1483
+ scan.gitPaths.push(value);
1484
+ if (/^GIT_CONFIG(?:_|$)/.test(name))
1485
+ scan.configEnv = true;
1486
+ if (name === "CDPATH")
1487
+ scan.cdpath = true;
1488
+ if (unknownShellWord(value))
1489
+ known.delete(name);
1490
+ else
1491
+ known.set(name, value);
1492
+ return true;
1493
+ }
1494
+ function scanShellForGit(command, scan, depth) {
1495
+ if (depth >= 32) {
1496
+ scan.opaqueShell = true;
1497
+ return;
1498
+ }
1499
+ const parsed = parseShellCommands(executableShellView(command));
1500
+ const known = new Map();
1501
+ for (const name of STABLE_VARIABLES)
1502
+ if (process.env[name])
1503
+ known.set(name, process.env[name]);
1504
+ for (const raw of parsed.commands) {
1505
+ const tokens = raw.map((token) => resolveKnownShellParameters(token, known, parsed.parameterNames));
1506
+ let index = 0;
1507
+ while (index < tokens.length && noteGitAssignment(tokens[index], scan, known))
1508
+ index += 1;
1509
+ if (DECLARATION_BUILTINS.has(tokens[index] ?? "")) {
1510
+ for (const token of tokens.slice(index + 1))
1511
+ noteGitAssignment(token, scan, known);
1512
+ continue;
1513
+ }
1514
+ if (index < tokens.length)
1515
+ scanCommandForGit(tokens, index, scan, known, depth);
1516
+ }
1517
+ }
1518
+ function scanCommandForGit(tokens, start, scan, known, depth) {
1519
+ let index = start;
1520
+ for (let hops = 0; hops <= tokens.length; hops += 1) {
1521
+ const resolved = resolveShellCommand(tokens, index);
1522
+ // A wrapper's own options: env -C and sudo --chdir change the directory, env NAME=value sets git's variables.
1523
+ for (let k = index; k < resolved.index; k += 1) {
1524
+ const word = tokens[k];
1525
+ if (word === "-C" || word === "--chdir")
1526
+ scan.cwdTargets.push(shellPathWord(tokens[k + 1]));
1527
+ else if (word.startsWith("--chdir="))
1528
+ scan.cwdTargets.push(shellPathWord(word.slice("--chdir=".length)));
1529
+ else if (/^-C./.test(word))
1530
+ scan.cwdTargets.push(shellPathWord(word.slice(2)));
1531
+ else
1532
+ noteGitAssignment(word, scan, known);
1533
+ }
1534
+ if (resolved.script !== undefined)
1535
+ return scanShellForGit(resolved.script, scan, depth + 1);
1536
+ index = resolved.index;
1537
+ if (index >= tokens.length)
1538
+ return;
1539
+ // A command word the guard cannot read ($RUN git status) could be anything, git or a wrapper around it.
1540
+ if (unknownShellWord(tokens[index])) {
1541
+ if (tokens.slice(index).some((word) => /(?:^|\/)git$/.test(word)))
1542
+ scan.opaqueShell = true;
1543
+ return;
1544
+ }
1545
+ const executable = exactShellExecutable(tokens[index]);
1546
+ if (executable === "git") {
1547
+ scan.calls.push(tokens.slice(index + 1));
1548
+ return;
1549
+ }
1550
+ if (executable === "cd" || executable === "pushd") {
1551
+ const target = tokens.slice(index + 1).find((word) => !/^-[LPe@]+$/.test(word));
1552
+ const viaCdpath = target !== undefined && scan.cdpath && !path.isAbsolute(expandHome(target));
1553
+ scan.cwdTargets.push(target === undefined ? (executable === "cd" ? os.homedir() : null) : target === "-" || viaCdpath ? null : shellPathWord(target));
1554
+ return;
1555
+ }
1556
+ if (GIT_GUARD_SHELLS.has(executable)) {
1557
+ const option = tokens.findIndex((word, k) => k > index && /^-[^-]*c/.test(word));
1558
+ if (option >= 0)
1559
+ return scanShellForGit(tokens[option + 1] ?? "", scan, depth + 1);
1560
+ if (tokens.slice(index + 1).every((word) => word.startsWith("-")))
1561
+ scan.opaqueShell = true;
1562
+ return;
1563
+ }
1564
+ if (executable === "eval")
1565
+ return scanShellForGit(tokens.slice(index + 1).join(" "), scan, depth + 1);
1566
+ if (!COMMAND_RUNNERS.has(executable))
1567
+ return;
1568
+ // find -execdir runs its command in each directory it finds.
1569
+ if (executable === "find" && tokens.some((word, k) => k > index && (word === "-execdir" || word === "-okdir")))
1570
+ scan.cwdTargets.push(null);
1571
+ // xargs -I/-i/--replace puts its input into the command's words.
1572
+ if (executable === "xargs" && tokens.some((word, k) => k > index && /^(?:-I|-i|--replace)/.test(word)))
1573
+ scan.cwdTargets.push(null);
1574
+ const next = tokens.findIndex((word, k) => k > index && RUNNER_GIT_TARGETS.has(exactShellExecutable(word)));
1575
+ if (next < 0)
1576
+ return;
1577
+ index = next;
1578
+ }
1579
+ }
1580
+ /** Where one git call works and what it names: its directories after -C, then --git-dir, --work-tree, and init or clone targets. Null when a word is unknown. */
1581
+ function gitCallPaths(args, cwds) {
1582
+ let dirs = [...cwds];
1583
+ const named = [];
1584
+ let index = 0;
1585
+ const value = (word) => shellPathWord(word);
1586
+ while (index < args.length) {
1587
+ const word = args[index];
1588
+ if (word === "-C") {
1589
+ const dir = value(args[index + 1]);
1590
+ if (dir === null)
1591
+ return null;
1592
+ dirs = dirs.map((cwd) => path.isAbsolute(dir) ? dir : cwd === null ? null : path.resolve(cwd, dir));
1593
+ index += 2;
1594
+ continue;
1595
+ }
1596
+ if (word === "--git-dir" || word === "--work-tree") {
1597
+ const dir = value(args[index + 1]);
1598
+ if (dir === null)
1599
+ return null;
1600
+ named.push(dir);
1601
+ index += 2;
1602
+ continue;
1603
+ }
1604
+ if (word.startsWith("--git-dir=") || word.startsWith("--work-tree=")) {
1605
+ const dir = value(word.slice(word.indexOf("=") + 1));
1606
+ if (dir === null)
1607
+ return null;
1608
+ named.push(dir);
1609
+ index += 1;
1610
+ continue;
1611
+ }
1612
+ if (word === "-c") {
1613
+ const setting = args[index + 1] ?? "";
1614
+ // An alias can run any command, git in any directory included.
1615
+ if (/^alias\./i.test(setting))
1616
+ return null;
1617
+ const worktree = setting.match(/^core\.worktree=(.*)$/is);
1618
+ if (worktree) {
1619
+ const dir = value(worktree[1]);
1620
+ if (dir === null)
1621
+ return null;
1622
+ named.push(dir);
1623
+ }
1624
+ index += 2;
1625
+ continue;
1626
+ }
1627
+ if (word === "--config-env" || word.startsWith("--config-env="))
1628
+ return null;
1629
+ if (GIT_VALUE_OPTIONS.has(word)) {
1630
+ index += 2;
1631
+ continue;
1632
+ }
1633
+ if (word.startsWith("-")) {
1634
+ index += 1;
1635
+ continue;
1636
+ }
1637
+ // The subcommand. init and clone also name a directory to create or fill.
1638
+ const positional = args.slice(index + 1).filter((arg) => !arg.startsWith("-"));
1639
+ const separate = args.slice(index + 1).flatMap((arg, k, rest) => arg === "--separate-git-dir" ? [rest[k + 1]] : arg.startsWith("--separate-git-dir=") ? [arg.slice("--separate-git-dir=".length)] : []);
1640
+ const targets = word === "init" ? [...positional.slice(-1), ...separate] : word === "clone" && positional.length >= 2 ? [...positional.slice(-1), ...separate] : [];
1641
+ for (const target of targets) {
1642
+ const dir = value(target);
1643
+ if (dir === null)
1644
+ return null;
1645
+ named.push(dir);
1646
+ }
1647
+ break;
1648
+ }
1649
+ if (dirs.some((dir) => dir === null))
1650
+ return null;
1651
+ const known = dirs;
1652
+ return [...known, ...named.flatMap((dir) => path.isAbsolute(dir) ? [dir] : known.map((cwd) => path.resolve(cwd, dir)))];
1653
+ }
1654
+ /** Whether a shell command could run git in the agent's bundle. Exported for tests. */
1655
+ function bundleGitRefused(command, agentRoot) {
1656
+ // Quotes and backslashes can split the word (g''it, "g"it, g\it); the shell joins it back.
1657
+ const plain = command.replace(/['"\\]/g, "");
1658
+ if (!agentRoot || !plain.includes("git"))
1659
+ return false;
1660
+ try {
1661
+ const scan = { cwdTargets: [], gitPaths: [], calls: [], opaqueShell: false, configEnv: false, cdpath: !!process.env.CDPATH };
1662
+ scanShellForGit(command, scan, 0);
1663
+ if (scan.opaqueShell && /(?:^|[^A-Za-z0-9_-])git(?:$|[^A-Za-z0-9_-])/.test(plain))
1664
+ return true;
1665
+ if (scan.calls.length === 0)
1666
+ return false;
1667
+ // Every directory the command could be working in: where the shell starts, and each change, from each earlier one.
1668
+ const cwds = [process.cwd()];
1669
+ for (const target of scan.cwdTargets) {
1670
+ if (target === null)
1671
+ cwds.push(null);
1672
+ else
1673
+ cwds.push(...(path.isAbsolute(target) ? [target] : cwds.map((cwd) => cwd === null ? null : path.resolve(cwd, target))));
1674
+ }
1675
+ const roots = [path.resolve(agentRoot), canonicalize(path.resolve(agentRoot))];
1676
+ // git config from the environment can hide a git aimed at the bundle in an alias: refuse it when the command names the bundle.
1677
+ if (scan.configEnv && [...roots, path.basename(agentRoot)].some((root) => plain.includes(root)))
1678
+ return true;
1679
+ const inBundle = (target) => [target, canonicalize(target)].some((form) => roots.some((root) => form === root || form.startsWith(`${root}${path.sep}`)));
1680
+ for (const call of scan.calls) {
1681
+ const paths = gitCallPaths(call, cwds);
1682
+ if (paths === null)
1683
+ return true;
1684
+ const variables = scan.gitPaths.map((value) => shellPathWord(value));
1685
+ if (variables.some((value) => value === null))
1686
+ return true;
1687
+ const bases = paths.slice(0, cwds.length);
1688
+ const named = variables.flatMap((value) => path.isAbsolute(value) ? [value] : bases.map((base) => path.resolve(base, value)));
1689
+ if ([...paths, ...named].some(inBundle))
1690
+ return true;
1691
+ }
1692
+ return false;
1693
+ }
1694
+ catch {
1695
+ // The command could not be parsed, or the filesystem could not say where a path lands: refuse rather than guess.
1696
+ return true;
1697
+ }
1698
+ }
1699
+ // --- the sync key's directory, closed to the read-only file tools ---
1700
+ /** The directories read_file, glob and grep would read for these arguments, as the tools resolve them. */
1701
+ function readToolTargets(toolName, args) {
1702
+ const local = (p) => path.isAbsolute(p) ? p : path.resolve((0, identity_1.getRepoRoot)(), p);
1703
+ if (toolName === "read_file")
1704
+ return [{ target: local(args.path ?? ""), recursive: false }];
1705
+ if (toolName === "grep")
1706
+ return [{ target: local(args.path ?? ""), recursive: true }];
1707
+ const cwd = args.cwd ? local(args.cwd) : process.cwd();
1708
+ // The part of the pattern before its first glob segment is a directory the glob reads.
1709
+ const segments = (args.pattern ?? "").split("/");
1710
+ const firstGlob = segments.findIndex((segment) => /[*?[\]{}()!]/.test(segment));
1711
+ const literal = (firstGlob < 0 ? segments : segments.slice(0, firstGlob)).join("/");
1712
+ return [{ target: cwd, recursive: false }, { target: path.resolve(cwd, literal || "."), recursive: false }];
1713
+ }
1714
+ function syncSecretReadRefused(toolName, args) {
1715
+ try {
1716
+ const lexical = path.resolve((0, sync_1.syncSecretDir)());
1717
+ const secrets = [lexical, canonicalize(lexical)];
1718
+ return readToolTargets(toolName, args).some(({ target, recursive }) => [target, canonicalize(target)].some((form) => secrets.some((secret) => form === secret || form.startsWith(`${secret}${path.sep}`) || (recursive && (form === path.parse(form).root || secret.startsWith(`${form}${path.sep}`))))));
1719
+ }
1720
+ catch {
1721
+ return true;
1722
+ }
1723
+ }
1724
+ /**
1725
+ * A shell command that names the key's directory. The agent's uid cannot read
1726
+ * the key; this only turns an accidental attempt into a plain refusal.
1727
+ */
1728
+ function syncSecretShellRefused(command) {
1729
+ return command.includes(path.resolve((0, sync_1.syncSecretDir)()));
1730
+ }
273
1731
  // --- main entry point ---
274
1732
  function guardInvocation(toolName, args, context) {
275
1733
  (0, runtime_1.emitNervesEvent)({
@@ -278,9 +1736,9 @@ function guardInvocation(toolName, args, context) {
278
1736
  message: "guardrail check",
279
1737
  meta: { toolName },
280
1738
  });
281
- // Read-only tools are always allowed (no structural or trust guardrails)
1739
+ // Read-only tools skip the structural and trust guardrails; only the sync key's directory is closed to them.
282
1740
  if (READ_ONLY_TOOLS.has(toolName))
283
- return allow;
1741
+ return syncSecretReadRefused(toolName, args) ? deny(REASONS.unreadablePath) : allow;
284
1742
  // Layer 1: structural guardrails (always on)
285
1743
  const structuralResult = checkStructuralGuardrails(toolName, args, context);
286
1744
  if (!structuralResult.allowed)