@cohortapp/agent-sdk 2.16.0 → 2.18.4

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 (529) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +23 -7
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/poller-daemon-setup.md +4 -1
  9. package/docs/guides/setup-wizard.md +1 -1
  10. package/docs/runbooks/fleet-rollout.md +156 -0
  11. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  12. package/lib/action-executor.js +19 -3
  13. package/lib/budget-guard.mjs +279 -3
  14. package/lib/channels/base-adapter.mjs +3 -1
  15. package/lib/channels/contract.mjs +2 -1
  16. package/lib/channels/inbox-item.mjs +8 -0
  17. package/lib/claude-bin.mjs +5 -6
  18. package/lib/cli/doctor-checks.mjs +141 -10
  19. package/lib/cli/global-setup-extras.mjs +5 -1
  20. package/lib/cli/inbox.mjs +100 -15
  21. package/lib/cli/seat-auth.mjs +463 -0
  22. package/lib/cli/session.mjs +80 -12
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/global-config.mjs +63 -1
  25. package/lib/collective/presence.mjs +142 -5
  26. package/lib/comms/send-gate.mjs +559 -1
  27. package/lib/context/budget.mjs +327 -0
  28. package/lib/context/history-scope.mjs +138 -0
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/economics.mjs +9 -0
  138. package/lib/model-router/failover.mjs +67 -16
  139. package/lib/model-router/llm-task.mjs +39 -3
  140. package/lib/model-router/resolve.mjs +95 -3
  141. package/lib/model-router/spawn.mjs +46 -47
  142. package/lib/model-router/taxonomy.mjs +126 -4
  143. package/lib/org/cost-sync.mjs +141 -11
  144. package/lib/org/inbound/broadcast.mjs +289 -0
  145. package/lib/org/inbound/collective.mjs +375 -0
  146. package/lib/org/inbound/directedness.mjs +96 -8
  147. package/lib/org/inbound/facts.mjs +82 -4
  148. package/lib/org/inbound/hydrate.mjs +555 -51
  149. package/lib/org/inbound/project.mjs +22 -0
  150. package/lib/org/inbound/surfaces.mjs +14 -0
  151. package/lib/org/llm-token.mjs +879 -0
  152. package/lib/org/mesh.mjs +61 -0
  153. package/lib/org/messaging.mjs +3 -1
  154. package/lib/org/protocol.checksum +1 -1
  155. package/lib/org/protocol.mjs +15 -0
  156. package/lib/org/quota.mjs +520 -0
  157. package/lib/org/tool-surface.mjs +104 -16
  158. package/lib/org/ui-parity.mjs +16 -1
  159. package/lib/org/work-ledger.mjs +37 -6
  160. package/lib/rate-guard.mjs +114 -1
  161. package/lib/resource-governor.mjs +41 -6
  162. package/lib/runtime/adapter.mjs +823 -0
  163. package/lib/runtime/child-env.mjs +191 -0
  164. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  165. package/lib/runtime/seat-engine.mjs +162 -0
  166. package/lib/session/ask-ledger.mjs +271 -0
  167. package/lib/session/current-work.mjs +676 -0
  168. package/lib/session/feed-core.mjs +40 -3
  169. package/lib/session/launch-args.mjs +56 -4
  170. package/lib/session/status-summary.mjs +26 -9
  171. package/lib/session/upgrade-notice.mjs +42 -0
  172. package/lib/setup/claude-probe.mjs +117 -13
  173. package/lib/setup/enrich.mjs +13 -10
  174. package/lib/setup/sections/model.mjs +39 -13
  175. package/lib/telemetry/collect.mjs +208 -9
  176. package/lib/upgrade/ignored-drift.mjs +105 -0
  177. package/lib/voice/post-call-brief.mjs +30 -17
  178. package/package.json +15 -3
  179. package/plugins/maestro-skills/skills/board-work.md +5 -0
  180. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  181. package/plugins/maestro-skills/skills/main-session.md +18 -7
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +29 -2
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +104 -59
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +804 -157
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/lib/session-router.mjs +310 -42
  198. package/scripts/daemon/maestro-daemon.mjs +11 -0
  199. package/scripts/daemon/prompt-builder.mjs +121 -12
  200. package/scripts/daemon/responder.mjs +315 -146
  201. package/scripts/daemon/sdk-version.mjs +98 -16
  202. package/scripts/eval/probe-gateway.mjs +635 -0
  203. package/scripts/eval/replay/extract.mjs +270 -0
  204. package/scripts/eval/replay/grade.mjs +260 -0
  205. package/scripts/eval/replay/lib/config.mjs +50 -0
  206. package/scripts/eval/replay/lib/effects.mjs +65 -0
  207. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  208. package/scripts/eval/replay/lib/judge.mjs +72 -0
  209. package/scripts/eval/replay/lib/redact.mjs +136 -0
  210. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  211. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  212. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  213. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  214. package/scripts/eval/replay/report.mjs +185 -0
  215. package/scripts/eval/replay/run.mjs +404 -0
  216. package/scripts/fleet/rollout.mjs +1094 -0
  217. package/scripts/hooks/pre-send-audit.sh +36 -245
  218. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  219. package/scripts/hooks/validate-state-yaml.sh +190 -0
  220. package/scripts/huddle/huddle-llm.mjs +361 -0
  221. package/scripts/huddle/huddle-server.mjs +46 -121
  222. package/scripts/local-triggers/autoupdate.sh +448 -78
  223. package/scripts/local-triggers/run-trigger.sh +13 -0
  224. package/scripts/maintenance/pin-integrity.mjs +364 -0
  225. package/scripts/poll-slack-events.sh +41 -9
  226. package/scripts/poller/slack-socket-mode.mjs +28 -3
  227. package/scripts/session/supervisor.mjs +80 -13
  228. package/scripts/spawn-session.sh +13 -0
  229. package/bin/maestro.test.mjs +0 -1574
  230. package/lib/action-executor.test.mjs +0 -871
  231. package/lib/archetype.test.mjs +0 -132
  232. package/lib/assurance/plan-note.test.mjs +0 -234
  233. package/lib/assurance/room-budget.test.mjs +0 -486
  234. package/lib/assurance/tier.test.mjs +0 -174
  235. package/lib/autonomy.test.mjs +0 -66
  236. package/lib/backlog.test.mjs +0 -302
  237. package/lib/backup/policy.test.mjs +0 -305
  238. package/lib/budget-escalate.test.mjs +0 -232
  239. package/lib/budget-guard.envelope.test.mjs +0 -476
  240. package/lib/budget-guard.test.mjs +0 -427
  241. package/lib/cadence-bus-requeue.test.mjs +0 -83
  242. package/lib/cadence-bus-schedule.test.mjs +0 -194
  243. package/lib/cadence-bus.test.mjs +0 -720
  244. package/lib/cadences.test.mjs +0 -230
  245. package/lib/capability/inventory.test.mjs +0 -232
  246. package/lib/capability.test.mjs +0 -78
  247. package/lib/channels/base-adapter.test.mjs +0 -590
  248. package/lib/channels/channels.test.mjs +0 -371
  249. package/lib/channels/contract.test.mjs +0 -162
  250. package/lib/channels/inbox-item.test.mjs +0 -368
  251. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  252. package/lib/channels/pairing.test.mjs +0 -270
  253. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  254. package/lib/channels/slack-adapter.test.mjs +0 -212
  255. package/lib/channels/telegram-adapter.test.mjs +0 -306
  256. package/lib/channels/voice/adapter.test.mjs +0 -278
  257. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  258. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  259. package/lib/charter.test.mjs +0 -89
  260. package/lib/claude-bin.test.mjs +0 -131
  261. package/lib/cli/board.test.mjs +0 -227
  262. package/lib/cli/design.test.mjs +0 -270
  263. package/lib/cli/doctor-checks.test.mjs +0 -336
  264. package/lib/cli/global-setup-extras.test.mjs +0 -462
  265. package/lib/cli/inbox.test.mjs +0 -230
  266. package/lib/cli/session-ack.test.mjs +0 -63
  267. package/lib/cli/session.test.mjs +0 -613
  268. package/lib/collective/capture.test.mjs +0 -121
  269. package/lib/collective/cards.test.mjs +0 -114
  270. package/lib/collective/config.test.mjs +0 -123
  271. package/lib/collective/global-config.test.mjs +0 -220
  272. package/lib/collective/global-skills.test.mjs +0 -126
  273. package/lib/collective/presence.test.mjs +0 -95
  274. package/lib/collective/recall.test.mjs +0 -116
  275. package/lib/collective/vendor-skills.test.mjs +0 -306
  276. package/lib/comms/send-gate.test.mjs +0 -770
  277. package/lib/comms.test.mjs +0 -41
  278. package/lib/cost/ledger-row.test.mjs +0 -183
  279. package/lib/design/design-md.test.mjs +0 -318
  280. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  281. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  282. package/lib/design/fixtures/foundation.json +0 -133
  283. package/lib/design/refresh-gate.test.mjs +0 -144
  284. package/lib/design/write.test.mjs +0 -241
  285. package/lib/diagnostics/alerts.test.mjs +0 -318
  286. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  287. package/lib/diagnostics/counters.test.mjs +0 -206
  288. package/lib/diagnostics/events.test.mjs +0 -290
  289. package/lib/diagnostics/otel.test.mjs +0 -196
  290. package/lib/diagnostics/trace.test.mjs +0 -251
  291. package/lib/env-compat.test.mjs +0 -104
  292. package/lib/execution/disposition.test.mjs +0 -553
  293. package/lib/execution/drive.test.mjs +0 -270
  294. package/lib/execution/effects.test.mjs +0 -344
  295. package/lib/execution/intake.test.mjs +0 -389
  296. package/lib/execution/journal.test.mjs +0 -261
  297. package/lib/execution/match.test.mjs +0 -235
  298. package/lib/execution/pipeline.test.mjs +0 -392
  299. package/lib/execution/route.test.mjs +0 -186
  300. package/lib/execution/surface-policy.test.mjs +0 -162
  301. package/lib/fs-atomic.test.mjs +0 -72
  302. package/lib/fs-ownership.test.mjs +0 -158
  303. package/lib/goals/admission.test.mjs +0 -164
  304. package/lib/goals/classify.test.mjs +0 -167
  305. package/lib/goals/collaborate.test.mjs +0 -336
  306. package/lib/goals/gaps.test.mjs +0 -284
  307. package/lib/goals/loop.test.mjs +0 -845
  308. package/lib/hooks/bus.test.mjs +0 -387
  309. package/lib/identity/persona.test.mjs +0 -142
  310. package/lib/kpi-sensors.test.mjs +0 -278
  311. package/lib/kpi.test.mjs +0 -244
  312. package/lib/learning/config.test.mjs +0 -75
  313. package/lib/learning/counters.test.mjs +0 -69
  314. package/lib/learning/curator-consolidate.test.mjs +0 -238
  315. package/lib/learning/curator.test.mjs +0 -106
  316. package/lib/learning/reflect.test.mjs +0 -0
  317. package/lib/learning/session-index.test.mjs +0 -125
  318. package/lib/learning/skill-writer.test.mjs +0 -210
  319. package/lib/mandate/audit.test.mjs +0 -195
  320. package/lib/mandate/contract.test.mjs +0 -185
  321. package/lib/mandate/derive.test.mjs +0 -274
  322. package/lib/mandate/model.test.mjs +0 -164
  323. package/lib/mandate/refresh.test.mjs +0 -389
  324. package/lib/mcp/server.test.mjs +0 -426
  325. package/lib/model-router/auth-profiles.test.mjs +0 -580
  326. package/lib/model-router/catalog.test.mjs +0 -385
  327. package/lib/model-router/economics.test.mjs +0 -438
  328. package/lib/model-router/failover.test.mjs +0 -439
  329. package/lib/model-router/health.test.mjs +0 -338
  330. package/lib/model-router/integration-coverage.test.mjs +0 -831
  331. package/lib/model-router/integration.test.mjs +0 -564
  332. package/lib/model-router/ledger.test.mjs +0 -415
  333. package/lib/model-router/llm-task.test.mjs +0 -392
  334. package/lib/model-router/org-credentials.test.mjs +0 -265
  335. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  336. package/lib/model-router/reconcile.test.mjs +0 -316
  337. package/lib/model-router/repair.test.mjs +0 -180
  338. package/lib/model-router/spawn.test.mjs +0 -446
  339. package/lib/model-router/taxonomy.test.mjs +0 -410
  340. package/lib/model-router.test.mjs +0 -1207
  341. package/lib/org/activity.test.mjs +0 -134
  342. package/lib/org/approvals.test.mjs +0 -216
  343. package/lib/org/awareness.test.mjs +0 -159
  344. package/lib/org/board-mine-cache.test.mjs +0 -53
  345. package/lib/org/board.test.mjs +0 -187
  346. package/lib/org/bootstrap-context.test.mjs +0 -153
  347. package/lib/org/client.test.mjs +0 -1206
  348. package/lib/org/cohort-client.test.mjs +0 -126
  349. package/lib/org/cost-sync.test.mjs +0 -153
  350. package/lib/org/doctor.test.mjs +0 -346
  351. package/lib/org/engagement-ledger.test.mjs +0 -112
  352. package/lib/org/engagement.test.mjs +0 -739
  353. package/lib/org/handoff.test.mjs +0 -269
  354. package/lib/org/inbound/directedness.test.mjs +0 -668
  355. package/lib/org/inbound/facts.test.mjs +0 -471
  356. package/lib/org/inbound/hydrate.test.mjs +0 -453
  357. package/lib/org/inbound/index.test.mjs +0 -429
  358. package/lib/org/inbound/project.test.mjs +0 -287
  359. package/lib/org/integration-tools.test.mjs +0 -160
  360. package/lib/org/keys.test.mjs +0 -92
  361. package/lib/org/knowledge.test.mjs +0 -326
  362. package/lib/org/leases.test.mjs +0 -235
  363. package/lib/org/mesh-directives.test.mjs +0 -110
  364. package/lib/org/mesh-integration.test.mjs +0 -127
  365. package/lib/org/mesh.test.mjs +0 -400
  366. package/lib/org/messaging.test.mjs +0 -471
  367. package/lib/org/param-contract.test.mjs +0 -477
  368. package/lib/org/policy.test.mjs +0 -237
  369. package/lib/org/protocol.checksum.test.mjs +0 -90
  370. package/lib/org/protocol.test.mjs +0 -323
  371. package/lib/org/push.test.mjs +0 -792
  372. package/lib/org/registry.test.mjs +0 -100
  373. package/lib/org/resource-tools.test.mjs +0 -361
  374. package/lib/org/tool-access.test.mjs +0 -144
  375. package/lib/org/tool-surface-integration.test.mjs +0 -120
  376. package/lib/org/tool-surface.test.mjs +0 -1268
  377. package/lib/org/typing.test.mjs +0 -291
  378. package/lib/org/ui-parity.test.mjs +0 -560
  379. package/lib/org/verify.test.mjs +0 -194
  380. package/lib/org/work-ledger.test.mjs +0 -273
  381. package/lib/plan/adoption-e2e.test.mjs +0 -366
  382. package/lib/plan/budget-enforcement.test.mjs +0 -400
  383. package/lib/plan/compile.test.mjs +0 -382
  384. package/lib/plan/emit.test.mjs +0 -269
  385. package/lib/plan/explain.test.mjs +0 -188
  386. package/lib/prompts/parallelism.test.mjs +0 -177
  387. package/lib/rag/rag.test.mjs +0 -505
  388. package/lib/rate-guard.test.mjs +0 -272
  389. package/lib/reactive-gate.test.mjs +0 -57
  390. package/lib/render.test.mjs +0 -68
  391. package/lib/resource-governor.test.mjs +0 -488
  392. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  393. package/lib/scheduling/jitter.test.mjs +0 -140
  394. package/lib/secrets/broker.test.mjs +0 -280
  395. package/lib/secrets/providers.test.mjs +0 -274
  396. package/lib/security/audit-engine.test.mjs +0 -424
  397. package/lib/security/coerce-args.test.mjs +0 -281
  398. package/lib/security/dangerous-tools.test.mjs +0 -68
  399. package/lib/security/external-content.test.mjs +0 -84
  400. package/lib/security/redact.test.mjs +0 -441
  401. package/lib/security/secret-equal.test.mjs +0 -55
  402. package/lib/session/config.test.mjs +0 -92
  403. package/lib/session/feed-core.test.mjs +0 -198
  404. package/lib/session/first-run.test.mjs +0 -121
  405. package/lib/session/frontdoor.test.mjs +0 -205
  406. package/lib/session/handoffs.test.mjs +0 -183
  407. package/lib/session/identity.test.mjs +0 -180
  408. package/lib/session/inbox-claims.test.mjs +0 -286
  409. package/lib/session/launch-args.test.mjs +0 -157
  410. package/lib/session/liveness.test.mjs +0 -100
  411. package/lib/session/status-summary.test.mjs +0 -118
  412. package/lib/session-permissions.test.mjs +0 -120
  413. package/lib/setup/claude-probe.test.mjs +0 -187
  414. package/lib/setup/completeness.test.mjs +0 -110
  415. package/lib/setup/context-pack.test.mjs +0 -89
  416. package/lib/setup/enrich.test.mjs +0 -115
  417. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  418. package/lib/setup/integration.test.mjs +0 -162
  419. package/lib/setup/io.test.mjs +0 -77
  420. package/lib/setup/runner.test.mjs +0 -132
  421. package/lib/setup/sections/identity.test.mjs +0 -234
  422. package/lib/setup/sections/inventory.test.mjs +0 -198
  423. package/lib/setup/sections/learning.test.mjs +0 -81
  424. package/lib/setup/sections/mandate.test.mjs +0 -388
  425. package/lib/setup/sections/messaging.test.mjs +0 -127
  426. package/lib/setup/sections/model.test.mjs +0 -240
  427. package/lib/setup/sections/org.test.mjs +0 -346
  428. package/lib/setup/sections/orgmail.test.mjs +0 -118
  429. package/lib/setup/sections/recovery.test.mjs +0 -98
  430. package/lib/setup/sections/subagents.test.mjs +0 -429
  431. package/lib/setup/sections/verify.test.mjs +0 -175
  432. package/lib/setup/sot.test.mjs +0 -81
  433. package/lib/setup/state.test.mjs +0 -115
  434. package/lib/singleton.test.mjs +0 -151
  435. package/lib/subagents/cli.test.mjs +0 -389
  436. package/lib/subagents/client.test.mjs +0 -309
  437. package/lib/subagents/gap.test.mjs +0 -234
  438. package/lib/subagents/lock.test.mjs +0 -248
  439. package/lib/subagents/manifest.test.mjs +0 -175
  440. package/lib/subagents/refs.test.mjs +0 -204
  441. package/lib/subagents/resolve.test.mjs +0 -422
  442. package/lib/subagents/schema.test.mjs +0 -328
  443. package/lib/telemetry/alerts.test.mjs +0 -109
  444. package/lib/telemetry/collect.test.mjs +0 -1274
  445. package/lib/tool-definitions-integration.test.mjs +0 -83
  446. package/lib/tool-definitions.test.mjs +0 -437
  447. package/lib/upgrade/global-refresh.test.mjs +0 -65
  448. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  449. package/lib/upgrade/post-steps.test.mjs +0 -200
  450. package/lib/upgrade/verify.test.mjs +0 -164
  451. package/lib/util/fetch-timeout.test.mjs +0 -202
  452. package/lib/util/reconnect.test.mjs +0 -369
  453. package/lib/util/unhandled.test.mjs +0 -216
  454. package/lib/voice/outbound.test.mjs +0 -69
  455. package/lib/voice/session-rotation.test.mjs +0 -114
  456. package/lib/voice/stt.test.mjs +0 -226
  457. package/lib/voice/voice.test.mjs +0 -990
  458. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  459. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  460. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  461. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  462. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  463. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  464. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  465. package/scripts/ci/check.test.mjs +0 -194
  466. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  467. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  468. package/scripts/collective/hook-runner.test.mjs +0 -173
  469. package/scripts/cost/fleet-digest.test.mjs +0 -207
  470. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  471. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  472. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  473. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  474. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  475. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  476. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  477. package/scripts/daemon/assurance.test.mjs +0 -1791
  478. package/scripts/daemon/board-mirror.test.mjs +0 -165
  479. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  480. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  481. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  482. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  483. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  484. package/scripts/daemon/classifier.test.mjs +0 -266
  485. package/scripts/daemon/classify-kind.test.mjs +0 -40
  486. package/scripts/daemon/context-compiler.test.mjs +0 -300
  487. package/scripts/daemon/deliver.test.mjs +0 -564
  488. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  489. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  490. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  491. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  492. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  493. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  494. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  495. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  496. package/scripts/daemon/integration.test.mjs +0 -149
  497. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  498. package/scripts/daemon/lib/session-router.test.mjs +0 -295
  499. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  500. package/scripts/daemon/prompt-builder.test.mjs +0 -344
  501. package/scripts/daemon/responder-cost.test.mjs +0 -68
  502. package/scripts/daemon/responder-history.test.mjs +0 -185
  503. package/scripts/daemon/sdk-version.test.mjs +0 -31
  504. package/scripts/daemon/session-lock.test.mjs +0 -252
  505. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  506. package/scripts/daemon/typing-registry.test.mjs +0 -102
  507. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  508. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  509. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  510. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  511. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  512. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  513. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  514. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  515. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  516. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  517. package/scripts/poller-launchd/install.test.mjs +0 -243
  518. package/scripts/restore-from-backup.test.mjs +0 -181
  519. package/scripts/session/feed.test.mjs +0 -196
  520. package/scripts/session/supervisor-sh.test.mjs +0 -218
  521. package/scripts/session/supervisor.test.mjs +0 -482
  522. package/scripts/setup/configure-macos.test.mjs +0 -306
  523. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  524. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  525. package/scripts/setup/generate-capability.test.mjs +0 -134
  526. package/scripts/setup/init-agent.test.mjs +0 -370
  527. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  528. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  529. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -0,0 +1,217 @@
1
+ /**
2
+ * lib/engine/tools/toolsearch.mjs — deferred tool loading and the ToolSearch tool.
3
+ *
4
+ * A run with many MCP tools pays for every definition on every turn. When the
5
+ * tool list is large — more than `DEFER_TOOL_COUNT` tools, or definitions worth
6
+ * more than `DEFER_WINDOW_SHARE` of the context window — MCP tools are
7
+ * DEFERRED: they stay callable, but their definitions are not sent. The model
8
+ * sees how many are held back (a line in the system prompt) and finds them with
9
+ * ToolSearch, which marks the matches loaded; their full definitions are sent
10
+ * from the next request on.
11
+ *
12
+ * Keeping the cached prefix stable: the sent list is the always-loaded tools
13
+ * in their fixed order, followed by loaded deferred tools IN THE ORDER THEY
14
+ * WERE LOADED. Nothing is ever unloaded or reordered (compaction included), so
15
+ * a load changes the request once — the cache is rewritten from the tool list
16
+ * on, one time — and every later request repeats the same bytes. ToolSearch
17
+ * results can name several tools at once, which keeps loads few.
18
+ *
19
+ * A deferred tool the model calls by name without searching runs (the guard
20
+ * still applies) and is loaded from then on.
21
+ *
22
+ * Authorship: the ToolSearch description was written for Cohort Engine by the
23
+ * Cohort team, 2026-09-14 (original text).
24
+ *
25
+ * @module lib/engine/tools/toolsearch
26
+ */
27
+
28
+ import { canonicalJson } from "../context/cache.mjs";
29
+
30
+ export const DEFER_TOOL_COUNT = 40;
31
+ export const DEFER_WINDOW_SHARE = 0.12;
32
+ export const DEFAULT_SEARCH_RESULTS = 5;
33
+ export const MAX_SEARCH_RESULTS = 25;
34
+ const DESCRIPTION_CHARS = 400;
35
+
36
+ /**
37
+ * Should MCP tools be deferred? (pure)
38
+ * @param {{tools:Array<{name:string, description:string, input_schema:object, mcp?:unknown}>, contextWindow:number, charsPerToken?:number, maxTools?:number, maxShare?:number}} p
39
+ */
40
+ export function shouldDeferTools({ tools, contextWindow, charsPerToken = 4, maxTools = DEFER_TOOL_COUNT, maxShare = DEFER_WINDOW_SHARE }) {
41
+ if (!tools.some((t) => t.mcp)) return false;
42
+ if (tools.length > maxTools) return true;
43
+ const chars = JSON.stringify(tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema }))).length;
44
+ return chars / charsPerToken > contextWindow * maxShare;
45
+ }
46
+
47
+ /**
48
+ * @template {{name:string, description:string, input_schema:object, mcp?:any}} T
49
+ * @param {{tools:T[], isDeferrable?:(t:T)=>boolean}} p
50
+ */
51
+ export function createDeferredTools({ tools, isDeferrable = (t) => Boolean(t.mcp) }) {
52
+ let all = [...tools];
53
+ /** @type {string[]} load order */
54
+ const loaded = [];
55
+ const deferrable = (/** @type T */ t) => isDeferrable(t);
56
+
57
+ const byName = () => new Map(all.map((t) => [t.name, t]));
58
+ return {
59
+ /** Every tool, callable. */
60
+ get all() {
61
+ return all;
62
+ },
63
+ isDeferred: (/** @type string */ name) => {
64
+ const t = byName().get(name);
65
+ return Boolean(t && deferrable(t));
66
+ },
67
+ isLoaded: (/** @type string */ name) => loaded.includes(name),
68
+ /** @returns {T[]} deferred tools not yet loaded */
69
+ pending: () => all.filter((t) => deferrable(t) && !loaded.includes(t.name)),
70
+ /** @returns {T[]} deferred tools, loaded or not */
71
+ deferred: () => all.filter((t) => deferrable(t)),
72
+ /**
73
+ * Mark tools loaded; returns the names that were newly loaded.
74
+ * @param {string[]} names
75
+ */
76
+ load(names) {
77
+ const map = byName();
78
+ const added = [];
79
+ for (const n of names) {
80
+ const t = map.get(n);
81
+ if (t && deferrable(t) && !loaded.includes(n)) {
82
+ loaded.push(n);
83
+ added.push(n);
84
+ }
85
+ }
86
+ return added;
87
+ },
88
+ /** The tools whose definitions are sent: fixed tools in order, then loaded ones in load order. @returns {T[]} */
89
+ active() {
90
+ const map = byName();
91
+ return [...all.filter((t) => !deferrable(t)), ...loaded.map((n) => map.get(n)).filter((t) => t !== undefined)];
92
+ },
93
+ loadedNames: () => [...loaded],
94
+ /** Replace the tool set (an MCP server's list changed). Loaded names that disappeared are dropped. @param {T[]} next */
95
+ replace(next) {
96
+ all = [...next];
97
+ const names = new Set(all.map((t) => t.name));
98
+ for (let i = loaded.length - 1; i >= 0; i--) if (!names.has(loaded[i])) loaded.splice(i, 1);
99
+ },
100
+ /** Server names that have deferred tools, sorted. */
101
+ servers() {
102
+ return [...new Set(all.filter((t) => deferrable(t)).map((t) => String(t.mcp?.server ?? "?")))].sort();
103
+ },
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Parse a query: `select:a,b` (exact names), or words where `+word` must appear
109
+ * in the tool name.
110
+ * @param {string} query
111
+ * @returns {{kind:'select', names:string[]}|{kind:'search', required:string[], terms:string[]}}
112
+ */
113
+ export function parseToolQuery(query) {
114
+ const q = String(query ?? "").trim();
115
+ if (/^select:/i.test(q)) return { kind: "select", names: q.slice(7).split(",").map((s) => s.trim()).filter(Boolean) };
116
+ const words = q.toLowerCase().split(/[\s,]+/).filter(Boolean);
117
+ return { kind: "search", required: words.filter((w) => w.startsWith("+") && w.length > 1).map((w) => w.slice(1)), terms: words.map((w) => w.replace(/^\+/, "")).filter(Boolean) };
118
+ }
119
+
120
+ /**
121
+ * Rank tools against a search query (pure).
122
+ * @template {{name:string, description:string}} T
123
+ * @param {T[]} candidates @param {string} query @param {number} max
124
+ * @returns {T[]}
125
+ */
126
+ export function searchTools(candidates, query, max) {
127
+ const parsed = parseToolQuery(query);
128
+ if (parsed.kind === "select") {
129
+ const out = [];
130
+ for (const n of parsed.names) {
131
+ const t = candidates.find((c) => c.name === n) ?? candidates.find((c) => c.name.toLowerCase() === n.toLowerCase());
132
+ if (t && !out.includes(t)) out.push(t);
133
+ }
134
+ return out;
135
+ }
136
+ if (parsed.terms.length === 0) return [];
137
+ const scored = [];
138
+ for (const t of candidates) {
139
+ const name = t.name.toLowerCase();
140
+ if (!parsed.required.every((r) => name.includes(r))) continue;
141
+ const parts = name.split(/__|_|-|\./).filter(Boolean);
142
+ const desc = String(t.description ?? "").toLowerCase();
143
+ let score = 0;
144
+ for (const term of parsed.terms) {
145
+ if (name === term) score += 100;
146
+ if (parts.includes(term)) score += 10;
147
+ else if (name.includes(term)) score += 5;
148
+ if (desc.includes(term)) score += 2;
149
+ }
150
+ if (score > 0) scored.push({ t, score });
151
+ }
152
+ scored.sort((a, b) => b.score - a.score || (a.t.name < b.t.name ? -1 : a.t.name > b.t.name ? 1 : 0));
153
+ return scored.slice(0, max).map((s) => s.t);
154
+ }
155
+
156
+ /** One line per parameter. @param {any} schema */
157
+ function describeParameters(schema) {
158
+ const props = schema && typeof schema.properties === "object" ? schema.properties : {};
159
+ const required = new Set(Array.isArray(schema?.required) ? schema.required : []);
160
+ const names = Object.keys(props);
161
+ if (names.length === 0) return "no parameters";
162
+ return names.map((n) => `${n} (${props[n]?.type ?? "any"}${required.has(n) ? ", required" : ""})`).join(", ");
163
+ }
164
+
165
+ /**
166
+ * The system-prompt note saying tools are held back (fixed for the run: it
167
+ * names the servers and the count at start, not what is loaded later).
168
+ * @param {ReturnType<typeof createDeferredTools>} state
169
+ */
170
+ export function formatDeferredNote(state) {
171
+ const n = state.deferred().length;
172
+ if (n === 0) return null;
173
+ return `${n} more tools, from the MCP servers ${state.servers().join(", ")}, are available but their definitions are not loaded. Use ToolSearch to find and load the ones you need before calling them.`;
174
+ }
175
+
176
+ /**
177
+ * @param {ReturnType<typeof createDeferredTools>} state
178
+ * @returns {import('./index.mjs').Tool}
179
+ */
180
+ export function createToolSearchTool(state) {
181
+ return {
182
+ name: "ToolSearch",
183
+ readOnly: true,
184
+ description:
185
+ "Find and load tools that are available in this run but whose definitions have not been sent to you yet (tools from MCP servers with many tools are held back to save context). " +
186
+ "Search with keywords that describe what you need, prefix a word with + to require it in the tool name, or pass `select:name1,name2` to load exact tool names. " +
187
+ "Matching tools are loaded, and you can call them from your next reply.",
188
+ input_schema: {
189
+ type: "object",
190
+ properties: {
191
+ query: { type: "string", description: 'Keywords, or "select:<tool name>[,<tool name>…]".' },
192
+ max_results: { type: "integer", minimum: 1, description: `How many matches to return (default ${DEFAULT_SEARCH_RESULTS}).` },
193
+ },
194
+ required: ["query"],
195
+ },
196
+ async run(input) {
197
+ const max = Math.min(MAX_SEARCH_RESULTS, input.max_results ?? DEFAULT_SEARCH_RESULTS);
198
+ const matches = searchTools(state.deferred(), input.query, max);
199
+ if (matches.length === 0) {
200
+ const pending = state.pending().length;
201
+ return {
202
+ ok: true,
203
+ content: `No held-back tool matches "${input.query}". ${pending} tool${pending === 1 ? " is" : "s are"} still unloaded, from: ${state.servers().join(", ") || "no servers"}. Try other words, or select:<exact tool name>.`,
204
+ };
205
+ }
206
+ const already = new Set(matches.filter((t) => state.isLoaded(t.name)).map((t) => t.name));
207
+ const added = state.load(matches.map((t) => t.name));
208
+ const lines = matches.map((t) => {
209
+ const d = String(t.description ?? "").replace(/\s+/g, " ").trim();
210
+ const desc = d.length > DESCRIPTION_CHARS ? `${d.slice(0, DESCRIPTION_CHARS - 1)}…` : d;
211
+ return `${t.name}${already.has(t.name) ? " (already loaded)" : ""}\n ${desc || "(no description)"}\n parameters: ${describeParameters(canonicalJson(t.input_schema))}`;
212
+ });
213
+ const head = added.length > 0 ? `Loaded ${added.length} tool${added.length === 1 ? "" : "s"}; ${added.length === 1 ? "it" : "they"} can be called from your next reply.` : "These tools are already loaded.";
214
+ return { ok: true, content: `${head}\n\n${lines.join("\n\n")}` };
215
+ },
216
+ };
217
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * lib/engine/tools/walk.mjs — glob matching, file walking, and the ripgrep seam.
3
+ *
4
+ * Glob and Grep prefer ripgrep when it is installed (fast, honours
5
+ * .gitignore) and fall back to a pure-JS walk when it is not. The two paths
6
+ * must agree on WHICH files match, so pattern matching is always ours:
7
+ * ripgrep only enumerates candidate files, and `globToRegExp` decides.
8
+ *
9
+ * Both paths skip dot-entries and `node_modules` (ripgrep skips hidden files by
10
+ * default and gets `node_modules` from RG_EXCLUDES, because a tree with no
11
+ * .gitignore would otherwise list it). Only ripgrep reads .gitignore. That
12
+ * difference is documented rather than hidden.
13
+ *
14
+ * @module lib/engine/tools/walk
15
+ */
16
+
17
+ /** Exclusions ripgrep needs to agree with the fallback walk. */
18
+ export const RG_EXCLUDES = Object.freeze(["--glob", "!**/node_modules/**"]);
19
+
20
+ import { readdirSync, statSync } from "node:fs";
21
+ import { spawn, spawnSync } from "node:child_process";
22
+ import path from "node:path";
23
+
24
+ /**
25
+ * Translate a glob to an anchored RegExp over POSIX-style relative paths.
26
+ * Supports `**`, `*`, `?`, `{a,b}` and `[...]` classes.
27
+ * @param {string} glob
28
+ * @returns {RegExp}
29
+ */
30
+ export function globToRegExp(glob) {
31
+ let re = "";
32
+ let braceDepth = 0;
33
+ for (let i = 0; i < glob.length; i++) {
34
+ const c = glob[i];
35
+ if (c === "*") {
36
+ if (glob[i + 1] === "*") {
37
+ // `**/` matches zero or more directories; a bare `**` matches anything.
38
+ if (glob[i + 2] === "/") {
39
+ re += "(?:.*/)?";
40
+ i += 2;
41
+ } else {
42
+ re += ".*";
43
+ i += 1;
44
+ }
45
+ } else {
46
+ re += "[^/]*";
47
+ }
48
+ } else if (c === "?") re += "[^/]";
49
+ else if (c === "{") {
50
+ braceDepth++;
51
+ re += "(?:";
52
+ } else if (c === "}" && braceDepth > 0) {
53
+ braceDepth--;
54
+ re += ")";
55
+ } else if (c === "," && braceDepth > 0) re += "|";
56
+ else if (c === "[") {
57
+ const end = glob.indexOf("]", i + 1);
58
+ if (end === -1) re += "\\[";
59
+ else {
60
+ let cls = glob.slice(i + 1, end).replace(/\\/g, "\\\\");
61
+ if (cls.startsWith("!")) cls = "^" + cls.slice(1);
62
+ re += `[${cls}]`;
63
+ i = end;
64
+ }
65
+ } else if ("\\^$+.()|".includes(c)) re += "\\" + c;
66
+ else re += c;
67
+ }
68
+ return new RegExp(`^${re}$`);
69
+ }
70
+
71
+ /**
72
+ * Does a relative path match a glob? A glob with no `/` matches the basename
73
+ * at any depth (ripgrep `--glob` semantics); one with a `/` matches the whole
74
+ * relative path.
75
+ * @param {string} relPath POSIX separators
76
+ * @param {string} glob
77
+ */
78
+ export function matchGlob(relPath, glob) {
79
+ const re = globToRegExp(glob);
80
+ if (!glob.includes("/")) return re.test(relPath.slice(relPath.lastIndexOf("/") + 1));
81
+ return re.test(relPath);
82
+ }
83
+
84
+ /**
85
+ * Pure-JS file enumeration under `root` (absolute paths). Bounded so a walk of
86
+ * `/` cannot hang the loop.
87
+ * @param {string} root
88
+ * @param {{ maxFiles?: number, signal?: AbortSignal }} [opts]
89
+ * @returns {string[]}
90
+ */
91
+ export function walkFiles(root, opts = {}) {
92
+ const maxFiles = opts.maxFiles ?? 200_000;
93
+ const out = [];
94
+ const stack = [root];
95
+ while (stack.length > 0 && out.length < maxFiles) {
96
+ if (opts.signal?.aborted) break;
97
+ const dir = /** @type string */ (stack.pop());
98
+ let entries;
99
+ try {
100
+ entries = readdirSync(dir, { withFileTypes: true });
101
+ } catch {
102
+ continue; // unreadable directory (permissions, raced delete): skip it, keep walking
103
+ }
104
+ for (const e of entries) {
105
+ if (e.name.startsWith(".") || e.name === "node_modules") continue;
106
+ const p = path.join(dir, e.name);
107
+ if (e.isDirectory()) stack.push(p);
108
+ else if (e.isFile()) out.push(p);
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+
114
+ let rgCache;
115
+ /**
116
+ * Is ripgrep on PATH? Probed once per process. `COHORT_ENGINE_NO_RG=1` forces
117
+ * the fallback (tests, and machines with a broken rg).
118
+ * @param {NodeJS.ProcessEnv} [env]
119
+ */
120
+ export function hasRipgrep(env = process.env) {
121
+ if (env.COHORT_ENGINE_NO_RG === "1") return false;
122
+ if (rgCache === undefined) {
123
+ try {
124
+ rgCache = spawnSync("rg", ["--version"], { stdio: "ignore" }).status === 0;
125
+ } catch {
126
+ rgCache = false; // spawn itself failed: treat as not installed
127
+ }
128
+ }
129
+ return rgCache;
130
+ }
131
+
132
+ /**
133
+ * Run ripgrep and collect stdout. Exit 1 means "no matches" and is success.
134
+ * @param {string[]} args
135
+ * @param {{ cwd: string, signal?: AbortSignal, maxBytes?: number }} opts
136
+ * @returns {Promise<{ ok:true, stdout:string, truncated:boolean } | { ok:false, error:string }>}
137
+ */
138
+ export function runRipgrep(args, opts) {
139
+ const maxBytes = opts.maxBytes ?? 20_000_000;
140
+ return new Promise((resolve) => {
141
+ let child;
142
+ try {
143
+ child = spawn("rg", args, { cwd: opts.cwd, stdio: ["ignore", "pipe", "pipe"], signal: opts.signal });
144
+ } catch (e) {
145
+ return resolve({ ok: false, error: `ripgrep failed to start: ${e instanceof Error ? e.message : e}` });
146
+ }
147
+ const chunks = [];
148
+ let size = 0;
149
+ let truncated = false;
150
+ let stderr = "";
151
+ child.stdout.on("data", (d) => {
152
+ if (size >= maxBytes) {
153
+ truncated = true;
154
+ child.kill();
155
+ return;
156
+ }
157
+ chunks.push(d);
158
+ size += d.length;
159
+ });
160
+ child.stderr.on("data", (d) => {
161
+ stderr += d.toString();
162
+ });
163
+ child.on("error", (e) => resolve({ ok: false, error: `ripgrep error: ${e.message}` }));
164
+ child.on("close", (code) => {
165
+ const stdout = Buffer.concat(chunks).toString("utf8");
166
+ if (code === 0 || code === 1 || truncated) resolve({ ok: true, stdout, truncated });
167
+ else resolve({ ok: false, error: stderr.trim() || `ripgrep exited ${code}` });
168
+ });
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Sort absolute paths newest-first by mtime (ties by name, for determinism).
174
+ * @param {string[]} files
175
+ */
176
+ export function sortByMtime(files) {
177
+ const withTime = files.map((f) => {
178
+ let t = 0;
179
+ try {
180
+ t = statSync(f).mtimeMs;
181
+ } catch {
182
+ /* vanished between listing and stat: sorts last */
183
+ }
184
+ return { f, t };
185
+ });
186
+ withTime.sort((a, b) => b.t - a.t || a.f.localeCompare(b.f));
187
+ return withTime.map((x) => x.f);
188
+ }
189
+
190
+ /** @param {string} p */
191
+ export function toPosix(p) {
192
+ return p.split(path.sep).join("/");
193
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * lib/engine/tools/web-switch.mjs — whether a run offers the web tools.
3
+ *
4
+ * WebFetch and WebSearch are built-ins, on by default. They are also an
5
+ * outbound channel the outbound send gate does not screen: whatever a model
6
+ * puts in a URL or a search query leaves the machine (the SSRF guard protects
7
+ * internal hosts, not the data). A seat whose sends are gated can switch them
8
+ * off from the default tool set:
9
+ *
10
+ * env COHORT_ENGINE_WEB_TOOLS=0 (or false/off/no); 1/true/on/yes forces on
11
+ * settings cohort.webTools: false
12
+ *
13
+ * Environment wins over settings. An explicit `--tools` list that names a web
14
+ * tool still gets it: the switch shapes the default set, the invoker's list
15
+ * is deliberate. Deny rules (`WebFetch`, `WebSearch`) remove them regardless.
16
+ *
17
+ * @module lib/engine/tools/web-switch
18
+ */
19
+
20
+ export const WEB_TOOL_NAMES = Object.freeze(["WebFetch", "WebSearch"]);
21
+
22
+ /**
23
+ * @param {{env?:string, setting?:boolean}} p COHORT_ENGINE_WEB_TOOLS and settings `cohort.webTools`
24
+ * @returns {boolean}
25
+ */
26
+ export function webToolsEnabled({ env, setting }) {
27
+ const v = String(env ?? "").trim().toLowerCase();
28
+ if (["0", "false", "off", "no"].includes(v)) return false;
29
+ if (["1", "true", "on", "yes"].includes(v)) return true;
30
+ return setting !== false;
31
+ }