vellum 0.2.13 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1019) hide show
  1. package/bin/vellum.js +2 -0
  2. package/package.json +6 -65
  3. package/.dockerignore +0 -27
  4. package/.env.example +0 -22
  5. package/Dockerfile +0 -99
  6. package/Dockerfile.sandbox +0 -5
  7. package/README.md +0 -169
  8. package/bun.lock +0 -1743
  9. package/bunfig.toml +0 -2
  10. package/docs/skills.md +0 -158
  11. package/drizzle/0000_dizzy_maggott.sql +0 -301
  12. package/drizzle/meta/0000_snapshot.json +0 -1999
  13. package/drizzle/meta/_journal.json +0 -13
  14. package/drizzle.config.ts +0 -7
  15. package/eslint.config.mjs +0 -17
  16. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  17. package/hook-templates/debug-prompt-logger/run.sh +0 -68
  18. package/knip.json +0 -9
  19. package/scripts/capture-x-graphql.ts +0 -545
  20. package/scripts/ipc/check-contract-inventory.ts +0 -104
  21. package/scripts/ipc/check-swift-decoder-drift.ts +0 -164
  22. package/scripts/ipc/generate-swift.ts +0 -492
  23. package/scripts/test-filesystem-tools.sh +0 -48
  24. package/scripts/test.sh +0 -127
  25. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -2316
  26. package/src/__tests__/account-registry.test.ts +0 -245
  27. package/src/__tests__/active-skill-tools.test.ts +0 -378
  28. package/src/__tests__/agent-heartbeat-service.test.ts +0 -250
  29. package/src/__tests__/agent-loop-thinking.test.ts +0 -81
  30. package/src/__tests__/agent-loop.test.ts +0 -1135
  31. package/src/__tests__/anthropic-provider.test.ts +0 -778
  32. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -290
  33. package/src/__tests__/app-bundler.test.ts +0 -292
  34. package/src/__tests__/app-executors.test.ts +0 -613
  35. package/src/__tests__/app-open-proxy.test.ts +0 -62
  36. package/src/__tests__/asset-materialize-tool.test.ts +0 -452
  37. package/src/__tests__/asset-search-tool.test.ts +0 -477
  38. package/src/__tests__/assistant-attachment-directive.test.ts +0 -401
  39. package/src/__tests__/assistant-attachments.test.ts +0 -437
  40. package/src/__tests__/assistant-event-hub.test.ts +0 -226
  41. package/src/__tests__/assistant-event.test.ts +0 -123
  42. package/src/__tests__/attachments-store.test.ts +0 -476
  43. package/src/__tests__/attachments.test.ts +0 -134
  44. package/src/__tests__/audit-log-rotation.test.ts +0 -154
  45. package/src/__tests__/browser-fill-credential.test.ts +0 -309
  46. package/src/__tests__/browser-manager.test.ts +0 -203
  47. package/src/__tests__/browser-runtime-check.test.ts +0 -55
  48. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +0 -68
  49. package/src/__tests__/browser-skill-endstate.test.ts +0 -195
  50. package/src/__tests__/bundle-scanner.test.ts +0 -313
  51. package/src/__tests__/call-bridge.test.ts +0 -425
  52. package/src/__tests__/call-constants.test.ts +0 -40
  53. package/src/__tests__/call-orchestrator.test.ts +0 -512
  54. package/src/__tests__/call-recovery.test.ts +0 -518
  55. package/src/__tests__/call-routes-http.test.ts +0 -459
  56. package/src/__tests__/call-state-machine.test.ts +0 -143
  57. package/src/__tests__/call-state.test.ts +0 -174
  58. package/src/__tests__/call-store.test.ts +0 -691
  59. package/src/__tests__/checker.test.ts +0 -3960
  60. package/src/__tests__/clarification-resolver.test.ts +0 -159
  61. package/src/__tests__/classifier.test.ts +0 -67
  62. package/src/__tests__/claude-code-skill-regression.test.ts +0 -127
  63. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -88
  64. package/src/__tests__/cli-discover.test.ts +0 -85
  65. package/src/__tests__/cli.test.ts +0 -81
  66. package/src/__tests__/clipboard.test.ts +0 -80
  67. package/src/__tests__/commit-guarantee.test.ts +0 -335
  68. package/src/__tests__/commit-message-enrichment-service.test.ts +0 -550
  69. package/src/__tests__/compaction.benchmark.test.ts +0 -176
  70. package/src/__tests__/computer-use-session-compaction.test.ts +0 -132
  71. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -293
  72. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -117
  73. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -74
  74. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -89
  75. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -217
  76. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -107
  77. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +0 -54
  78. package/src/__tests__/computer-use-tools.test.ts +0 -250
  79. package/src/__tests__/config-schema.test.ts +0 -1343
  80. package/src/__tests__/conflict-store.test.ts +0 -330
  81. package/src/__tests__/connection-policy.test.ts +0 -102
  82. package/src/__tests__/contacts-tools.test.ts +0 -331
  83. package/src/__tests__/context-memory-e2e.test.ts +0 -434
  84. package/src/__tests__/context-token-estimator.test.ts +0 -135
  85. package/src/__tests__/context-window-manager.test.ts +0 -376
  86. package/src/__tests__/contradiction-checker.test.ts +0 -216
  87. package/src/__tests__/conversation-store.test.ts +0 -612
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +0 -517
  89. package/src/__tests__/credential-broker-server-use.test.ts +0 -554
  90. package/src/__tests__/credential-broker.test.ts +0 -167
  91. package/src/__tests__/credential-host-pattern-match.test.ts +0 -104
  92. package/src/__tests__/credential-metadata-store.test.ts +0 -779
  93. package/src/__tests__/credential-policy-validate.test.ts +0 -121
  94. package/src/__tests__/credential-resolve.test.ts +0 -328
  95. package/src/__tests__/credential-security-e2e.test.ts +0 -352
  96. package/src/__tests__/credential-security-invariants.test.ts +0 -567
  97. package/src/__tests__/credential-selection.test.ts +0 -354
  98. package/src/__tests__/credential-vault.test.ts +0 -852
  99. package/src/__tests__/daemon-assistant-events.test.ts +0 -164
  100. package/src/__tests__/daemon-server-session-init.test.ts +0 -522
  101. package/src/__tests__/date-context.test.ts +0 -373
  102. package/src/__tests__/db-schedule-syntax-migration.test.ts +0 -129
  103. package/src/__tests__/delete-managed-skill-tool.test.ts +0 -97
  104. package/src/__tests__/diff.test.ts +0 -121
  105. package/src/__tests__/domain-normalize.test.ts +0 -112
  106. package/src/__tests__/domain-policy.test.ts +0 -124
  107. package/src/__tests__/doordash-client.test.ts +0 -186
  108. package/src/__tests__/doordash-session.test.ts +0 -152
  109. package/src/__tests__/dynamic-page-surface.test.ts +0 -91
  110. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -132
  111. package/src/__tests__/edit-engine.test.ts +0 -180
  112. package/src/__tests__/elevenlabs-client.test.ts +0 -209
  113. package/src/__tests__/email-cli.test.ts +0 -283
  114. package/src/__tests__/encrypted-store.test.ts +0 -332
  115. package/src/__tests__/entity-extractor.test.ts +0 -190
  116. package/src/__tests__/ephemeral-permissions.test.ts +0 -312
  117. package/src/__tests__/evaluate-typescript-tool.test.ts +0 -286
  118. package/src/__tests__/event-bus.test.ts +0 -222
  119. package/src/__tests__/file-edit-tool.test.ts +0 -122
  120. package/src/__tests__/file-ops-service.test.ts +0 -330
  121. package/src/__tests__/file-read-tool.test.ts +0 -75
  122. package/src/__tests__/file-write-tool.test.ts +0 -113
  123. package/src/__tests__/fixtures/credential-security-fixtures.ts +0 -181
  124. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -126
  125. package/src/__tests__/fixtures/mock-signup-server.ts +0 -387
  126. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  127. package/src/__tests__/followup-tools.test.ts +0 -303
  128. package/src/__tests__/forbidden-legacy-symbols.test.ts +0 -71
  129. package/src/__tests__/fuzzy-match-property.test.ts +0 -216
  130. package/src/__tests__/fuzzy-match.test.ts +0 -138
  131. package/src/__tests__/gateway-only-enforcement.test.ts +0 -436
  132. package/src/__tests__/gemini-image-service.test.ts +0 -261
  133. package/src/__tests__/gemini-provider.test.ts +0 -651
  134. package/src/__tests__/get-weather.test.ts +0 -318
  135. package/src/__tests__/gmail-integration.test.ts +0 -73
  136. package/src/__tests__/handlers-cu-observation-blob.test.ts +0 -351
  137. package/src/__tests__/handlers-ipc-blob-probe.test.ts +0 -190
  138. package/src/__tests__/handlers-slack-config.test.ts +0 -199
  139. package/src/__tests__/handlers-task-submit-slash.test.ts +0 -38
  140. package/src/__tests__/handlers-twitter-config.test.ts +0 -718
  141. package/src/__tests__/headless-browser-interactions.test.ts +0 -536
  142. package/src/__tests__/headless-browser-navigate.test.ts +0 -211
  143. package/src/__tests__/headless-browser-read-tools.test.ts +0 -261
  144. package/src/__tests__/headless-browser-snapshot.test.ts +0 -185
  145. package/src/__tests__/history-repair-observability.test.ts +0 -56
  146. package/src/__tests__/history-repair.test.ts +0 -510
  147. package/src/__tests__/home-base-bootstrap.test.ts +0 -82
  148. package/src/__tests__/hooks-blocking.test.ts +0 -128
  149. package/src/__tests__/hooks-cli.test.ts +0 -144
  150. package/src/__tests__/hooks-config.test.ts +0 -93
  151. package/src/__tests__/hooks-discovery.test.ts +0 -199
  152. package/src/__tests__/hooks-integration.test.ts +0 -189
  153. package/src/__tests__/hooks-manager.test.ts +0 -187
  154. package/src/__tests__/hooks-runner.test.ts +0 -178
  155. package/src/__tests__/hooks-settings.test.ts +0 -154
  156. package/src/__tests__/hooks-templates.test.ts +0 -137
  157. package/src/__tests__/hooks-ts-runner.test.ts +0 -125
  158. package/src/__tests__/hooks-watch.test.ts +0 -100
  159. package/src/__tests__/host-file-edit-tool.test.ts +0 -104
  160. package/src/__tests__/host-file-read-tool.test.ts +0 -61
  161. package/src/__tests__/host-file-write-tool.test.ts +0 -77
  162. package/src/__tests__/host-shell-tool.test.ts +0 -311
  163. package/src/__tests__/ingress-url-consistency.test.ts +0 -214
  164. package/src/__tests__/intent-routing.test.ts +0 -259
  165. package/src/__tests__/ipc-blob-store.test.ts +0 -315
  166. package/src/__tests__/ipc-contract-inventory.test.ts +0 -54
  167. package/src/__tests__/ipc-contract.test.ts +0 -74
  168. package/src/__tests__/ipc-protocol.test.ts +0 -113
  169. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +0 -237
  170. package/src/__tests__/ipc-snapshot.test.ts +0 -1698
  171. package/src/__tests__/ipc-validate.test.ts +0 -357
  172. package/src/__tests__/key-migration.test.ts +0 -183
  173. package/src/__tests__/keychain.test.ts +0 -258
  174. package/src/__tests__/llm-usage-store.test.ts +0 -221
  175. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -257
  176. package/src/__tests__/managed-store.test.ts +0 -608
  177. package/src/__tests__/media-generate-image.test.ts +0 -238
  178. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -676
  179. package/src/__tests__/media-visibility-policy.test.ts +0 -141
  180. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -235
  181. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -481
  182. package/src/__tests__/memory-query-builder.test.ts +0 -59
  183. package/src/__tests__/memory-recall-quality.test.ts +0 -846
  184. package/src/__tests__/memory-regressions.experimental.test.ts +0 -538
  185. package/src/__tests__/memory-regressions.test.ts +0 -4336
  186. package/src/__tests__/memory-retrieval-budget.test.ts +0 -49
  187. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -430
  188. package/src/__tests__/migration-cli-flows.test.ts +0 -169
  189. package/src/__tests__/migration-ordering.test.ts +0 -249
  190. package/src/__tests__/mock-signup-server.test.ts +0 -528
  191. package/src/__tests__/oauth-callback-registry.test.ts +0 -85
  192. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -285
  193. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -176
  194. package/src/__tests__/onboarding-template-contract.test.ts +0 -58
  195. package/src/__tests__/openai-provider.test.ts +0 -753
  196. package/src/__tests__/parallel-tool.benchmark.test.ts +0 -294
  197. package/src/__tests__/parser.test.ts +0 -472
  198. package/src/__tests__/path-classifier.test.ts +0 -73
  199. package/src/__tests__/path-policy.test.ts +0 -435
  200. package/src/__tests__/platform-move-helper.test.ts +0 -99
  201. package/src/__tests__/platform-socket-path.test.ts +0 -52
  202. package/src/__tests__/platform-workspace-migration.test.ts +0 -1000
  203. package/src/__tests__/platform.test.ts +0 -131
  204. package/src/__tests__/playbook-tools.test.ts +0 -342
  205. package/src/__tests__/prebuilt-home-base-seed.test.ts +0 -75
  206. package/src/__tests__/pricing.test.ts +0 -256
  207. package/src/__tests__/profile-compiler.test.ts +0 -374
  208. package/src/__tests__/provider-commit-message-generator.test.ts +0 -342
  209. package/src/__tests__/provider-registry-ollama.test.ts +0 -16
  210. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -773
  211. package/src/__tests__/proxy-approval-callback.test.ts +0 -601
  212. package/src/__tests__/public-ingress-urls.test.ts +0 -222
  213. package/src/__tests__/ratelimit.test.ts +0 -297
  214. package/src/__tests__/recurrence-engine-rruleset.test.ts +0 -78
  215. package/src/__tests__/recurrence-engine.test.ts +0 -69
  216. package/src/__tests__/recurrence-types.test.ts +0 -71
  217. package/src/__tests__/registry.test.ts +0 -494
  218. package/src/__tests__/relay-server.test.ts +0 -688
  219. package/src/__tests__/reminder-store.test.ts +0 -223
  220. package/src/__tests__/reminder.test.ts +0 -229
  221. package/src/__tests__/request-file-tool.test.ts +0 -158
  222. package/src/__tests__/run-orchestrator-assistant-events.test.ts +0 -222
  223. package/src/__tests__/run-orchestrator.test.ts +0 -200
  224. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -189
  225. package/src/__tests__/runtime-events-sse-parity.test.ts +0 -343
  226. package/src/__tests__/runtime-events-sse.test.ts +0 -162
  227. package/src/__tests__/runtime-runs-http.test.ts +0 -433
  228. package/src/__tests__/runtime-runs.test.ts +0 -273
  229. package/src/__tests__/sandbox-diagnostics.test.ts +0 -408
  230. package/src/__tests__/sandbox-host-parity.test.ts +0 -950
  231. package/src/__tests__/scaffold-managed-skill-tool.test.ts +0 -253
  232. package/src/__tests__/schedule-store.test.ts +0 -482
  233. package/src/__tests__/schedule-tools.test.ts +0 -700
  234. package/src/__tests__/scheduler-recurrence.test.ts +0 -329
  235. package/src/__tests__/script-proxy-certs.test.ts +0 -90
  236. package/src/__tests__/script-proxy-connect-tunnel.test.ts +0 -177
  237. package/src/__tests__/script-proxy-decision-trace.test.ts +0 -156
  238. package/src/__tests__/script-proxy-http-forwarder.test.ts +0 -281
  239. package/src/__tests__/script-proxy-injection-runtime.test.ts +0 -401
  240. package/src/__tests__/script-proxy-mitm-handler.test.ts +0 -407
  241. package/src/__tests__/script-proxy-policy-runtime.test.ts +0 -287
  242. package/src/__tests__/script-proxy-policy.test.ts +0 -310
  243. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +0 -135
  244. package/src/__tests__/script-proxy-router.test.ts +0 -180
  245. package/src/__tests__/script-proxy-session-manager.test.ts +0 -382
  246. package/src/__tests__/script-proxy-session-runtime.test.ts +0 -113
  247. package/src/__tests__/secret-allowlist.test.ts +0 -229
  248. package/src/__tests__/secret-ingress-handler.test.ts +0 -99
  249. package/src/__tests__/secret-onetime-send.test.ts +0 -130
  250. package/src/__tests__/secret-prompt-log-hygiene.test.ts +0 -106
  251. package/src/__tests__/secret-response-routing.test.ts +0 -93
  252. package/src/__tests__/secret-scanner-executor.test.ts +0 -348
  253. package/src/__tests__/secret-scanner.test.ts +0 -857
  254. package/src/__tests__/secure-keys.test.ts +0 -323
  255. package/src/__tests__/server-history-render.test.ts +0 -431
  256. package/src/__tests__/session-abort-tool-results.test.ts +0 -240
  257. package/src/__tests__/session-conflict-gate.test.ts +0 -700
  258. package/src/__tests__/session-error.test.ts +0 -369
  259. package/src/__tests__/session-evictor.test.ts +0 -188
  260. package/src/__tests__/session-init.benchmark.test.ts +0 -462
  261. package/src/__tests__/session-load-history-repair.test.ts +0 -222
  262. package/src/__tests__/session-pre-run-repair.test.ts +0 -213
  263. package/src/__tests__/session-profile-injection.test.ts +0 -444
  264. package/src/__tests__/session-provider-retry-repair.test.ts +0 -306
  265. package/src/__tests__/session-queue.test.ts +0 -1535
  266. package/src/__tests__/session-runtime-assembly.test.ts +0 -476
  267. package/src/__tests__/session-runtime-workspace.test.ts +0 -183
  268. package/src/__tests__/session-skill-tools.test.ts +0 -2431
  269. package/src/__tests__/session-slash-known.test.ts +0 -368
  270. package/src/__tests__/session-slash-queue.test.ts +0 -288
  271. package/src/__tests__/session-slash-unknown.test.ts +0 -271
  272. package/src/__tests__/session-surfaces-task-progress.test.ts +0 -104
  273. package/src/__tests__/session-tool-setup-app-refresh.test.ts +0 -473
  274. package/src/__tests__/session-tool-setup-memory-scope.test.ts +0 -140
  275. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +0 -140
  276. package/src/__tests__/session-undo.test.ts +0 -75
  277. package/src/__tests__/session-workspace-cache-state.test.ts +0 -246
  278. package/src/__tests__/session-workspace-injection.test.ts +0 -327
  279. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -240
  280. package/src/__tests__/shared-filesystem-errors.test.ts +0 -78
  281. package/src/__tests__/shell-credential-ref.test.ts +0 -187
  282. package/src/__tests__/shell-parser-fuzz.test.ts +0 -544
  283. package/src/__tests__/shell-parser-property.test.ts +0 -433
  284. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -272
  285. package/src/__tests__/signup-e2e.test.ts +0 -353
  286. package/src/__tests__/size-guard.test.ts +0 -117
  287. package/src/__tests__/skill-include-graph.test.ts +0 -303
  288. package/src/__tests__/skill-load-tool.test.ts +0 -409
  289. package/src/__tests__/skill-projection.benchmark.test.ts +0 -328
  290. package/src/__tests__/skill-script-runner-host.test.ts +0 -489
  291. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -349
  292. package/src/__tests__/skill-script-runner.test.ts +0 -159
  293. package/src/__tests__/skill-tool-factory.test.ts +0 -252
  294. package/src/__tests__/skill-tool-manifest.test.ts +0 -658
  295. package/src/__tests__/skill-version-hash.test.ts +0 -182
  296. package/src/__tests__/skills.test.ts +0 -680
  297. package/src/__tests__/slash-commands-catalog.test.ts +0 -86
  298. package/src/__tests__/slash-commands-parser.test.ts +0 -119
  299. package/src/__tests__/slash-commands-resolver.test.ts +0 -193
  300. package/src/__tests__/slash-commands-rewrite.test.ts +0 -39
  301. package/src/__tests__/speaker-identification.test.ts +0 -52
  302. package/src/__tests__/starter-bundle.test.ts +0 -136
  303. package/src/__tests__/starter-task-flow.test.ts +0 -143
  304. package/src/__tests__/subagent-manager-notify.test.ts +0 -404
  305. package/src/__tests__/subagent-tools.test.ts +0 -218
  306. package/src/__tests__/subagent-types.test.ts +0 -78
  307. package/src/__tests__/swarm-orchestrator.test.ts +0 -428
  308. package/src/__tests__/swarm-plan-validator.test.ts +0 -330
  309. package/src/__tests__/swarm-recursion.test.ts +0 -165
  310. package/src/__tests__/swarm-router-planner.test.ts +0 -208
  311. package/src/__tests__/swarm-session-integration.test.ts +0 -274
  312. package/src/__tests__/swarm-tool.test.ts +0 -145
  313. package/src/__tests__/swarm-worker-backend.test.ts +0 -129
  314. package/src/__tests__/swarm-worker-runner.test.ts +0 -272
  315. package/src/__tests__/system-prompt.test.ts +0 -439
  316. package/src/__tests__/task-compiler.test.ts +0 -284
  317. package/src/__tests__/task-runner.test.ts +0 -216
  318. package/src/__tests__/task-scheduler.test.ts +0 -217
  319. package/src/__tests__/task-tools.test.ts +0 -595
  320. package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1064
  321. package/src/__tests__/terminal-sandbox.integration.test.ts +0 -178
  322. package/src/__tests__/terminal-sandbox.test.ts +0 -202
  323. package/src/__tests__/test-support/browser-skill-harness.ts +0 -90
  324. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -45
  325. package/src/__tests__/tool-audit-listener.test.ts +0 -113
  326. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -253
  327. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -500
  328. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -516
  329. package/src/__tests__/tool-executor-redaction.test.ts +0 -289
  330. package/src/__tests__/tool-executor.test.ts +0 -2055
  331. package/src/__tests__/tool-metrics-listener.test.ts +0 -225
  332. package/src/__tests__/tool-notification-listener.test.ts +0 -49
  333. package/src/__tests__/tool-policy.test.ts +0 -54
  334. package/src/__tests__/tool-profiling-listener.test.ts +0 -268
  335. package/src/__tests__/tool-result-truncation.test.ts +0 -217
  336. package/src/__tests__/tool-trace-listener.test.ts +0 -226
  337. package/src/__tests__/top-level-renderer.test.ts +0 -121
  338. package/src/__tests__/top-level-scanner.test.ts +0 -141
  339. package/src/__tests__/trace-emitter.test.ts +0 -173
  340. package/src/__tests__/trust-store.test.ts +0 -2031
  341. package/src/__tests__/turn-commit.test.ts +0 -554
  342. package/src/__tests__/twilio-provider.test.ts +0 -179
  343. package/src/__tests__/twilio-routes-twiml.test.ts +0 -127
  344. package/src/__tests__/twilio-routes.test.ts +0 -822
  345. package/src/__tests__/twitter-auth-handler.test.ts +0 -666
  346. package/src/__tests__/url-safety.test.ts +0 -418
  347. package/src/__tests__/view-image-tool.test.ts +0 -217
  348. package/src/__tests__/weather-skill-regression.test.ts +0 -225
  349. package/src/__tests__/web-fetch.test.ts +0 -869
  350. package/src/__tests__/web-search.test.ts +0 -584
  351. package/src/__tests__/workspace-git-service.test.ts +0 -1153
  352. package/src/__tests__/workspace-heartbeat-service.test.ts +0 -486
  353. package/src/__tests__/workspace-lifecycle.test.ts +0 -292
  354. package/src/agent/attachments.ts +0 -35
  355. package/src/agent/loop.ts +0 -500
  356. package/src/agent/message-types.ts +0 -17
  357. package/src/agent-heartbeat/agent-heartbeat-service.ts +0 -155
  358. package/src/autonomy/autonomy-resolver.ts +0 -60
  359. package/src/autonomy/autonomy-store.ts +0 -122
  360. package/src/autonomy/disposition-mapper.ts +0 -31
  361. package/src/autonomy/index.ts +0 -11
  362. package/src/autonomy/types.ts +0 -39
  363. package/src/bundler/app-bundler.ts +0 -295
  364. package/src/bundler/bundle-scanner.ts +0 -535
  365. package/src/bundler/bundle-signer.ts +0 -124
  366. package/src/bundler/manifest.ts +0 -21
  367. package/src/bundler/signature-verifier.ts +0 -184
  368. package/src/calls/call-bridge.ts +0 -95
  369. package/src/calls/call-constants.ts +0 -48
  370. package/src/calls/call-domain.ts +0 -278
  371. package/src/calls/call-orchestrator.ts +0 -412
  372. package/src/calls/call-recovery.ts +0 -207
  373. package/src/calls/call-state-machine.ts +0 -68
  374. package/src/calls/call-state.ts +0 -87
  375. package/src/calls/call-store.ts +0 -416
  376. package/src/calls/elevenlabs-client.ts +0 -89
  377. package/src/calls/elevenlabs-config.ts +0 -29
  378. package/src/calls/relay-server.ts +0 -390
  379. package/src/calls/speaker-identification.ts +0 -213
  380. package/src/calls/twilio-config.ts +0 -45
  381. package/src/calls/twilio-provider.ts +0 -178
  382. package/src/calls/twilio-routes.ts +0 -316
  383. package/src/calls/types.ts +0 -37
  384. package/src/calls/voice-provider.ts +0 -14
  385. package/src/calls/voice-quality.ts +0 -92
  386. package/src/cli/autonomy.ts +0 -188
  387. package/src/cli/config-commands.ts +0 -334
  388. package/src/cli/contacts.ts +0 -149
  389. package/src/cli/core-commands.ts +0 -784
  390. package/src/cli/doordash.ts +0 -1055
  391. package/src/cli/email-guardrails.ts +0 -200
  392. package/src/cli/email.ts +0 -405
  393. package/src/cli/ipc-client.ts +0 -82
  394. package/src/cli/main-screen.tsx +0 -53
  395. package/src/cli/map.ts +0 -270
  396. package/src/cli/twitter.ts +0 -575
  397. package/src/cli.ts +0 -937
  398. package/src/commands/__tests__/cc-command-registry.test.ts +0 -319
  399. package/src/commands/cc-command-registry.ts +0 -209
  400. package/src/config/bundled-skills/.gitkeep +0 -0
  401. package/src/config/bundled-skills/agentmail/SKILL.md +0 -128
  402. package/src/config/bundled-skills/agentmail/icon.svg +0 -21
  403. package/src/config/bundled-skills/app-builder/SKILL.md +0 -1404
  404. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -279
  405. package/src/config/bundled-skills/app-builder/icon.svg +0 -9
  406. package/src/config/bundled-skills/app-builder/tools/app-create.ts +0 -15
  407. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +0 -10
  408. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -11
  409. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -10
  410. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -18
  411. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -11
  412. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -10
  413. package/src/config/bundled-skills/app-builder/tools/app-query.ts +0 -10
  414. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -20
  415. package/src/config/bundled-skills/browser/SKILL.md +0 -28
  416. package/src/config/bundled-skills/browser/TOOLS.json +0 -234
  417. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -9
  418. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -9
  419. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -9
  420. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -9
  421. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -9
  422. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -9
  423. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -9
  424. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -9
  425. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -9
  426. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -9
  427. package/src/config/bundled-skills/claude-code/SKILL.md +0 -50
  428. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -40
  429. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -9
  430. package/src/config/bundled-skills/computer-use/SKILL.md +0 -17
  431. package/src/config/bundled-skills/computer-use/TOOLS.json +0 -326
  432. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +0 -9
  433. package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +0 -9
  434. package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +0 -9
  435. package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +0 -9
  436. package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +0 -9
  437. package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +0 -9
  438. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -9
  439. package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +0 -9
  440. package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +0 -9
  441. package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +0 -9
  442. package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +0 -9
  443. package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +0 -9
  444. package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +0 -9
  445. package/src/config/bundled-skills/contacts/SKILL.md +0 -39
  446. package/src/config/bundled-skills/contacts/TOOLS.json +0 -122
  447. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +0 -9
  448. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -9
  449. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -9
  450. package/src/config/bundled-skills/document/SKILL.md +0 -26
  451. package/src/config/bundled-skills/document/TOOLS.json +0 -53
  452. package/src/config/bundled-skills/document/tools/document-create.ts +0 -9
  453. package/src/config/bundled-skills/document/tools/document-update.ts +0 -9
  454. package/src/config/bundled-skills/doordash/SKILL.md +0 -163
  455. package/src/config/bundled-skills/followups/SKILL.md +0 -32
  456. package/src/config/bundled-skills/followups/TOOLS.json +0 -100
  457. package/src/config/bundled-skills/followups/icon.svg +0 -24
  458. package/src/config/bundled-skills/followups/tools/followup-create.ts +0 -9
  459. package/src/config/bundled-skills/followups/tools/followup-list.ts +0 -9
  460. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +0 -9
  461. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  462. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -108
  463. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -165
  464. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -21
  465. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -42
  466. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -13
  467. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -30
  468. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -41
  469. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -18
  470. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  471. package/src/config/bundled-skills/image-studio/SKILL.md +0 -32
  472. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -42
  473. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +0 -115
  474. package/src/config/bundled-skills/macos-automation/SKILL.md +0 -66
  475. package/src/config/bundled-skills/messaging/SKILL.md +0 -130
  476. package/src/config/bundled-skills/messaging/TOOLS.json +0 -357
  477. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +0 -23
  478. package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +0 -23
  479. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +0 -25
  480. package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +0 -26
  481. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +0 -25
  482. package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +0 -23
  483. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +0 -84
  484. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +0 -18
  485. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +0 -125
  486. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +0 -16
  487. package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +0 -49
  488. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +0 -21
  489. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +0 -25
  490. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +0 -28
  491. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +0 -29
  492. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +0 -22
  493. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -27
  494. package/src/config/bundled-skills/messaging/tools/shared.ts +0 -71
  495. package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +0 -25
  496. package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +0 -23
  497. package/src/config/bundled-skills/phone-calls/SKILL.md +0 -414
  498. package/src/config/bundled-skills/playbooks/SKILL.md +0 -31
  499. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -126
  500. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +0 -9
  501. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +0 -9
  502. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +0 -9
  503. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +0 -9
  504. package/src/config/bundled-skills/public-ingress/SKILL.md +0 -183
  505. package/src/config/bundled-skills/reminder/SKILL.md +0 -20
  506. package/src/config/bundled-skills/reminder/TOOLS.json +0 -67
  507. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +0 -9
  508. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +0 -9
  509. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +0 -9
  510. package/src/config/bundled-skills/schedule/SKILL.md +0 -74
  511. package/src/config/bundled-skills/schedule/TOOLS.json +0 -135
  512. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +0 -9
  513. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +0 -9
  514. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +0 -9
  515. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +0 -9
  516. package/src/config/bundled-skills/self-upgrade/SKILL.md +0 -68
  517. package/src/config/bundled-skills/start-the-day/SKILL.md +0 -70
  518. package/src/config/bundled-skills/start-the-day/icon.svg +0 -13
  519. package/src/config/bundled-skills/subagent/SKILL.md +0 -25
  520. package/src/config/bundled-skills/subagent/TOOLS.json +0 -107
  521. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +0 -9
  522. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +0 -9
  523. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +0 -9
  524. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +0 -9
  525. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +0 -9
  526. package/src/config/bundled-skills/tasks/SKILL.md +0 -28
  527. package/src/config/bundled-skills/tasks/TOOLS.json +0 -281
  528. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -9
  529. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -9
  530. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -9
  531. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -9
  532. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -9
  533. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -9
  534. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -9
  535. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -9
  536. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -9
  537. package/src/config/bundled-skills/transcribe/SKILL.md +0 -25
  538. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -32
  539. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +0 -370
  540. package/src/config/bundled-skills/twitter/SKILL.md +0 -134
  541. package/src/config/bundled-skills/watcher/SKILL.md +0 -27
  542. package/src/config/bundled-skills/watcher/TOOLS.json +0 -147
  543. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -9
  544. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -9
  545. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -9
  546. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -9
  547. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -9
  548. package/src/config/bundled-skills/weather/SKILL.md +0 -37
  549. package/src/config/bundled-skills/weather/TOOLS.json +0 -32
  550. package/src/config/bundled-skills/weather/icon.svg +0 -24
  551. package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -9
  552. package/src/config/computer-use-prompt.ts +0 -97
  553. package/src/config/defaults.ts +0 -252
  554. package/src/config/loader.ts +0 -339
  555. package/src/config/schema.ts +0 -1356
  556. package/src/config/skill-state.ts +0 -95
  557. package/src/config/skills.ts +0 -972
  558. package/src/config/system-prompt.ts +0 -675
  559. package/src/config/templates/BOOTSTRAP.md +0 -70
  560. package/src/config/templates/IDENTITY.md +0 -25
  561. package/src/config/templates/LOOKS.md +0 -25
  562. package/src/config/templates/SOUL.md +0 -37
  563. package/src/config/templates/USER.md +0 -19
  564. package/src/config/types.ts +0 -40
  565. package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +0 -179
  566. package/src/config/vellum-skills/document-writer/SKILL.md +0 -195
  567. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
  568. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +0 -153
  569. package/src/config/vellum-skills/telegram-setup/SKILL.md +0 -102
  570. package/src/contacts/contact-store.ts +0 -410
  571. package/src/contacts/index.ts +0 -11
  572. package/src/contacts/types.ts +0 -28
  573. package/src/context/token-estimator.ts +0 -108
  574. package/src/context/tool-result-truncation.ts +0 -128
  575. package/src/context/window-manager.ts +0 -531
  576. package/src/daemon/assistant-attachments.ts +0 -689
  577. package/src/daemon/classifier.ts +0 -110
  578. package/src/daemon/computer-use-session.ts +0 -903
  579. package/src/daemon/connection-policy.ts +0 -41
  580. package/src/daemon/date-context.ts +0 -136
  581. package/src/daemon/handlers/apps.ts +0 -461
  582. package/src/daemon/handlers/browser.ts +0 -54
  583. package/src/daemon/handlers/computer-use.ts +0 -187
  584. package/src/daemon/handlers/config.ts +0 -707
  585. package/src/daemon/handlers/diagnostics.ts +0 -338
  586. package/src/daemon/handlers/documents.ts +0 -173
  587. package/src/daemon/handlers/home-base.ts +0 -78
  588. package/src/daemon/handlers/identity.ts +0 -127
  589. package/src/daemon/handlers/index.ts +0 -128
  590. package/src/daemon/handlers/misc.ts +0 -331
  591. package/src/daemon/handlers/open-bundle-handler.ts +0 -80
  592. package/src/daemon/handlers/publish.ts +0 -187
  593. package/src/daemon/handlers/sessions.ts +0 -539
  594. package/src/daemon/handlers/shared.ts +0 -569
  595. package/src/daemon/handlers/signing.ts +0 -37
  596. package/src/daemon/handlers/skills.ts +0 -501
  597. package/src/daemon/handlers/subagents.ts +0 -210
  598. package/src/daemon/handlers/twitter-auth.ts +0 -198
  599. package/src/daemon/handlers/work-items.ts +0 -632
  600. package/src/daemon/handlers/workspace-files.ts +0 -75
  601. package/src/daemon/handlers.ts +0 -17
  602. package/src/daemon/history-repair.ts +0 -214
  603. package/src/daemon/ipc-blob-store.ts +0 -231
  604. package/src/daemon/ipc-contract-inventory.json +0 -463
  605. package/src/daemon/ipc-contract-inventory.ts +0 -126
  606. package/src/daemon/ipc-contract.ts +0 -2352
  607. package/src/daemon/ipc-protocol.ts +0 -75
  608. package/src/daemon/ipc-validate.ts +0 -171
  609. package/src/daemon/lifecycle.ts +0 -580
  610. package/src/daemon/main.ts +0 -21
  611. package/src/daemon/media-visibility-policy.ts +0 -57
  612. package/src/daemon/ride-shotgun-handler.ts +0 -309
  613. package/src/daemon/server.ts +0 -1207
  614. package/src/daemon/session-agent-loop.ts +0 -922
  615. package/src/daemon/session-attachments.ts +0 -196
  616. package/src/daemon/session-conflict-gate.ts +0 -128
  617. package/src/daemon/session-dynamic-profile.ts +0 -63
  618. package/src/daemon/session-error.ts +0 -290
  619. package/src/daemon/session-evictor.ts +0 -196
  620. package/src/daemon/session-history.ts +0 -437
  621. package/src/daemon/session-lifecycle.ts +0 -147
  622. package/src/daemon/session-media-retry.ts +0 -147
  623. package/src/daemon/session-memory.ts +0 -212
  624. package/src/daemon/session-messaging.ts +0 -145
  625. package/src/daemon/session-notifiers.ts +0 -193
  626. package/src/daemon/session-process.ts +0 -264
  627. package/src/daemon/session-queue-manager.ts +0 -82
  628. package/src/daemon/session-runtime-assembly.ts +0 -447
  629. package/src/daemon/session-skill-tools.ts +0 -356
  630. package/src/daemon/session-slash.ts +0 -305
  631. package/src/daemon/session-surfaces.ts +0 -702
  632. package/src/daemon/session-tool-setup.ts +0 -524
  633. package/src/daemon/session-usage.ts +0 -72
  634. package/src/daemon/session-workspace.ts +0 -19
  635. package/src/daemon/session.ts +0 -400
  636. package/src/daemon/trace-emitter.ts +0 -82
  637. package/src/daemon/video-thumbnail.ts +0 -60
  638. package/src/daemon/watch-handler.ts +0 -274
  639. package/src/doordash/client.ts +0 -999
  640. package/src/doordash/queries.ts +0 -1311
  641. package/src/doordash/query-extractor.ts +0 -93
  642. package/src/doordash/session.ts +0 -82
  643. package/src/email/provider.ts +0 -117
  644. package/src/email/providers/agentmail.ts +0 -317
  645. package/src/email/providers/index.ts +0 -58
  646. package/src/email/service.ts +0 -303
  647. package/src/email/types.ts +0 -126
  648. package/src/events/bus.ts +0 -157
  649. package/src/events/domain-events.ts +0 -83
  650. package/src/events/index.ts +0 -18
  651. package/src/events/tool-audit-listener.ts +0 -80
  652. package/src/events/tool-domain-event-publisher.ts +0 -111
  653. package/src/events/tool-metrics-listener.ts +0 -159
  654. package/src/events/tool-notification-listener.ts +0 -17
  655. package/src/events/tool-profiling-listener.ts +0 -158
  656. package/src/events/tool-trace-listener.ts +0 -75
  657. package/src/export/formatter.ts +0 -98
  658. package/src/followups/followup-store.ts +0 -168
  659. package/src/followups/index.ts +0 -10
  660. package/src/followups/types.ts +0 -29
  661. package/src/gallery/default-gallery.ts +0 -795
  662. package/src/gallery/gallery-manifest.ts +0 -24
  663. package/src/home-base/app-link-store.ts +0 -82
  664. package/src/home-base/bootstrap.ts +0 -68
  665. package/src/home-base/prebuilt/index.html +0 -662
  666. package/src/home-base/prebuilt/seed-metadata.json +0 -21
  667. package/src/home-base/prebuilt/seed.ts +0 -112
  668. package/src/home-base/prebuilt-home-base-updater.ts +0 -30
  669. package/src/hooks/cli.ts +0 -163
  670. package/src/hooks/config.ts +0 -88
  671. package/src/hooks/discovery.ts +0 -110
  672. package/src/hooks/manager.ts +0 -128
  673. package/src/hooks/runner.ts +0 -123
  674. package/src/hooks/templates.ts +0 -52
  675. package/src/hooks/types.ts +0 -72
  676. package/src/inbound/public-ingress-urls.ts +0 -123
  677. package/src/index.ts +0 -75
  678. package/src/instrument.ts +0 -60
  679. package/src/logfire.ts +0 -99
  680. package/src/media/gemini-image-service.ts +0 -136
  681. package/src/memory/account-store.ts +0 -108
  682. package/src/memory/admin.ts +0 -211
  683. package/src/memory/app-store.ts +0 -556
  684. package/src/memory/attachments-store.ts +0 -397
  685. package/src/memory/channel-delivery-store.ts +0 -353
  686. package/src/memory/checkpoints.ts +0 -52
  687. package/src/memory/clarification-resolver.ts +0 -298
  688. package/src/memory/conflict-intent.ts +0 -114
  689. package/src/memory/conflict-store.ts +0 -342
  690. package/src/memory/contradiction-checker.ts +0 -330
  691. package/src/memory/conversation-key-store.ts +0 -107
  692. package/src/memory/conversation-store.ts +0 -470
  693. package/src/memory/db.ts +0 -1825
  694. package/src/memory/embedding-backend.ts +0 -229
  695. package/src/memory/embedding-gemini.ts +0 -52
  696. package/src/memory/embedding-local.ts +0 -75
  697. package/src/memory/embedding-ollama.ts +0 -55
  698. package/src/memory/embedding-openai.ts +0 -25
  699. package/src/memory/entity-extractor.ts +0 -474
  700. package/src/memory/fingerprint.ts +0 -20
  701. package/src/memory/indexer.ts +0 -156
  702. package/src/memory/items-extractor.ts +0 -461
  703. package/src/memory/job-handlers/backfill.ts +0 -139
  704. package/src/memory/job-handlers/cleanup.ts +0 -58
  705. package/src/memory/job-handlers/conflict.ts +0 -121
  706. package/src/memory/job-handlers/embedding.ts +0 -61
  707. package/src/memory/job-handlers/extraction.ts +0 -123
  708. package/src/memory/job-handlers/index-maintenance.ts +0 -54
  709. package/src/memory/job-handlers/summarization.ts +0 -286
  710. package/src/memory/job-utils.ts +0 -170
  711. package/src/memory/jobs-store.ts +0 -401
  712. package/src/memory/jobs-worker.ts +0 -274
  713. package/src/memory/llm-request-log-store.ts +0 -45
  714. package/src/memory/llm-usage-store.ts +0 -60
  715. package/src/memory/message-content.ts +0 -54
  716. package/src/memory/profile-compiler.ts +0 -160
  717. package/src/memory/published-pages-store.ts +0 -137
  718. package/src/memory/qdrant-client.ts +0 -366
  719. package/src/memory/qdrant-manager.ts +0 -242
  720. package/src/memory/query-builder.ts +0 -45
  721. package/src/memory/retrieval-budget.ts +0 -30
  722. package/src/memory/retriever.ts +0 -653
  723. package/src/memory/runs-store.ts +0 -255
  724. package/src/memory/schema.ts +0 -588
  725. package/src/memory/search/entity.ts +0 -298
  726. package/src/memory/search/formatting.ts +0 -207
  727. package/src/memory/search/lexical.ts +0 -227
  728. package/src/memory/search/ranking.ts +0 -401
  729. package/src/memory/search/semantic.ts +0 -121
  730. package/src/memory/search/types.ts +0 -137
  731. package/src/memory/segmenter.ts +0 -68
  732. package/src/memory/shared-app-links-store.ts +0 -138
  733. package/src/memory/tool-usage-store.ts +0 -62
  734. package/src/messaging/activity-analyzer.ts +0 -76
  735. package/src/messaging/draft-store.ts +0 -88
  736. package/src/messaging/index.ts +0 -3
  737. package/src/messaging/provider-types.ts +0 -80
  738. package/src/messaging/provider.ts +0 -43
  739. package/src/messaging/providers/gmail/adapter.ts +0 -193
  740. package/src/messaging/providers/gmail/client.ts +0 -204
  741. package/src/messaging/providers/gmail/types.ts +0 -90
  742. package/src/messaging/providers/slack/adapter.ts +0 -202
  743. package/src/messaging/providers/slack/client.ts +0 -198
  744. package/src/messaging/providers/slack/types.ts +0 -119
  745. package/src/messaging/registry.ts +0 -34
  746. package/src/messaging/style-analyzer.ts +0 -159
  747. package/src/messaging/thread-summarizer.ts +0 -306
  748. package/src/messaging/triage-engine.ts +0 -323
  749. package/src/messaging/types.ts +0 -55
  750. package/src/permissions/checker.ts +0 -636
  751. package/src/permissions/defaults.ts +0 -254
  752. package/src/permissions/prompter.ts +0 -102
  753. package/src/permissions/secret-prompter.ts +0 -114
  754. package/src/permissions/trust-store.ts +0 -584
  755. package/src/permissions/types.ts +0 -62
  756. package/src/playbooks/index.ts +0 -2
  757. package/src/playbooks/playbook-compiler.ts +0 -90
  758. package/src/playbooks/types.ts +0 -55
  759. package/src/providers/anthropic/client.ts +0 -751
  760. package/src/providers/failover.ts +0 -129
  761. package/src/providers/fireworks/client.ts +0 -20
  762. package/src/providers/gemini/client.ts +0 -285
  763. package/src/providers/ollama/client.ts +0 -30
  764. package/src/providers/openai/client.ts +0 -337
  765. package/src/providers/openrouter/client.ts +0 -20
  766. package/src/providers/ratelimit.ts +0 -93
  767. package/src/providers/registry.ts +0 -146
  768. package/src/providers/retry.ts +0 -106
  769. package/src/providers/stream-timeout.ts +0 -38
  770. package/src/providers/types.ts +0 -109
  771. package/src/runtime/assistant-event-hub.ts +0 -120
  772. package/src/runtime/assistant-event.ts +0 -82
  773. package/src/runtime/gateway-client.ts +0 -42
  774. package/src/runtime/http-server.ts +0 -1056
  775. package/src/runtime/http-types.ts +0 -66
  776. package/src/runtime/routes/app-routes.ts +0 -174
  777. package/src/runtime/routes/attachment-routes.ts +0 -133
  778. package/src/runtime/routes/call-routes.ts +0 -140
  779. package/src/runtime/routes/channel-routes.ts +0 -382
  780. package/src/runtime/routes/conversation-routes.ts +0 -352
  781. package/src/runtime/routes/events-routes.ts +0 -79
  782. package/src/runtime/routes/run-routes.ts +0 -262
  783. package/src/runtime/routes/secret-routes.ts +0 -76
  784. package/src/runtime/run-orchestrator.ts +0 -296
  785. package/src/schedule/recurrence-engine.ts +0 -138
  786. package/src/schedule/recurrence-types.ts +0 -67
  787. package/src/schedule/schedule-store.ts +0 -497
  788. package/src/schedule/scheduler.ts +0 -171
  789. package/src/security/encrypted-store.ts +0 -238
  790. package/src/security/keychain.ts +0 -252
  791. package/src/security/oauth-callback-registry.ts +0 -66
  792. package/src/security/oauth2.ts +0 -274
  793. package/src/security/redaction.ts +0 -89
  794. package/src/security/secret-allowlist.ts +0 -164
  795. package/src/security/secret-ingress.ts +0 -57
  796. package/src/security/secret-scanner.ts +0 -543
  797. package/src/security/secure-keys.ts +0 -180
  798. package/src/security/token-manager.ts +0 -141
  799. package/src/services/published-app-updater.ts +0 -69
  800. package/src/services/vercel-deploy.ts +0 -73
  801. package/src/skills/active-skill-tools.ts +0 -81
  802. package/src/skills/clawhub.ts +0 -414
  803. package/src/skills/include-graph.ts +0 -146
  804. package/src/skills/managed-store.ts +0 -233
  805. package/src/skills/path-classifier.ts +0 -128
  806. package/src/skills/slash-commands.ts +0 -174
  807. package/src/skills/tool-manifest.ts +0 -165
  808. package/src/skills/version-hash.ts +0 -110
  809. package/src/slack/slack-webhook.ts +0 -61
  810. package/src/subagent/index.ts +0 -19
  811. package/src/subagent/manager.ts +0 -511
  812. package/src/subagent/types.ts +0 -69
  813. package/src/swarm/backend-claude-code.ts +0 -145
  814. package/src/swarm/index.ts +0 -44
  815. package/src/swarm/limits.ts +0 -37
  816. package/src/swarm/orchestrator.ts +0 -279
  817. package/src/swarm/plan-validator.ts +0 -151
  818. package/src/swarm/router-planner.ts +0 -100
  819. package/src/swarm/router-prompts.ts +0 -36
  820. package/src/swarm/synthesizer.ts +0 -62
  821. package/src/swarm/types.ts +0 -62
  822. package/src/swarm/worker-backend.ts +0 -121
  823. package/src/swarm/worker-prompts.ts +0 -79
  824. package/src/swarm/worker-runner.ts +0 -164
  825. package/src/tasks/SPEC.md +0 -139
  826. package/src/tasks/candidate-store.ts +0 -86
  827. package/src/tasks/ephemeral-permissions.ts +0 -50
  828. package/src/tasks/task-compiler.ts +0 -199
  829. package/src/tasks/task-runner.ts +0 -90
  830. package/src/tasks/task-scheduler.ts +0 -20
  831. package/src/tasks/task-store.ts +0 -127
  832. package/src/tasks/tool-sanitizer.ts +0 -36
  833. package/src/tools/apps/definitions.ts +0 -59
  834. package/src/tools/apps/executors.ts +0 -313
  835. package/src/tools/apps/open-proxy.ts +0 -43
  836. package/src/tools/apps/registry.ts +0 -16
  837. package/src/tools/assets/materialize.ts +0 -218
  838. package/src/tools/assets/search.ts +0 -361
  839. package/src/tools/browser/__tests__/auth-cache.test.ts +0 -219
  840. package/src/tools/browser/__tests__/auth-detector.test.ts +0 -362
  841. package/src/tools/browser/__tests__/jit-auth.test.ts +0 -189
  842. package/src/tools/browser/api-map.ts +0 -293
  843. package/src/tools/browser/auth-cache.ts +0 -149
  844. package/src/tools/browser/auth-detector.ts +0 -347
  845. package/src/tools/browser/auto-navigate.ts +0 -270
  846. package/src/tools/browser/browser-execution.ts +0 -980
  847. package/src/tools/browser/browser-handoff.ts +0 -79
  848. package/src/tools/browser/browser-manager.ts +0 -715
  849. package/src/tools/browser/browser-screencast.ts +0 -217
  850. package/src/tools/browser/headless-browser.ts +0 -450
  851. package/src/tools/browser/jit-auth.ts +0 -51
  852. package/src/tools/browser/network-recorder.ts +0 -349
  853. package/src/tools/browser/network-recording-types.ts +0 -49
  854. package/src/tools/browser/recording-store.ts +0 -49
  855. package/src/tools/browser/runtime-check.ts +0 -43
  856. package/src/tools/browser/x-auto-navigate.ts +0 -207
  857. package/src/tools/calls/call-end.ts +0 -67
  858. package/src/tools/calls/call-start.ts +0 -73
  859. package/src/tools/calls/call-status.ts +0 -81
  860. package/src/tools/claude-code/claude-code.ts +0 -428
  861. package/src/tools/computer-use/definitions.ts +0 -443
  862. package/src/tools/computer-use/registry.ts +0 -22
  863. package/src/tools/computer-use/request-computer-control.ts +0 -53
  864. package/src/tools/computer-use/skill-proxy-bridge.ts +0 -28
  865. package/src/tools/contacts/contact-merge.ts +0 -55
  866. package/src/tools/contacts/contact-search.ts +0 -58
  867. package/src/tools/contacts/contact-upsert.ts +0 -64
  868. package/src/tools/credentials/account-registry.ts +0 -127
  869. package/src/tools/credentials/broker-types.ts +0 -107
  870. package/src/tools/credentials/broker.ts +0 -372
  871. package/src/tools/credentials/domain-policy.ts +0 -51
  872. package/src/tools/credentials/host-pattern-match.ts +0 -60
  873. package/src/tools/credentials/metadata-store.ts +0 -335
  874. package/src/tools/credentials/policy-types.ts +0 -52
  875. package/src/tools/credentials/policy-validate.ts +0 -80
  876. package/src/tools/credentials/resolve.ts +0 -122
  877. package/src/tools/credentials/selection.ts +0 -159
  878. package/src/tools/credentials/tool-policy.ts +0 -25
  879. package/src/tools/credentials/vault.ts +0 -657
  880. package/src/tools/document/document-tool.ts +0 -92
  881. package/src/tools/document/editor-template.ts +0 -237
  882. package/src/tools/executor.ts +0 -944
  883. package/src/tools/filesystem/edit.ts +0 -127
  884. package/src/tools/filesystem/fuzzy-match.ts +0 -202
  885. package/src/tools/filesystem/read.ts +0 -71
  886. package/src/tools/filesystem/view-image.ts +0 -199
  887. package/src/tools/filesystem/write.ts +0 -79
  888. package/src/tools/followups/followup_create.ts +0 -76
  889. package/src/tools/followups/followup_list.ts +0 -60
  890. package/src/tools/followups/followup_resolve.ts +0 -56
  891. package/src/tools/host-filesystem/edit.ts +0 -125
  892. package/src/tools/host-filesystem/read.ts +0 -80
  893. package/src/tools/host-filesystem/write.ts +0 -76
  894. package/src/tools/host-terminal/cli-discover.ts +0 -180
  895. package/src/tools/host-terminal/host-shell.ts +0 -191
  896. package/src/tools/memory/definitions.ts +0 -69
  897. package/src/tools/memory/handlers.ts +0 -246
  898. package/src/tools/memory/register.ts +0 -66
  899. package/src/tools/network/__tests__/web-search.test.ts +0 -427
  900. package/src/tools/network/domain-normalize.ts +0 -85
  901. package/src/tools/network/script-proxy/__tests__/logging.test.ts +0 -248
  902. package/src/tools/network/script-proxy/__tests__/policy.test.ts +0 -234
  903. package/src/tools/network/script-proxy/__tests__/router.test.ts +0 -76
  904. package/src/tools/network/script-proxy/certs.ts +0 -237
  905. package/src/tools/network/script-proxy/connect-tunnel.ts +0 -82
  906. package/src/tools/network/script-proxy/http-forwarder.ts +0 -151
  907. package/src/tools/network/script-proxy/index.ts +0 -28
  908. package/src/tools/network/script-proxy/logging.ts +0 -196
  909. package/src/tools/network/script-proxy/mitm-handler.ts +0 -269
  910. package/src/tools/network/script-proxy/policy.ts +0 -152
  911. package/src/tools/network/script-proxy/router.ts +0 -60
  912. package/src/tools/network/script-proxy/server.ts +0 -136
  913. package/src/tools/network/script-proxy/session-manager.ts +0 -534
  914. package/src/tools/network/script-proxy/types.ts +0 -125
  915. package/src/tools/network/url-safety.ts +0 -227
  916. package/src/tools/network/web-fetch.ts +0 -713
  917. package/src/tools/network/web-search.ts +0 -319
  918. package/src/tools/playbooks/index.ts +0 -4
  919. package/src/tools/playbooks/playbook-create.ts +0 -96
  920. package/src/tools/playbooks/playbook-delete.ts +0 -52
  921. package/src/tools/playbooks/playbook-list.ts +0 -74
  922. package/src/tools/playbooks/playbook-update.ts +0 -111
  923. package/src/tools/registry.ts +0 -295
  924. package/src/tools/reminder/reminder-store.ts +0 -148
  925. package/src/tools/reminder/reminder.ts +0 -80
  926. package/src/tools/schedule/create.ts +0 -81
  927. package/src/tools/schedule/delete.ts +0 -28
  928. package/src/tools/schedule/list.ts +0 -69
  929. package/src/tools/schedule/update.ts +0 -90
  930. package/src/tools/shared/filesystem/edit-engine.ts +0 -56
  931. package/src/tools/shared/filesystem/errors.ts +0 -85
  932. package/src/tools/shared/filesystem/file-ops-service.ts +0 -215
  933. package/src/tools/shared/filesystem/format-diff.ts +0 -35
  934. package/src/tools/shared/filesystem/path-policy.ts +0 -125
  935. package/src/tools/shared/filesystem/size-guard.ts +0 -41
  936. package/src/tools/shared/filesystem/types.ts +0 -80
  937. package/src/tools/shared/shell-output.ts +0 -52
  938. package/src/tools/skills/delete-managed.ts +0 -60
  939. package/src/tools/skills/load.ts +0 -139
  940. package/src/tools/skills/sandbox-runner.ts +0 -279
  941. package/src/tools/skills/scaffold-managed.ts +0 -150
  942. package/src/tools/skills/script-contract.ts +0 -6
  943. package/src/tools/skills/skill-script-runner.ts +0 -86
  944. package/src/tools/skills/skill-tool-factory.ts +0 -64
  945. package/src/tools/skills/vellum-catalog.ts +0 -217
  946. package/src/tools/subagent/abort.ts +0 -33
  947. package/src/tools/subagent/message.ts +0 -39
  948. package/src/tools/subagent/read.ts +0 -67
  949. package/src/tools/subagent/spawn.ts +0 -46
  950. package/src/tools/subagent/status.ts +0 -45
  951. package/src/tools/swarm/delegate.ts +0 -183
  952. package/src/tools/system/request-permission.ts +0 -98
  953. package/src/tools/system/version.ts +0 -43
  954. package/src/tools/tasks/index.ts +0 -27
  955. package/src/tools/tasks/task-delete.ts +0 -82
  956. package/src/tools/tasks/task-list.ts +0 -44
  957. package/src/tools/tasks/task-run.ts +0 -97
  958. package/src/tools/tasks/task-save.ts +0 -47
  959. package/src/tools/tasks/work-item-enqueue.ts +0 -234
  960. package/src/tools/tasks/work-item-list.ts +0 -55
  961. package/src/tools/tasks/work-item-remove.ts +0 -60
  962. package/src/tools/tasks/work-item-run.ts +0 -78
  963. package/src/tools/tasks/work-item-update.ts +0 -114
  964. package/src/tools/terminal/backends/docker.ts +0 -372
  965. package/src/tools/terminal/backends/native.ts +0 -190
  966. package/src/tools/terminal/backends/types.ts +0 -26
  967. package/src/tools/terminal/evaluate-typescript.ts +0 -275
  968. package/src/tools/terminal/parser.ts +0 -415
  969. package/src/tools/terminal/safe-env.ts +0 -37
  970. package/src/tools/terminal/sandbox-diagnostics.ts +0 -149
  971. package/src/tools/terminal/sandbox.ts +0 -44
  972. package/src/tools/terminal/shell.ts +0 -257
  973. package/src/tools/tool-manifest.ts +0 -198
  974. package/src/tools/types.ts +0 -183
  975. package/src/tools/ui-surface/definitions.ts +0 -244
  976. package/src/tools/ui-surface/registry.ts +0 -14
  977. package/src/tools/watch/screen-watch.ts +0 -130
  978. package/src/tools/watch/watch-state.ts +0 -119
  979. package/src/tools/watcher/create.ts +0 -64
  980. package/src/tools/watcher/delete.ts +0 -27
  981. package/src/tools/watcher/digest.ts +0 -50
  982. package/src/tools/watcher/list.ts +0 -60
  983. package/src/tools/watcher/update.ts +0 -56
  984. package/src/tools/weather/service.ts +0 -551
  985. package/src/twitter/client.ts +0 -690
  986. package/src/twitter/session.ts +0 -91
  987. package/src/usage/actors.ts +0 -24
  988. package/src/usage/types.ts +0 -37
  989. package/src/util/clipboard.ts +0 -33
  990. package/src/util/content-id.ts +0 -16
  991. package/src/util/diff.ts +0 -181
  992. package/src/util/errors.ts +0 -129
  993. package/src/util/logger.ts +0 -243
  994. package/src/util/platform.ts +0 -607
  995. package/src/util/pricing.ts +0 -150
  996. package/src/util/spinner.ts +0 -51
  997. package/src/util/time.ts +0 -16
  998. package/src/util/truncate.ts +0 -6
  999. package/src/util/xml.ts +0 -4
  1000. package/src/version.ts +0 -3
  1001. package/src/watcher/constants.ts +0 -11
  1002. package/src/watcher/engine.ts +0 -199
  1003. package/src/watcher/provider-registry.ts +0 -15
  1004. package/src/watcher/provider-types.ts +0 -48
  1005. package/src/watcher/providers/gmail.ts +0 -198
  1006. package/src/watcher/providers/google-calendar.ts +0 -228
  1007. package/src/watcher/providers/slack.ts +0 -129
  1008. package/src/watcher/watcher-store.ts +0 -419
  1009. package/src/work-items/work-item-runner.ts +0 -171
  1010. package/src/work-items/work-item-store.ts +0 -325
  1011. package/src/workspace/commit-message-enrichment-service.ts +0 -284
  1012. package/src/workspace/commit-message-provider.ts +0 -95
  1013. package/src/workspace/git-service.ts +0 -840
  1014. package/src/workspace/heartbeat-service.ts +0 -345
  1015. package/src/workspace/provider-commit-message-generator.ts +0 -285
  1016. package/src/workspace/top-level-renderer.ts +0 -19
  1017. package/src/workspace/top-level-scanner.ts +0 -41
  1018. package/src/workspace/turn-commit.ts +0 -175
  1019. package/tsconfig.json +0 -21
@@ -1,2431 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterAll, mock } from 'bun:test';
2
- import * as realFs from 'node:fs';
3
- import type { Message, ToolDefinition, ToolUseContent, ToolResultContent } from '../providers/types.js';
4
- import type { SkillSummary, SkillToolManifest } from '../config/skills.js';
5
- import type { Tool } from '../tools/types.js';
6
- import { RiskLevel } from '../permissions/types.js';
7
- import {
8
- BROWSER_TOOL_NAMES,
9
- buildSkillLoadHistory,
10
- assertBrowserToolsPresent,
11
- assertBrowserToolsAbsent,
12
- } from './test-support/browser-skill-harness.js';
13
-
14
- // ---------------------------------------------------------------------------
15
- // Mock state — controlled by tests
16
- // ---------------------------------------------------------------------------
17
-
18
- let mockCatalog: SkillSummary[] = [];
19
- let mockManifests: Record<string, SkillToolManifest | null> = {};
20
- let mockRegisteredTools: Map<string, Tool[]> = new Map();
21
- let mockUnregisteredSkillIds: string[] = [];
22
- let mockSkillRefCount: Map<string, number> = new Map();
23
- /** Per-skill version hash overrides. When set, computeSkillVersionHash returns this value. */
24
- let mockVersionHashes: Record<string, string> = {};
25
- /** Skill IDs for which computeSkillVersionHash should throw (simulates unreadable directories). */
26
- let mockVersionHashErrors: Set<string> = new Set();
27
-
28
- // ---------------------------------------------------------------------------
29
- // Mocks — must be set up before importing the module under test
30
- // ---------------------------------------------------------------------------
31
-
32
- mock.module('../config/skills.js', () => ({
33
- loadSkillCatalog: () => mockCatalog,
34
- }));
35
-
36
- mock.module('../skills/active-skill-tools.js', () => {
37
- // Shared parsing logic for both deriveActiveSkills and deriveActiveSkillIds
38
- const parseMarkers = (messages: Message[]) => {
39
- // Two-pass approach matching real implementation:
40
- // 1. Collect tool_use IDs where name === 'skill_load'
41
- const skillLoadUseIds = new Set<string>();
42
- for (const msg of messages) {
43
- for (const block of msg.content) {
44
- if (block.type === 'tool_use' && block.name === 'skill_load') {
45
- skillLoadUseIds.add(block.id);
46
- }
47
- }
48
- }
49
-
50
- // 2. Parse markers only from tool_result blocks whose tool_use_id matches
51
- const re = /<loaded_skill\s+id="([^"]+)"(?:\s+version="([^"]+)")?\s*\/>/g;
52
- const seen = new Set<string>();
53
- const entries: Array<{ id: string; version?: string }> = [];
54
- for (const msg of messages) {
55
- for (const block of msg.content) {
56
- if (block.type !== 'tool_result') continue;
57
- if (!skillLoadUseIds.has(block.tool_use_id)) continue;
58
- const text = block.content;
59
- if (!text) continue;
60
- for (const match of text.matchAll(re)) {
61
- if (!seen.has(match[1])) {
62
- seen.add(match[1]);
63
- const entry: { id: string; version?: string } = { id: match[1] };
64
- if (match[2]) {
65
- entry.version = match[2];
66
- }
67
- entries.push(entry);
68
- }
69
- }
70
- }
71
- }
72
- return entries;
73
- };
74
-
75
- return {
76
- deriveActiveSkills: (messages: Message[]) => parseMarkers(messages),
77
- deriveActiveSkillIds: (messages: Message[]) =>
78
- parseMarkers(messages).map((e) => e.id),
79
- };
80
- });
81
-
82
- mock.module('../skills/tool-manifest.js', () => ({
83
- parseToolManifestFile: (filePath: string) => {
84
- // Extract skill ID from path: /skills/<id>/TOOLS.json → <id>
85
- const parts = filePath.split('/');
86
- const skillId = parts[parts.length - 2];
87
- const manifest = mockManifests[skillId];
88
- if (!manifest) {
89
- throw new Error(`Mock: no manifest for skill "${skillId}"`);
90
- }
91
- return manifest;
92
- },
93
- }));
94
-
95
- mock.module('../tools/skills/skill-tool-factory.js', () => ({
96
- createSkillToolsFromManifest: (
97
- entries: SkillToolManifest['tools'],
98
- skillId: string,
99
- _skillDir: string,
100
- versionHash: string,
101
- bundled?: boolean,
102
- ): Tool[] => {
103
- return entries.map((entry) => ({
104
- name: entry.name,
105
- description: entry.description,
106
- category: entry.category,
107
- defaultRiskLevel: RiskLevel.Medium,
108
- origin: 'skill' as const,
109
- ownerSkillId: skillId,
110
- ownerSkillVersionHash: versionHash,
111
- ownerSkillBundled: bundled ?? undefined,
112
- getDefinition: () => ({
113
- name: entry.name,
114
- description: entry.description,
115
- input_schema: entry.input_schema as object,
116
- }),
117
- execute: async () => ({ content: '', isError: false }),
118
- }));
119
- },
120
- }));
121
-
122
- mock.module('../tools/registry.js', () => ({
123
- registerSkillTools: (tools: Tool[]) => {
124
- const skillIds = new Set<string>();
125
- for (const tool of tools) {
126
- const skillId = tool.ownerSkillId!;
127
- skillIds.add(skillId);
128
- const existing = mockRegisteredTools.get(skillId) ?? [];
129
- existing.push(tool);
130
- mockRegisteredTools.set(skillId, existing);
131
- }
132
- for (const id of skillIds) {
133
- mockSkillRefCount.set(id, (mockSkillRefCount.get(id) ?? 0) + 1);
134
- }
135
- },
136
- unregisterSkillTools: (skillId: string) => {
137
- mockUnregisteredSkillIds.push(skillId);
138
- const current = mockSkillRefCount.get(skillId) ?? 0;
139
- if (current > 1) {
140
- mockSkillRefCount.set(skillId, current - 1);
141
- return;
142
- }
143
- mockSkillRefCount.delete(skillId);
144
- mockRegisteredTools.delete(skillId);
145
- },
146
- getTool: (name: string): Tool | undefined => {
147
- // Return the last matching tool to match production behavior where
148
- // re-registering a tool overwrites the previous entry (last wins).
149
- let found: Tool | undefined;
150
- for (const tools of mockRegisteredTools.values()) {
151
- for (const tool of tools) {
152
- if (tool.name === name) found = tool;
153
- }
154
- }
155
- return found;
156
- },
157
- getSkillToolNames: () => {
158
- const names: string[] = [];
159
- for (const tools of mockRegisteredTools.values()) {
160
- for (const tool of tools) {
161
- names.push(tool.name);
162
- }
163
- }
164
- return names;
165
- },
166
- }));
167
-
168
- // Stub existsSync so TOOLS.json existence checks pass for skills that have manifests
169
- mock.module('node:fs', () => ({
170
- ...realFs,
171
- existsSync: (p: string) => {
172
- if (typeof p === 'string' && p.endsWith('TOOLS.json')) {
173
- const parts = p.split('/');
174
- const skillId = parts[parts.length - 2];
175
- return skillId in mockManifests;
176
- }
177
- return realFs.existsSync(p);
178
- },
179
- }));
180
-
181
- mock.module('../skills/version-hash.js', () => ({
182
- computeSkillVersionHash: (skillDir: string) => {
183
- const parts = skillDir.split('/');
184
- const skillId = parts[parts.length - 1];
185
- if (mockVersionHashErrors.has(skillId)) {
186
- throw new Error(`EACCES: permission denied, scandir '${skillDir}'`);
187
- }
188
- if (skillId in mockVersionHashes) {
189
- return mockVersionHashes[skillId];
190
- }
191
- return `v1:default-hash-${skillId}`;
192
- },
193
- }));
194
-
195
- mock.module('../util/logger.js', () => ({
196
- getLogger: () => ({
197
- info: () => {},
198
- warn: () => {},
199
- debug: () => {},
200
- error: () => {},
201
- }),
202
- }));
203
-
204
- // ---------------------------------------------------------------------------
205
- // Import module under test (after mocks)
206
- // ---------------------------------------------------------------------------
207
-
208
- const { projectSkillTools, resetSkillToolProjection } = await import(
209
- '../daemon/session-skill-tools.js'
210
- );
211
-
212
- // ---------------------------------------------------------------------------
213
- // Helpers
214
- // ---------------------------------------------------------------------------
215
-
216
- function makeSkill(id: string, dir?: string): SkillSummary {
217
- return {
218
- id,
219
- name: id,
220
- description: `Skill ${id}`,
221
- directoryPath: dir ?? `/skills/${id}`,
222
- skillFilePath: `/skills/${id}/SKILL.md`,
223
- userInvocable: true,
224
- disableModelInvocation: false,
225
- source: 'managed',
226
- };
227
- }
228
-
229
- function makeManifest(toolNames: string[]): SkillToolManifest {
230
- return {
231
- version: 1,
232
- tools: toolNames.map((name) => ({
233
- name,
234
- description: `Tool ${name}`,
235
- category: 'test',
236
- risk: 'medium' as const,
237
- input_schema: { type: 'object', properties: {} },
238
- executor: 'run.ts',
239
- execution_target: 'host' as const,
240
- })),
241
- };
242
- }
243
-
244
- let toolUseCounter = 0;
245
-
246
- /**
247
- * Creates a pair of messages representing a skill_load tool_use followed by
248
- * its tool_result with the given content (typically a `<loaded_skill>` marker).
249
- */
250
- function skillLoadMessages(content: string): Message[] {
251
- const id = `sl-${++toolUseCounter}`;
252
- return [
253
- {
254
- role: 'assistant',
255
- content: [{ type: 'tool_use', id, name: 'skill_load', input: {} }],
256
- },
257
- {
258
- role: 'user',
259
- content: [{ type: 'tool_result', tool_use_id: id, content }],
260
- },
261
- ];
262
- }
263
-
264
- // ---------------------------------------------------------------------------
265
- // Tests
266
- // ---------------------------------------------------------------------------
267
-
268
- afterAll(() => { mock.restore(); });
269
-
270
- describe('projectSkillTools', () => {
271
- let sessionState: Map<string, string>;
272
-
273
- beforeEach(() => {
274
- mockCatalog = [];
275
- mockManifests = {};
276
- mockRegisteredTools = new Map();
277
- mockUnregisteredSkillIds = [];
278
- mockSkillRefCount = new Map();
279
- mockSkillRefCount = new Map();
280
- mockVersionHashes = {};
281
- mockVersionHashErrors = new Set();
282
- sessionState = new Map<string, string>();
283
- });
284
-
285
- test('no active skills returns empty projection', () => {
286
- const result = projectSkillTools([], { previouslyActiveSkillIds: sessionState });
287
-
288
- expect(result.toolDefinitions).toEqual([]);
289
- expect(result.allowedToolNames.size).toBe(0);
290
- });
291
-
292
- test('active skill with valid manifest returns tool definitions', () => {
293
- mockCatalog = [makeSkill('deploy')];
294
- mockManifests = { deploy: makeManifest(['deploy_run', 'deploy_status']) };
295
-
296
- const history: Message[] = [
297
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
298
- ];
299
-
300
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
301
-
302
- expect(result.toolDefinitions).toHaveLength(2);
303
- expect(result.toolDefinitions.map((d) => d.name)).toEqual([
304
- 'deploy_run',
305
- 'deploy_status',
306
- ]);
307
- expect(result.allowedToolNames).toEqual(
308
- new Set(['deploy_run', 'deploy_status']),
309
- );
310
- });
311
-
312
- test('multiple active skills are projected', () => {
313
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
314
- mockManifests = {
315
- deploy: makeManifest(['deploy_run']),
316
- oncall: makeManifest(['oncall_page']),
317
- };
318
-
319
- const history: Message[] = [
320
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
321
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
322
- ];
323
-
324
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
325
-
326
- expect(result.toolDefinitions).toHaveLength(2);
327
- expect(result.allowedToolNames).toEqual(
328
- new Set(['deploy_run', 'oncall_page']),
329
- );
330
- });
331
-
332
- test('preactivated skill IDs are included', () => {
333
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
334
- mockManifests = {
335
- deploy: makeManifest(['deploy_run']),
336
- oncall: makeManifest(['oncall_page']),
337
- };
338
-
339
- // Only deploy is in history; oncall is preactivated
340
- const history: Message[] = [
341
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
342
- ];
343
-
344
- const result = projectSkillTools(history, {
345
- preactivatedSkillIds: ['oncall'],
346
- previouslyActiveSkillIds: sessionState,
347
- });
348
-
349
- expect(result.toolDefinitions).toHaveLength(2);
350
- expect(result.allowedToolNames).toEqual(
351
- new Set(['deploy_run', 'oncall_page']),
352
- );
353
- });
354
-
355
- test('skill deactivation: previously active skill is unregistered when removed from history', () => {
356
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
357
- mockManifests = {
358
- deploy: makeManifest(['deploy_run']),
359
- oncall: makeManifest(['oncall_page']),
360
- };
361
-
362
- // First turn: both skills active
363
- const history1: Message[] = [
364
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
365
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
366
- ];
367
- projectSkillTools(history1, { previouslyActiveSkillIds: sessionState });
368
-
369
- // Second turn: only deploy remains active (oncall marker gone)
370
- mockUnregisteredSkillIds = [];
371
- const history2: Message[] = [
372
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
373
- ];
374
- const result = projectSkillTools(history2, { previouslyActiveSkillIds: sessionState });
375
-
376
- expect(mockUnregisteredSkillIds).toContain('oncall');
377
- expect(result.allowedToolNames).toEqual(new Set(['deploy_run']));
378
- });
379
-
380
- test('invalid/missing manifest is gracefully handled', () => {
381
- mockCatalog = [makeSkill('broken')];
382
- // No manifest registered for "broken", so parseToolManifestFile will throw
383
-
384
- const history: Message[] = [
385
- ...skillLoadMessages('<loaded_skill id="broken" />'),
386
- ];
387
-
388
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
389
-
390
- // Should not throw, just return empty projection for that skill
391
- expect(result.toolDefinitions).toEqual([]);
392
- expect(result.allowedToolNames.size).toBe(0);
393
- });
394
-
395
- test('skill ID not in catalog is gracefully skipped', () => {
396
- mockCatalog = []; // empty catalog
397
- mockManifests = {};
398
-
399
- const history: Message[] = [
400
- ...skillLoadMessages('<loaded_skill id="nonexistent" />'),
401
- ];
402
-
403
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
404
-
405
- expect(result.toolDefinitions).toEqual([]);
406
- expect(result.allowedToolNames.size).toBe(0);
407
- });
408
-
409
- test('skill with catalog miss on turn 1 is registered when catalog is populated on turn 2', () => {
410
- // Turn 1: skill is active but NOT in the catalog — should not be tracked
411
- mockCatalog = []; // empty catalog
412
- mockManifests = {};
413
-
414
- const history: Message[] = [
415
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
416
- ];
417
-
418
- const result1 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
419
- expect(result1.toolDefinitions).toEqual([]);
420
- expect(sessionState.has('deploy')).toBe(false);
421
-
422
- // Turn 2: catalog now has the skill — should register successfully
423
- mockCatalog = [makeSkill('deploy')];
424
- mockManifests = { deploy: makeManifest(['deploy_run']) };
425
-
426
- const result2 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
427
- expect(result2.toolDefinitions).toHaveLength(1);
428
- expect(result2.toolDefinitions[0].name).toBe('deploy_run');
429
- expect(result2.allowedToolNames.has('deploy_run')).toBe(true);
430
- expect(sessionState.has('deploy')).toBe(true);
431
-
432
- // Verify registerSkillTools was called (tool is in the registry)
433
- expect(mockRegisteredTools.has('deploy')).toBe(true);
434
- });
435
-
436
- test('skill with manifest failure on turn 1 is registered when manifest is available on turn 2', () => {
437
- mockCatalog = [makeSkill('deploy')];
438
- // No manifest — will fail to load
439
- mockManifests = {};
440
-
441
- const history: Message[] = [
442
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
443
- ];
444
-
445
- const result1 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
446
- expect(result1.toolDefinitions).toEqual([]);
447
- expect(sessionState.has('deploy')).toBe(false);
448
-
449
- // Turn 2: manifest now available
450
- mockManifests = { deploy: makeManifest(['deploy_run']) };
451
-
452
- const result2 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
453
- expect(result2.toolDefinitions).toHaveLength(1);
454
- expect(result2.toolDefinitions[0].name).toBe('deploy_run');
455
- expect(sessionState.has('deploy')).toBe(true);
456
- expect(mockRegisteredTools.has('deploy')).toBe(true);
457
- });
458
-
459
- test('previously-registered skill that transiently fails is unregistered to prevent refcount leak', () => {
460
- mockCatalog = [makeSkill('deploy')];
461
- mockManifests = { deploy: makeManifest(['deploy_run']) };
462
-
463
- const history: Message[] = [
464
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
465
- ];
466
-
467
- // Turn 1: skill registered successfully
468
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
469
- expect(sessionState.has('deploy')).toBe(true);
470
- expect(mockSkillRefCount.get('deploy')).toBe(1);
471
-
472
- // Turn 2: manifest transiently fails — skill should be unregistered
473
- mockManifests = {};
474
- mockUnregisteredSkillIds = [];
475
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
476
- expect(sessionState.has('deploy')).toBe(false);
477
- expect(mockUnregisteredSkillIds).toContain('deploy');
478
- // Ref count should be 0 (properly decremented)
479
- expect(mockSkillRefCount.has('deploy')).toBe(false);
480
-
481
- // Turn 3: manifest recovers — skill re-registered with correct ref count
482
- mockManifests = { deploy: makeManifest(['deploy_run']) };
483
- mockUnregisteredSkillIds = [];
484
- const result3 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
485
- expect(result3.toolDefinitions).toHaveLength(1);
486
- expect(sessionState.has('deploy')).toBe(true);
487
- // Ref count should be exactly 1, not 2
488
- expect(mockSkillRefCount.get('deploy')).toBe(1);
489
- });
490
-
491
- test('skill version hash change triggers unregister and re-register', () => {
492
- mockCatalog = [makeSkill('deploy')];
493
- mockManifests = { deploy: makeManifest(['deploy_run']) };
494
- mockVersionHashes = { deploy: 'v1:hash-aaa' };
495
-
496
- const history: Message[] = [
497
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
498
- ];
499
-
500
- // Turn 1: skill registered with hash-aaa
501
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
502
- expect(sessionState.has('deploy')).toBe(true);
503
- expect(sessionState.get('deploy')).toBe('v1:hash-aaa');
504
- expect(mockSkillRefCount.get('deploy')).toBe(1);
505
-
506
- // Turn 2: hash changes — should unregister old and re-register new
507
- mockVersionHashes = { deploy: 'v1:hash-bbb' };
508
- mockUnregisteredSkillIds = [];
509
- const result2 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
510
- expect(result2.toolDefinitions).toHaveLength(1);
511
- expect(result2.toolDefinitions[0].name).toBe('deploy_run');
512
- expect(sessionState.get('deploy')).toBe('v1:hash-bbb');
513
- // Unregister was called for the stale version
514
- expect(mockUnregisteredSkillIds).toContain('deploy');
515
- // Ref count should remain 1 (unregister decremented, re-register incremented)
516
- expect(mockSkillRefCount.get('deploy')).toBe(1);
517
- });
518
-
519
- test('skill version hash unchanged skips re-registration', () => {
520
- mockCatalog = [makeSkill('deploy')];
521
- mockManifests = { deploy: makeManifest(['deploy_run']) };
522
- mockVersionHashes = { deploy: 'v1:stable-hash' };
523
-
524
- const history: Message[] = [
525
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
526
- ];
527
-
528
- // Turn 1: skill registered
529
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
530
- expect(mockSkillRefCount.get('deploy')).toBe(1);
531
-
532
- // Turn 2: same hash — should NOT call registerSkillTools again
533
- mockUnregisteredSkillIds = [];
534
- const result2 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
535
- expect(result2.toolDefinitions).toHaveLength(1);
536
- expect(mockUnregisteredSkillIds).not.toContain('deploy');
537
- // Ref count should still be 1 (no additional registration)
538
- expect(mockSkillRefCount.get('deploy')).toBe(1);
539
- });
540
-
541
- test('preactivated IDs merge with context-derived IDs (dedup)', () => {
542
- mockCatalog = [makeSkill('deploy')];
543
- mockManifests = { deploy: makeManifest(['deploy_run']) };
544
-
545
- const history: Message[] = [
546
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
547
- ];
548
-
549
- // deploy is both in history AND preactivated — should not duplicate
550
- const result = projectSkillTools(history, {
551
- preactivatedSkillIds: ['deploy'],
552
- previouslyActiveSkillIds: sessionState,
553
- });
554
-
555
- expect(result.toolDefinitions).toHaveLength(1);
556
- expect(result.allowedToolNames).toEqual(new Set(['deploy_run']));
557
- });
558
-
559
- test('no markers in history with preactivated IDs still projects tools', () => {
560
- mockCatalog = [makeSkill('oncall')];
561
- mockManifests = { oncall: makeManifest(['oncall_page']) };
562
-
563
- const result = projectSkillTools([], {
564
- preactivatedSkillIds: ['oncall'],
565
- previouslyActiveSkillIds: sessionState,
566
- });
567
-
568
- expect(result.toolDefinitions).toHaveLength(1);
569
- expect(result.allowedToolNames).toEqual(new Set(['oncall_page']));
570
- });
571
-
572
- test('concurrent sessions do not interfere with each other', () => {
573
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
574
- mockManifests = {
575
- deploy: makeManifest(['deploy_run']),
576
- oncall: makeManifest(['oncall_page']),
577
- };
578
-
579
- const sessionA = new Map<string, string>();
580
- const sessionB = new Map<string, string>();
581
-
582
- // Session A activates deploy
583
- const historyA: Message[] = [...skillLoadMessages('<loaded_skill id="deploy" />')];
584
- const resultA = projectSkillTools(historyA, { previouslyActiveSkillIds: sessionA });
585
- expect(resultA.allowedToolNames.has('deploy_run')).toBe(true);
586
-
587
- // Session B activates oncall — should NOT unregister deploy from session A
588
- mockUnregisteredSkillIds = [];
589
- const historyB: Message[] = [...skillLoadMessages('<loaded_skill id="oncall" />')];
590
- projectSkillTools(historyB, { previouslyActiveSkillIds: sessionB });
591
- expect(mockUnregisteredSkillIds).not.toContain('deploy');
592
-
593
- // Session A's state should still track deploy
594
- expect(sessionA.has('deploy')).toBe(true);
595
- expect(sessionB.has('oncall')).toBe(true);
596
- });
597
-
598
- test('disposing session A while session B uses the same skill does NOT remove tools', () => {
599
- mockCatalog = [makeSkill('deploy')];
600
- mockManifests = { deploy: makeManifest(['deploy_run']) };
601
-
602
- const sessionA = new Map<string, string>();
603
- const sessionB = new Map<string, string>();
604
-
605
- const history: Message[] = [...skillLoadMessages('<loaded_skill id="deploy" />')];
606
-
607
- // Both sessions activate deploy
608
- projectSkillTools(history, { previouslyActiveSkillIds: sessionA });
609
- projectSkillTools(history, { previouslyActiveSkillIds: sessionB });
610
-
611
- // Ref count should be 2
612
- expect(mockSkillRefCount.get('deploy')).toBe(2);
613
-
614
- // Session A tears down
615
- resetSkillToolProjection(sessionA);
616
-
617
- // Tools should still be registered (ref count decremented but > 0)
618
- expect(mockRegisteredTools.has('deploy')).toBe(true);
619
- expect(mockSkillRefCount.get('deploy')).toBe(1);
620
-
621
- // Session B can still project the skill tools
622
- const resultB = projectSkillTools(history, { previouslyActiveSkillIds: sessionB });
623
- expect(resultB.allowedToolNames.has('deploy_run')).toBe(true);
624
- });
625
-
626
- test('tools ARE removed when the last session using them disposes', () => {
627
- mockCatalog = [makeSkill('deploy')];
628
- mockManifests = { deploy: makeManifest(['deploy_run']) };
629
-
630
- const sessionA = new Map<string, string>();
631
- const sessionB = new Map<string, string>();
632
-
633
- const history: Message[] = [...skillLoadMessages('<loaded_skill id="deploy" />')];
634
-
635
- // Both sessions activate deploy
636
- projectSkillTools(history, { previouslyActiveSkillIds: sessionA });
637
- projectSkillTools(history, { previouslyActiveSkillIds: sessionB });
638
-
639
- // Both sessions tear down
640
- resetSkillToolProjection(sessionA);
641
- expect(mockRegisteredTools.has('deploy')).toBe(true);
642
-
643
- resetSkillToolProjection(sessionB);
644
- expect(mockRegisteredTools.has('deploy')).toBe(false);
645
- expect(mockSkillRefCount.has('deploy')).toBe(false);
646
- });
647
- });
648
-
649
- // ---------------------------------------------------------------------------
650
- // resolveTools callback integration tests
651
- // ---------------------------------------------------------------------------
652
-
653
- describe('resolveTools callback (session wiring)', () => {
654
- // Simulates the resolveTools callback wired in the Session constructor:
655
- // (history) => [...baseToolDefs, ...projectSkillTools(history).toolDefinitions]
656
- const baseToolDefs: ToolDefinition[] = [
657
- { name: 'file_read', description: 'Read a file', input_schema: { type: 'object', properties: {} } },
658
- { name: 'bash', description: 'Run a shell command', input_schema: { type: 'object', properties: {} } },
659
- ];
660
-
661
- let sessionState: Map<string, string>;
662
-
663
- function makeResolveTools(base: ToolDefinition[]) {
664
- return (history: Message[]): ToolDefinition[] => {
665
- const projection = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
666
- return [...base, ...projection.toolDefinitions];
667
- };
668
- }
669
-
670
- beforeEach(() => {
671
- mockCatalog = [];
672
- mockManifests = {};
673
- mockRegisteredTools = new Map();
674
- mockUnregisteredSkillIds = [];
675
- mockSkillRefCount = new Map();
676
- mockSkillRefCount = new Map();
677
- mockVersionHashes = {};
678
- mockVersionHashErrors = new Set();
679
- sessionState = new Map<string, string>();
680
- });
681
-
682
- test('returns only base tools when no skills are active', () => {
683
- const resolveTools = makeResolveTools(baseToolDefs);
684
- const result = resolveTools([]);
685
-
686
- expect(result).toHaveLength(2);
687
- expect(result.map((d) => d.name)).toEqual(['file_read', 'bash']);
688
- });
689
-
690
- test('combines base tools with projected skill tools', () => {
691
- mockCatalog = [makeSkill('deploy')];
692
- mockManifests = { deploy: makeManifest(['deploy_run', 'deploy_status']) };
693
-
694
- const resolveTools = makeResolveTools(baseToolDefs);
695
- const history: Message[] = [
696
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
697
- ];
698
-
699
- const result = resolveTools(history);
700
-
701
- expect(result).toHaveLength(4);
702
- expect(result.map((d) => d.name)).toEqual([
703
- 'file_read',
704
- 'bash',
705
- 'deploy_run',
706
- 'deploy_status',
707
- ]);
708
- });
709
-
710
- test('skill tools appear after base tools and do not replace them', () => {
711
- mockCatalog = [makeSkill('oncall')];
712
- mockManifests = { oncall: makeManifest(['oncall_page']) };
713
-
714
- const resolveTools = makeResolveTools(baseToolDefs);
715
- const history: Message[] = [
716
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
717
- ];
718
-
719
- const result = resolveTools(history);
720
-
721
- // Base tools come first, skill tools are appended
722
- expect(result[0].name).toBe('file_read');
723
- expect(result[1].name).toBe('bash');
724
- expect(result[2].name).toBe('oncall_page');
725
- });
726
-
727
- test('multiple skills add all their tools alongside base tools', () => {
728
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
729
- mockManifests = {
730
- deploy: makeManifest(['deploy_run']),
731
- oncall: makeManifest(['oncall_page', 'oncall_ack']),
732
- };
733
-
734
- const resolveTools = makeResolveTools(baseToolDefs);
735
- const history: Message[] = [
736
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
737
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
738
- ];
739
-
740
- const result = resolveTools(history);
741
-
742
- expect(result).toHaveLength(5);
743
- const names = result.map((d) => d.name);
744
- expect(names).toContain('file_read');
745
- expect(names).toContain('bash');
746
- expect(names).toContain('deploy_run');
747
- expect(names).toContain('oncall_page');
748
- expect(names).toContain('oncall_ack');
749
- });
750
- });
751
-
752
- // ---------------------------------------------------------------------------
753
- // Tests — allowed tool set merging with core tools
754
- // ---------------------------------------------------------------------------
755
-
756
- describe('allowed tool set merging', () => {
757
- const CORE_TOOL_NAMES = new Set(['bash', 'file_read', 'file_write', 'file_edit']);
758
- let sessionState: Map<string, string>;
759
-
760
- beforeEach(() => {
761
- mockCatalog = [];
762
- mockManifests = {};
763
- mockRegisteredTools = new Map();
764
- mockUnregisteredSkillIds = [];
765
- mockSkillRefCount = new Map();
766
- mockSkillRefCount = new Map();
767
- mockVersionHashes = {};
768
- mockVersionHashErrors = new Set();
769
- sessionState = new Map<string, string>();
770
- });
771
-
772
- /**
773
- * Simulates the merging logic from session.ts:
774
- * union of core tool names + projected skill tool names.
775
- */
776
- function buildAllowedSet(projection: { allowedToolNames: Set<string> }): Set<string> {
777
- const merged = new Set(CORE_TOOL_NAMES);
778
- for (const name of projection.allowedToolNames) {
779
- merged.add(name);
780
- }
781
- return merged;
782
- }
783
-
784
- test('core tools are always included even with no active skills', () => {
785
- const projection = projectSkillTools([], { previouslyActiveSkillIds: sessionState });
786
- const allowed = buildAllowedSet(projection);
787
-
788
- for (const core of CORE_TOOL_NAMES) {
789
- expect(allowed.has(core)).toBe(true);
790
- }
791
- });
792
-
793
- test('active skill tools are included alongside core tools', () => {
794
- mockCatalog = [makeSkill('deploy')];
795
- mockManifests = { deploy: makeManifest(['deploy_run', 'deploy_status']) };
796
-
797
- const history: Message[] = [
798
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
799
- ];
800
-
801
- const projection = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
802
- const allowed = buildAllowedSet(projection);
803
-
804
- // Core tools present
805
- for (const core of CORE_TOOL_NAMES) {
806
- expect(allowed.has(core)).toBe(true);
807
- }
808
- // Active skill tools present
809
- expect(allowed.has('deploy_run')).toBe(true);
810
- expect(allowed.has('deploy_status')).toBe(true);
811
- });
812
-
813
- test('inactive skill tools are NOT in the allowed set', () => {
814
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
815
- mockManifests = {
816
- deploy: makeManifest(['deploy_run']),
817
- oncall: makeManifest(['oncall_page']),
818
- };
819
-
820
- // Only deploy is active
821
- const history: Message[] = [
822
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
823
- ];
824
-
825
- const projection = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
826
- const allowed = buildAllowedSet(projection);
827
-
828
- expect(allowed.has('deploy_run')).toBe(true);
829
- // oncall_page is not active — not in projection, not in allowed set
830
- expect(allowed.has('oncall_page')).toBe(false);
831
- });
832
-
833
- test('allowed set updates when skills activate and deactivate', () => {
834
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
835
- mockManifests = {
836
- deploy: makeManifest(['deploy_run']),
837
- oncall: makeManifest(['oncall_page']),
838
- };
839
-
840
- // Turn 1: both active
841
- const history1: Message[] = [
842
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
843
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
844
- ];
845
- const projection1 = projectSkillTools(history1, { previouslyActiveSkillIds: sessionState });
846
- const allowed1 = buildAllowedSet(projection1);
847
-
848
- expect(allowed1.has('deploy_run')).toBe(true);
849
- expect(allowed1.has('oncall_page')).toBe(true);
850
-
851
- // Turn 2: only deploy remains
852
- const history2: Message[] = [
853
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
854
- ];
855
- const projection2 = projectSkillTools(history2, { previouslyActiveSkillIds: sessionState });
856
- const allowed2 = buildAllowedSet(projection2);
857
-
858
- expect(allowed2.has('deploy_run')).toBe(true);
859
- expect(allowed2.has('oncall_page')).toBe(false);
860
- // Core tools still present
861
- for (const core of CORE_TOOL_NAMES) {
862
- expect(allowed2.has(core)).toBe(true);
863
- }
864
- });
865
- });
866
-
867
- // ---------------------------------------------------------------------------
868
- // End-to-end mid-run activation tests
869
- // ---------------------------------------------------------------------------
870
-
871
- // ── Security invariant (PR 34): skill_load is the permission gate ──
872
- // In strict mode, skill_load requires an explicit trust rule before the
873
- // tool executor emits a <loaded_skill> marker. Without that marker in
874
- // the conversation history, projectSkillTools will never activate the
875
- // skill's tools. The permission enforcement lives in checker.ts; the
876
- // tests here verify that tool activation only occurs when markers are
877
- // present — meaning the permission check already succeeded.
878
-
879
- describe('skill activation requires loaded_skill marker (security invariant)', () => {
880
- let sessionState: Map<string, string>;
881
-
882
- beforeEach(() => {
883
- mockCatalog = [];
884
- mockManifests = {};
885
- mockRegisteredTools = new Map();
886
- mockUnregisteredSkillIds = [];
887
- mockSkillRefCount = new Map();
888
- mockVersionHashes = {};
889
- mockVersionHashErrors = new Set();
890
- sessionState = new Map<string, string>();
891
- });
892
-
893
- test('skill_load tool_use without tool_result marker does not activate skill tools', () => {
894
- mockCatalog = [makeSkill('gated')];
895
- mockManifests = { gated: makeManifest(['gated_action']) };
896
-
897
- // History has a skill_load call but NO tool_result with a
898
- // <loaded_skill> marker — simulating a permission denial or pending
899
- // prompt in strict mode where the tool never executed.
900
- const history: Message[] = [
901
- {
902
- role: 'assistant',
903
- content: [{ type: 'tool_use', id: 'sl-gate-1', name: 'skill_load', input: { skill_id: 'gated' } }],
904
- },
905
- {
906
- role: 'user',
907
- content: [{ type: 'tool_result', tool_use_id: 'sl-gate-1', content: 'Permission denied.' }],
908
- },
909
- ];
910
-
911
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
912
- expect(result.toolDefinitions).toHaveLength(0);
913
- expect(result.allowedToolNames.size).toBe(0);
914
- });
915
-
916
- test('skill_load with valid marker activates skill tools (approved path)', () => {
917
- mockCatalog = [makeSkill('approved')];
918
- mockManifests = { approved: makeManifest(['approved_action']) };
919
-
920
- const history: Message[] = [
921
- ...skillLoadMessages('<loaded_skill id="approved" />'),
922
- ];
923
-
924
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
925
- expect(result.toolDefinitions).toHaveLength(1);
926
- expect(result.toolDefinitions[0].name).toBe('approved_action');
927
- expect(result.allowedToolNames.has('approved_action')).toBe(true);
928
- });
929
- });
930
-
931
- describe('mid-run skill tool activation (end-to-end)', () => {
932
- const baseToolDefs: ToolDefinition[] = [
933
- { name: 'file_read', description: 'Read a file', input_schema: { type: 'object', properties: {} } },
934
- { name: 'bash', description: 'Run a shell command', input_schema: { type: 'object', properties: {} } },
935
- ];
936
-
937
- const CORE_TOOL_NAMES = new Set(['bash', 'file_read', 'file_write', 'file_edit']);
938
- let sessionState: Map<string, string>;
939
-
940
- function makeResolveTools(base: ToolDefinition[]) {
941
- return (history: Message[]) => {
942
- const projection = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
943
- return {
944
- toolDefinitions: [...base, ...projection.toolDefinitions],
945
- allowedToolNames: new Set([...CORE_TOOL_NAMES, ...projection.allowedToolNames]),
946
- };
947
- };
948
- }
949
-
950
- beforeEach(() => {
951
- mockCatalog = [];
952
- mockManifests = {};
953
- mockRegisteredTools = new Map();
954
- mockUnregisteredSkillIds = [];
955
- mockSkillRefCount = new Map();
956
- mockSkillRefCount = new Map();
957
- mockVersionHashes = {};
958
- mockVersionHashErrors = new Set();
959
- sessionState = new Map<string, string>();
960
- });
961
-
962
- test('Turn 1 calls skill_load → Turn 2 sees added tool', () => {
963
- mockCatalog = [makeSkill('deploy')];
964
- mockManifests = { deploy: makeManifest(['deploy_run']) };
965
-
966
- const resolveTools = makeResolveTools(baseToolDefs);
967
-
968
- // Turn 1: no skill markers in history yet
969
- const historyTurn1: Message[] = [
970
- { role: 'user', content: [{ type: 'text', text: 'Please deploy' }] },
971
- { role: 'assistant', content: [{ type: 'text', text: 'Let me load the deploy skill.' }] },
972
- ];
973
-
974
- const turn1Result = resolveTools(historyTurn1);
975
- expect(turn1Result.toolDefinitions.map((d) => d.name)).toEqual(['file_read', 'bash']);
976
- expect(turn1Result.allowedToolNames.has('deploy_run')).toBe(false);
977
-
978
- // Simulate skill_load output appended as a tool result in the same run
979
- const historyTurn2: Message[] = [
980
- ...historyTurn1,
981
- {
982
- role: 'assistant',
983
- content: [{ type: 'tool_use', id: 'skill-load-1', name: 'skill_load', input: { skill_id: 'deploy' } }],
984
- },
985
- {
986
- role: 'user',
987
- content: [
988
- { type: 'tool_result', tool_use_id: 'skill-load-1', content: '<loaded_skill id="deploy" />' },
989
- ],
990
- },
991
- ];
992
-
993
- const turn2Result = resolveTools(historyTurn2);
994
- expect(turn2Result.toolDefinitions.map((d) => d.name)).toEqual([
995
- 'file_read',
996
- 'bash',
997
- 'deploy_run',
998
- ]);
999
- expect(turn2Result.allowedToolNames.has('deploy_run')).toBe(true);
1000
- });
1001
-
1002
- test('activation succeeds without requiring a new user message', () => {
1003
- mockCatalog = [makeSkill('monitor')];
1004
- mockManifests = { monitor: makeManifest(['monitor_check', 'monitor_alert']) };
1005
-
1006
- const resolveTools = makeResolveTools(baseToolDefs);
1007
-
1008
- // History contains only the initial user message and the assistant's
1009
- // tool_use that triggered skill_load, followed by the tool result.
1010
- // No second user message is present — the agent loop re-projects
1011
- // tools between turns within the same run.
1012
- const history: Message[] = [
1013
- { role: 'user', content: [{ type: 'text', text: 'Monitor the service' }] },
1014
- {
1015
- role: 'assistant',
1016
- content: [{ type: 'tool_use', id: 'tu-1', name: 'skill_load', input: { skill_id: 'monitor' } }],
1017
- },
1018
- {
1019
- role: 'user',
1020
- content: [
1021
- { type: 'tool_result', tool_use_id: 'tu-1', content: '<loaded_skill id="monitor" />' },
1022
- ],
1023
- },
1024
- ];
1025
-
1026
- const result = resolveTools(history);
1027
-
1028
- // Skill tools appear without needing another user message
1029
- expect(result.toolDefinitions.map((d) => d.name)).toContain('monitor_check');
1030
- expect(result.toolDefinitions.map((d) => d.name)).toContain('monitor_alert');
1031
- expect(result.allowedToolNames.has('monitor_check')).toBe(true);
1032
- expect(result.allowedToolNames.has('monitor_alert')).toBe(true);
1033
-
1034
- // Core tools remain accessible
1035
- for (const core of CORE_TOOL_NAMES) {
1036
- expect(result.allowedToolNames.has(core)).toBe(true);
1037
- }
1038
- });
1039
-
1040
- test('multiple skills can activate in sequence across turns', () => {
1041
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall'), makeSkill('metrics')];
1042
- mockManifests = {
1043
- deploy: makeManifest(['deploy_run']),
1044
- oncall: makeManifest(['oncall_page']),
1045
- metrics: makeManifest(['metrics_query', 'metrics_dashboard']),
1046
- };
1047
-
1048
- const resolveTools = makeResolveTools(baseToolDefs);
1049
-
1050
- // Step 1: Load skill A (deploy)
1051
- const historyAfterA: Message[] = [
1052
- { role: 'user', content: [{ type: 'text', text: 'I need to deploy and check oncall' }] },
1053
- {
1054
- role: 'assistant',
1055
- content: [{ type: 'tool_use', id: 'tu-1', name: 'skill_load', input: { skill_id: 'deploy' } }],
1056
- },
1057
- {
1058
- role: 'user',
1059
- content: [
1060
- { type: 'tool_result', tool_use_id: 'tu-1', content: '<loaded_skill id="deploy" />' },
1061
- ],
1062
- },
1063
- ];
1064
-
1065
- const resultA = resolveTools(historyAfterA);
1066
- const namesA = resultA.toolDefinitions.map((d) => d.name);
1067
- expect(namesA).toContain('deploy_run');
1068
- expect(namesA).not.toContain('oncall_page');
1069
- expect(namesA).not.toContain('metrics_query');
1070
-
1071
- // Step 2: Load skill B (oncall) — deploy should remain active
1072
- const historyAfterB: Message[] = [
1073
- ...historyAfterA,
1074
- {
1075
- role: 'assistant',
1076
- content: [{ type: 'tool_use', id: 'tu-2', name: 'skill_load', input: { skill_id: 'oncall' } }],
1077
- },
1078
- {
1079
- role: 'user',
1080
- content: [
1081
- { type: 'tool_result', tool_use_id: 'tu-2', content: '<loaded_skill id="oncall" />' },
1082
- ],
1083
- },
1084
- ];
1085
-
1086
- const resultB = resolveTools(historyAfterB);
1087
- const namesB = resultB.toolDefinitions.map((d) => d.name);
1088
- expect(namesB).toContain('deploy_run');
1089
- expect(namesB).toContain('oncall_page');
1090
- expect(namesB).not.toContain('metrics_query');
1091
-
1092
- // Step 3: Load skill C (metrics) — all three should be active
1093
- const historyAfterC: Message[] = [
1094
- ...historyAfterB,
1095
- {
1096
- role: 'assistant',
1097
- content: [{ type: 'tool_use', id: 'tu-3', name: 'skill_load', input: { skill_id: 'metrics' } }],
1098
- },
1099
- {
1100
- role: 'user',
1101
- content: [
1102
- { type: 'tool_result', tool_use_id: 'tu-3', content: '<loaded_skill id="metrics" />' },
1103
- ],
1104
- },
1105
- ];
1106
-
1107
- const resultC = resolveTools(historyAfterC);
1108
- const namesC = resultC.toolDefinitions.map((d) => d.name);
1109
- expect(namesC).toContain('deploy_run');
1110
- expect(namesC).toContain('oncall_page');
1111
- expect(namesC).toContain('metrics_query');
1112
- expect(namesC).toContain('metrics_dashboard');
1113
-
1114
- // Verify allowed tool names include all skill tools plus core tools
1115
- expect(resultC.allowedToolNames.has('deploy_run')).toBe(true);
1116
- expect(resultC.allowedToolNames.has('oncall_page')).toBe(true);
1117
- expect(resultC.allowedToolNames.has('metrics_query')).toBe(true);
1118
- expect(resultC.allowedToolNames.has('metrics_dashboard')).toBe(true);
1119
- for (const core of CORE_TOOL_NAMES) {
1120
- expect(resultC.allowedToolNames.has(core)).toBe(true);
1121
- }
1122
- });
1123
- });
1124
-
1125
- // Context-derived deactivation regression tests
1126
- // ---------------------------------------------------------------------------
1127
-
1128
- describe('context-derived deactivation regression', () => {
1129
- const baseToolDefs: ToolDefinition[] = [
1130
- { name: 'file_read', description: 'Read a file', input_schema: { type: 'object', properties: {} } },
1131
- { name: 'bash', description: 'Run a shell command', input_schema: { type: 'object', properties: {} } },
1132
- ];
1133
-
1134
- const CORE_TOOL_NAMES = new Set(['bash', 'file_read', 'file_write', 'file_edit']);
1135
- let sessionState: Map<string, string>;
1136
-
1137
- function makeResolveTools(base: ToolDefinition[]) {
1138
- return (history: Message[]) => {
1139
- const projection = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1140
- return {
1141
- toolDefinitions: [...base, ...projection.toolDefinitions],
1142
- allowedToolNames: new Set([...CORE_TOOL_NAMES, ...projection.allowedToolNames]),
1143
- };
1144
- };
1145
- }
1146
-
1147
- beforeEach(() => {
1148
- mockCatalog = [];
1149
- mockManifests = {};
1150
- mockRegisteredTools = new Map();
1151
- mockUnregisteredSkillIds = [];
1152
- mockSkillRefCount = new Map();
1153
- mockSkillRefCount = new Map();
1154
- mockVersionHashes = {};
1155
- mockVersionHashErrors = new Set();
1156
- sessionState = new Map<string, string>();
1157
- });
1158
-
1159
- test('tool definitions shrink when skill load marker is removed from history', () => {
1160
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
1161
- mockManifests = {
1162
- deploy: makeManifest(['deploy_run']),
1163
- oncall: makeManifest(['oncall_page', 'oncall_ack']),
1164
- };
1165
-
1166
- const resolveTools = makeResolveTools(baseToolDefs);
1167
-
1168
- // Turn 1: both skills active
1169
- const history1: Message[] = [
1170
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1171
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
1172
- ];
1173
- const result1 = resolveTools(history1);
1174
- expect(result1.toolDefinitions).toHaveLength(5); // 2 base + 3 skill tools
1175
- expect(result1.toolDefinitions.map((d) => d.name)).toContain('oncall_page');
1176
- expect(result1.toolDefinitions.map((d) => d.name)).toContain('oncall_ack');
1177
-
1178
- // Turn 2: oncall marker removed from history (truncated)
1179
- const history2: Message[] = [
1180
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1181
- ];
1182
- const result2 = resolveTools(history2);
1183
-
1184
- // Tool definitions should only have base + deploy tools
1185
- expect(result2.toolDefinitions).toHaveLength(3); // 2 base + 1 skill tool
1186
- expect(result2.toolDefinitions.map((d) => d.name)).not.toContain('oncall_page');
1187
- expect(result2.toolDefinitions.map((d) => d.name)).not.toContain('oncall_ack');
1188
- expect(result2.toolDefinitions.map((d) => d.name)).toContain('deploy_run');
1189
- });
1190
-
1191
- test('executor blocks the tool after deactivation — allowedToolNames excludes it', () => {
1192
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
1193
- mockManifests = {
1194
- deploy: makeManifest(['deploy_run']),
1195
- oncall: makeManifest(['oncall_page']),
1196
- };
1197
-
1198
- const resolveTools = makeResolveTools(baseToolDefs);
1199
-
1200
- // Turn 1: both skills active, both tools allowed
1201
- const history1: Message[] = [
1202
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1203
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
1204
- ];
1205
- const result1 = resolveTools(history1);
1206
- expect(result1.allowedToolNames.has('oncall_page')).toBe(true);
1207
- expect(result1.allowedToolNames.has('deploy_run')).toBe(true);
1208
-
1209
- // Turn 2: oncall marker gone — its tool should be blocked
1210
- const history2: Message[] = [
1211
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1212
- ];
1213
- const result2 = resolveTools(history2);
1214
-
1215
- // oncall_page is no longer in allowedToolNames — executor would block it
1216
- expect(result2.allowedToolNames.has('oncall_page')).toBe(false);
1217
- // deploy_run remains allowed
1218
- expect(result2.allowedToolNames.has('deploy_run')).toBe(true);
1219
- // Core tools remain allowed
1220
- for (const core of CORE_TOOL_NAMES) {
1221
- expect(result2.allowedToolNames.has(core)).toBe(true);
1222
- }
1223
- });
1224
-
1225
- test('unregisterSkillTools is called for deactivated skill', () => {
1226
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
1227
- mockManifests = {
1228
- deploy: makeManifest(['deploy_run']),
1229
- oncall: makeManifest(['oncall_page']),
1230
- };
1231
-
1232
- // Turn 1: both active
1233
- const history1: Message[] = [
1234
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1235
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
1236
- ];
1237
- projectSkillTools(history1, { previouslyActiveSkillIds: sessionState });
1238
-
1239
- // Clear tracking before turn 2
1240
- mockUnregisteredSkillIds = [];
1241
-
1242
- // Turn 2: deploy marker gone
1243
- const history2: Message[] = [
1244
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
1245
- ];
1246
- projectSkillTools(history2, { previouslyActiveSkillIds: sessionState });
1247
-
1248
- expect(mockUnregisteredSkillIds).toContain('deploy');
1249
- expect(mockUnregisteredSkillIds).not.toContain('oncall');
1250
- });
1251
-
1252
- test('all skills deactivate when all markers leave history', () => {
1253
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
1254
- mockManifests = {
1255
- deploy: makeManifest(['deploy_run']),
1256
- oncall: makeManifest(['oncall_page']),
1257
- };
1258
-
1259
- const resolveTools = makeResolveTools(baseToolDefs);
1260
-
1261
- // Turn 1: both skills active
1262
- const history1: Message[] = [
1263
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1264
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
1265
- ];
1266
- const result1 = resolveTools(history1);
1267
- expect(result1.toolDefinitions).toHaveLength(4); // 2 base + 2 skill
1268
-
1269
- // Clear tracking before turn 2
1270
- mockUnregisteredSkillIds = [];
1271
-
1272
- // Turn 2: all markers gone (e.g. context window fully truncated)
1273
- const history2: Message[] = [
1274
- { role: 'user', content: [{ type: 'text', text: 'Continue working' }] },
1275
- ];
1276
- const result2 = resolveTools(history2);
1277
-
1278
- // Only base tools remain
1279
- expect(result2.toolDefinitions).toHaveLength(2);
1280
- expect(result2.toolDefinitions.map((d) => d.name)).toEqual(['file_read', 'bash']);
1281
-
1282
- // Both skills were unregistered
1283
- expect(mockUnregisteredSkillIds).toContain('deploy');
1284
- expect(mockUnregisteredSkillIds).toContain('oncall');
1285
-
1286
- // No skill tools in allowed set
1287
- expect(result2.allowedToolNames.has('deploy_run')).toBe(false);
1288
- expect(result2.allowedToolNames.has('oncall_page')).toBe(false);
1289
-
1290
- // Core tools still present
1291
- for (const core of CORE_TOOL_NAMES) {
1292
- expect(result2.allowedToolNames.has(core)).toBe(true);
1293
- }
1294
- });
1295
-
1296
- test('skill can reactivate after deactivation', () => {
1297
- mockCatalog = [makeSkill('deploy')];
1298
- mockManifests = {
1299
- deploy: makeManifest(['deploy_run']),
1300
- };
1301
-
1302
- const resolveTools = makeResolveTools(baseToolDefs);
1303
-
1304
- // Turn 1: deploy active
1305
- const history1: Message[] = [
1306
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1307
- ];
1308
- const result1 = resolveTools(history1);
1309
- expect(result1.allowedToolNames.has('deploy_run')).toBe(true);
1310
-
1311
- // Turn 2: marker gone — deactivated
1312
- const history2: Message[] = [];
1313
- const result2 = resolveTools(history2);
1314
- expect(result2.allowedToolNames.has('deploy_run')).toBe(false);
1315
-
1316
- // Turn 3: marker reappears — reactivated
1317
- const history3: Message[] = [
1318
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1319
- ];
1320
- const result3 = resolveTools(history3);
1321
- expect(result3.allowedToolNames.has('deploy_run')).toBe(true);
1322
- expect(result3.toolDefinitions.map((d) => d.name)).toContain('deploy_run');
1323
- });
1324
- });
1325
-
1326
- // ---------------------------------------------------------------------------
1327
- // Slash preactivation tests
1328
- // ---------------------------------------------------------------------------
1329
-
1330
- describe('slash preactivation through session processing', () => {
1331
- let sessionState: Map<string, string>;
1332
-
1333
- beforeEach(() => {
1334
- mockCatalog = [];
1335
- mockManifests = {};
1336
- mockRegisteredTools = new Map();
1337
- mockUnregisteredSkillIds = [];
1338
- mockSkillRefCount = new Map();
1339
- mockSkillRefCount = new Map();
1340
- mockVersionHashes = {};
1341
- mockVersionHashErrors = new Set();
1342
- sessionState = new Map<string, string>();
1343
- });
1344
-
1345
- test('slash-known skill has its tools available on first projection (turn-0)', () => {
1346
- mockCatalog = [makeSkill('deploy')];
1347
- mockManifests = { deploy: makeManifest(['deploy_run', 'deploy_status']) };
1348
-
1349
- // Empty history — no loaded_skill markers yet. The skill is preactivated
1350
- // via slash resolution, so its tools should be available immediately.
1351
- const emptyHistory: Message[] = [];
1352
-
1353
- const result = projectSkillTools(emptyHistory, {
1354
- preactivatedSkillIds: ['deploy'],
1355
- previouslyActiveSkillIds: sessionState,
1356
- });
1357
-
1358
- expect(result.toolDefinitions).toHaveLength(2);
1359
- expect(result.toolDefinitions.map((d) => d.name)).toEqual([
1360
- 'deploy_run',
1361
- 'deploy_status',
1362
- ]);
1363
- expect(result.allowedToolNames).toEqual(
1364
- new Set(['deploy_run', 'deploy_status']),
1365
- );
1366
- });
1367
-
1368
- test('preactivation is request-scoped — does not persist to unrelated runs', () => {
1369
- mockCatalog = [makeSkill('deploy')];
1370
- mockManifests = { deploy: makeManifest(['deploy_run']) };
1371
-
1372
- // First request: preactivated via slash command
1373
- const result1 = projectSkillTools([], {
1374
- preactivatedSkillIds: ['deploy'],
1375
- previouslyActiveSkillIds: sessionState,
1376
- });
1377
- expect(result1.toolDefinitions).toHaveLength(1);
1378
- expect(result1.allowedToolNames.has('deploy_run')).toBe(true);
1379
-
1380
- // Second request: no preactivation, no history markers.
1381
- // Without preactivated IDs, the skill should not appear.
1382
- const result2 = projectSkillTools([], { previouslyActiveSkillIds: sessionState });
1383
-
1384
- expect(result2.toolDefinitions).toHaveLength(0);
1385
- expect(result2.allowedToolNames.has('deploy_run')).toBe(false);
1386
- });
1387
-
1388
- test('preactivated skill tools merge with history-derived skills on turn-0', () => {
1389
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
1390
- mockManifests = {
1391
- deploy: makeManifest(['deploy_run']),
1392
- oncall: makeManifest(['oncall_page']),
1393
- };
1394
-
1395
- // History has an oncall marker from a previous exchange
1396
- const history: Message[] = [
1397
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
1398
- ];
1399
-
1400
- // deploy is preactivated via slash, oncall is from history
1401
- const result = projectSkillTools(history, {
1402
- preactivatedSkillIds: ['deploy'],
1403
- previouslyActiveSkillIds: sessionState,
1404
- });
1405
-
1406
- expect(result.toolDefinitions).toHaveLength(2);
1407
- expect(result.allowedToolNames).toEqual(
1408
- new Set(['deploy_run', 'oncall_page']),
1409
- );
1410
- });
1411
- });
1412
-
1413
- // ---------------------------------------------------------------------------
1414
- // Bundled skill pipeline integration tests
1415
- // ---------------------------------------------------------------------------
1416
-
1417
- const GMAIL_TOOL_NAMES = [
1418
- 'gmail_search',
1419
- 'gmail_list_messages',
1420
- 'gmail_get_message',
1421
- 'gmail_mark_read',
1422
- 'gmail_draft',
1423
- 'gmail_archive',
1424
- 'gmail_batch_archive',
1425
- 'gmail_label',
1426
- 'gmail_batch_label',
1427
- 'gmail_trash',
1428
- 'gmail_send',
1429
- 'gmail_unsubscribe',
1430
- ] as const;
1431
-
1432
- describe('bundled skill: gmail', () => {
1433
- let sessionState: Map<string, string>;
1434
-
1435
- beforeEach(() => {
1436
- mockCatalog = [];
1437
- mockManifests = {};
1438
- mockRegisteredTools = new Map();
1439
- mockUnregisteredSkillIds = [];
1440
- mockSkillRefCount = new Map();
1441
- mockSkillRefCount = new Map();
1442
- mockVersionHashes = {};
1443
- mockVersionHashErrors = new Set();
1444
- sessionState = new Map<string, string>();
1445
- });
1446
-
1447
- test('gmail skill activation via loaded_skill marker projects all 12 tool definitions', () => {
1448
- mockCatalog = [makeSkill('gmail', '/path/to/bundled-skills/gmail')];
1449
- mockManifests = { gmail: makeManifest([...GMAIL_TOOL_NAMES]) };
1450
-
1451
- const history: Message[] = [
1452
- ...skillLoadMessages('<loaded_skill id="gmail" />'),
1453
- ];
1454
-
1455
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1456
-
1457
- expect(result.toolDefinitions).toHaveLength(12);
1458
- expect(result.toolDefinitions.map((d) => d.name)).toEqual([...GMAIL_TOOL_NAMES]);
1459
- expect(result.allowedToolNames).toEqual(new Set(GMAIL_TOOL_NAMES));
1460
- });
1461
-
1462
- test('gmail tools are NOT available when gmail skill is not in active context', () => {
1463
- mockCatalog = [makeSkill('gmail', '/path/to/bundled-skills/gmail')];
1464
- mockManifests = { gmail: makeManifest([...GMAIL_TOOL_NAMES]) };
1465
-
1466
- // No loaded_skill marker for gmail in history
1467
- const history: Message[] = [
1468
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
1469
- ];
1470
-
1471
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1472
-
1473
- expect(result.toolDefinitions).toHaveLength(0);
1474
- expect(result.allowedToolNames.size).toBe(0);
1475
- for (const name of GMAIL_TOOL_NAMES) {
1476
- expect(result.allowedToolNames.has(name)).toBe(false);
1477
- }
1478
- });
1479
- });
1480
-
1481
- describe('bundled skill: claude-code', () => {
1482
- let sessionState: Map<string, string>;
1483
-
1484
- beforeEach(() => {
1485
- mockCatalog = [];
1486
- mockManifests = {};
1487
- mockRegisteredTools = new Map();
1488
- mockUnregisteredSkillIds = [];
1489
- mockSkillRefCount = new Map();
1490
- mockSkillRefCount = new Map();
1491
- mockVersionHashes = {};
1492
- mockVersionHashErrors = new Set();
1493
- sessionState = new Map<string, string>();
1494
- });
1495
-
1496
- test('claude-code skill activation produces claude_code tool definition', () => {
1497
- mockCatalog = [makeSkill('claude-code', '/path/to/bundled-skills/claude-code')];
1498
- mockManifests = { 'claude-code': makeManifest(['claude_code']) };
1499
-
1500
- const history: Message[] = [
1501
- ...skillLoadMessages('<loaded_skill id="claude-code" />'),
1502
- ];
1503
-
1504
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1505
-
1506
- expect(result.toolDefinitions).toHaveLength(1);
1507
- expect(result.toolDefinitions[0].name).toBe('claude_code');
1508
- expect(result.allowedToolNames).toEqual(new Set(['claude_code']));
1509
- });
1510
-
1511
- test('claude_code tool is absent when claude-code skill is not active', () => {
1512
- mockCatalog = [makeSkill('claude-code', '/path/to/bundled-skills/claude-code')];
1513
- mockManifests = { 'claude-code': makeManifest(['claude_code']) };
1514
-
1515
- const history: Message[] = [
1516
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
1517
- ];
1518
-
1519
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1520
-
1521
- expect(result.toolDefinitions).toHaveLength(0);
1522
- expect(result.allowedToolNames.has('claude_code')).toBe(false);
1523
- });
1524
- });
1525
-
1526
- describe('bundled skill: weather', () => {
1527
- let sessionState: Map<string, string>;
1528
-
1529
- beforeEach(() => {
1530
- mockCatalog = [];
1531
- mockManifests = {};
1532
- mockRegisteredTools = new Map();
1533
- mockUnregisteredSkillIds = [];
1534
- mockSkillRefCount = new Map();
1535
- mockSkillRefCount = new Map();
1536
- mockVersionHashes = {};
1537
- mockVersionHashErrors = new Set();
1538
- sessionState = new Map<string, string>();
1539
- });
1540
-
1541
- test('weather skill activation produces get_weather tool definition', () => {
1542
- mockCatalog = [makeSkill('weather', '/path/to/bundled-skills/weather')];
1543
- mockManifests = { weather: makeManifest(['get_weather']) };
1544
-
1545
- const history: Message[] = [
1546
- ...skillLoadMessages('<loaded_skill id="weather" />'),
1547
- ];
1548
-
1549
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1550
-
1551
- expect(result.toolDefinitions).toHaveLength(1);
1552
- expect(result.toolDefinitions[0].name).toBe('get_weather');
1553
- expect(result.allowedToolNames).toEqual(new Set(['get_weather']));
1554
- });
1555
-
1556
- test('get_weather tool is absent when weather skill is not active', () => {
1557
- mockCatalog = [makeSkill('weather', '/path/to/bundled-skills/weather')];
1558
- mockManifests = { weather: makeManifest(['get_weather']) };
1559
-
1560
- const history: Message[] = [
1561
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
1562
- ];
1563
-
1564
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1565
-
1566
- expect(result.toolDefinitions).toHaveLength(0);
1567
- expect(result.allowedToolNames.has('get_weather')).toBe(false);
1568
- });
1569
- });
1570
-
1571
- // ---------------------------------------------------------------------------
1572
- // Bundled skill: app-builder
1573
- // ---------------------------------------------------------------------------
1574
-
1575
- const APP_BUILDER_TOOL_NAMES = [
1576
- 'app_create',
1577
- 'app_list',
1578
- 'app_query',
1579
- 'app_update',
1580
- 'app_delete',
1581
- 'app_file_list',
1582
- 'app_file_read',
1583
- 'app_file_edit',
1584
- 'app_file_write',
1585
- ] as const;
1586
-
1587
- describe('bundled skill: app-builder', () => {
1588
- let sessionState: Map<string, string>;
1589
-
1590
- beforeEach(() => {
1591
- mockCatalog = [];
1592
- mockManifests = {};
1593
- mockRegisteredTools = new Map();
1594
- mockUnregisteredSkillIds = [];
1595
- mockSkillRefCount = new Map();
1596
- mockVersionHashes = {};
1597
- mockVersionHashErrors = new Set();
1598
- sessionState = new Map<string, string>();
1599
- });
1600
-
1601
- test('app-builder skill activation projects all 9 canonical non-proxy tool definitions', () => {
1602
- mockCatalog = [makeSkill('app-builder', '/path/to/bundled-skills/app-builder')];
1603
- mockManifests = { 'app-builder': makeManifest([...APP_BUILDER_TOOL_NAMES]) };
1604
-
1605
- const history: Message[] = [
1606
- ...skillLoadMessages('<loaded_skill id="app-builder" />'),
1607
- ];
1608
-
1609
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1610
-
1611
- expect(result.toolDefinitions).toHaveLength(9);
1612
- expect(result.toolDefinitions.map((d) => d.name)).toEqual([...APP_BUILDER_TOOL_NAMES]);
1613
- expect(result.allowedToolNames).toEqual(new Set(APP_BUILDER_TOOL_NAMES));
1614
- });
1615
-
1616
- test('app-builder tools are NOT available when skill is not in active context', () => {
1617
- mockCatalog = [makeSkill('app-builder', '/path/to/bundled-skills/app-builder')];
1618
- mockManifests = { 'app-builder': makeManifest([...APP_BUILDER_TOOL_NAMES]) };
1619
-
1620
- const history: Message[] = [
1621
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
1622
- ];
1623
-
1624
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1625
-
1626
- expect(result.toolDefinitions).toHaveLength(0);
1627
- expect(result.allowedToolNames.size).toBe(0);
1628
- for (const name of APP_BUILDER_TOOL_NAMES) {
1629
- expect(result.allowedToolNames.has(name)).toBe(false);
1630
- }
1631
- });
1632
-
1633
- test('skill-projected app tools use host execution (script runners)', () => {
1634
- mockCatalog = [makeSkill('app-builder', '/path/to/bundled-skills/app-builder')];
1635
- mockManifests = { 'app-builder': makeManifest([...APP_BUILDER_TOOL_NAMES]) };
1636
-
1637
- const history: Message[] = [
1638
- ...skillLoadMessages('<loaded_skill id="app-builder" />'),
1639
- ];
1640
-
1641
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1642
-
1643
- const tools = mockRegisteredTools.get('app-builder');
1644
- expect(tools).toBeDefined();
1645
- expect(tools!.length).toBe(9);
1646
-
1647
- // All tools should have skill origin metadata
1648
- for (const tool of tools!) {
1649
- expect(tool.origin).toBe('skill');
1650
- expect(tool.ownerSkillId).toBe('app-builder');
1651
- }
1652
- });
1653
- });
1654
-
1655
- // ---------------------------------------------------------------------------
1656
- // Bundled skill: browser
1657
- // ---------------------------------------------------------------------------
1658
-
1659
- describe('bundled skill: browser', () => {
1660
- let sessionState: Map<string, string>;
1661
-
1662
- beforeEach(() => {
1663
- mockCatalog = [];
1664
- mockManifests = {};
1665
- mockRegisteredTools = new Map();
1666
- mockUnregisteredSkillIds = [];
1667
- mockSkillRefCount = new Map();
1668
- mockVersionHashes = {};
1669
- mockVersionHashErrors = new Set();
1670
- sessionState = new Map<string, string>();
1671
- });
1672
-
1673
- test('browser skill activation via loaded_skill marker projects all 10 tool definitions', () => {
1674
- mockCatalog = [makeSkill('browser', '/path/to/bundled-skills/browser')];
1675
- mockManifests = { browser: makeManifest([...BROWSER_TOOL_NAMES]) };
1676
-
1677
- const history: Message[] = [
1678
- ...buildSkillLoadHistory('browser', 'v1:testhash'),
1679
- ];
1680
-
1681
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1682
-
1683
- expect(result.toolDefinitions).toHaveLength(10);
1684
- expect(result.toolDefinitions.map((d) => d.name)).toEqual([...BROWSER_TOOL_NAMES]);
1685
- expect(result.allowedToolNames).toEqual(new Set(BROWSER_TOOL_NAMES));
1686
- });
1687
-
1688
- test('browser tools are NOT available when browser skill is not in active context', () => {
1689
- mockCatalog = [makeSkill('browser', '/path/to/bundled-skills/browser')];
1690
- mockManifests = { browser: makeManifest([...BROWSER_TOOL_NAMES]) };
1691
-
1692
- const history: Message[] = [
1693
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
1694
- ];
1695
-
1696
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1697
-
1698
- expect(result.toolDefinitions).toHaveLength(0);
1699
- expect(result.allowedToolNames.size).toBe(0);
1700
- for (const name of BROWSER_TOOL_NAMES) {
1701
- expect(result.allowedToolNames.has(name)).toBe(false);
1702
- }
1703
- });
1704
-
1705
- test('browser skill tools have skill origin metadata', () => {
1706
- mockCatalog = [makeSkill('browser', '/path/to/bundled-skills/browser')];
1707
- mockManifests = { browser: makeManifest([...BROWSER_TOOL_NAMES]) };
1708
-
1709
- const history: Message[] = [
1710
- ...buildSkillLoadHistory('browser', 'v1:testhash'),
1711
- ];
1712
-
1713
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1714
-
1715
- const tools = mockRegisteredTools.get('browser');
1716
- expect(tools).toBeDefined();
1717
- expect(tools!.length).toBe(10);
1718
-
1719
- for (const tool of tools!) {
1720
- expect(tool.origin).toBe('skill');
1721
- expect(tool.ownerSkillId).toBe('browser');
1722
- }
1723
- });
1724
- });
1725
-
1726
- // ---------------------------------------------------------------------------
1727
- // Tamper detection regression tests
1728
- // ---------------------------------------------------------------------------
1729
-
1730
- describe('tamper detection', () => {
1731
- let sessionState: Map<string, string>;
1732
-
1733
- beforeEach(() => {
1734
- mockCatalog = [];
1735
- mockManifests = {};
1736
- mockRegisteredTools = new Map();
1737
- mockUnregisteredSkillIds = [];
1738
- mockSkillRefCount = new Map();
1739
- mockVersionHashes = {};
1740
- mockVersionHashErrors = new Set();
1741
- sessionState = new Map<string, string>();
1742
- });
1743
-
1744
- test('file mutation after projection invalidates the stored hash, causing re-registration on next turn', () => {
1745
- mockCatalog = [makeSkill('deploy')];
1746
- mockManifests = { deploy: makeManifest(['deploy_run']) };
1747
- mockVersionHashes = { deploy: 'v1:original-file-hash' };
1748
-
1749
- const history: Message[] = [
1750
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1751
- ];
1752
-
1753
- // Turn 1: project with original hash
1754
- const result1 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1755
- expect(result1.toolDefinitions).toHaveLength(1);
1756
- expect(result1.toolDefinitions[0].name).toBe('deploy_run');
1757
- expect(sessionState.get('deploy')).toBe('v1:original-file-hash');
1758
-
1759
- // Simulate file mutation on disk — the hash changes
1760
- mockVersionHashes = { deploy: 'v1:tampered-file-hash' };
1761
-
1762
- // Turn 2: re-project detects hash drift and re-registers
1763
- mockUnregisteredSkillIds = [];
1764
- const result2 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1765
-
1766
- // Tools are still available (re-registered with new hash)
1767
- expect(result2.toolDefinitions).toHaveLength(1);
1768
- expect(result2.toolDefinitions[0].name).toBe('deploy_run');
1769
-
1770
- // Old tools were unregistered before new ones registered
1771
- expect(mockUnregisteredSkillIds).toContain('deploy');
1772
-
1773
- // Session state now tracks the new hash
1774
- expect(sessionState.get('deploy')).toBe('v1:tampered-file-hash');
1775
-
1776
- // Refcount stays at 1 (unregister decremented, re-register incremented)
1777
- expect(mockSkillRefCount.get('deploy')).toBe(1);
1778
- });
1779
-
1780
- test('unmodified skill file does NOT trigger re-registration across multiple turns', () => {
1781
- mockCatalog = [makeSkill('deploy')];
1782
- mockManifests = { deploy: makeManifest(['deploy_run']) };
1783
- mockVersionHashes = { deploy: 'v1:stable-content-hash' };
1784
-
1785
- const history: Message[] = [
1786
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1787
- ];
1788
-
1789
- // Turn 1: initial projection
1790
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1791
- expect(mockSkillRefCount.get('deploy')).toBe(1);
1792
-
1793
- // Turns 2-4: hash stays the same, no re-registration should occur
1794
- for (let turn = 2; turn <= 4; turn++) {
1795
- mockUnregisteredSkillIds = [];
1796
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1797
- expect(result.toolDefinitions).toHaveLength(1);
1798
- expect(mockUnregisteredSkillIds).not.toContain('deploy');
1799
- expect(mockSkillRefCount.get('deploy')).toBe(1);
1800
- }
1801
- });
1802
-
1803
- test('re-projection after tamper produces tools with the updated hash', () => {
1804
- mockCatalog = [makeSkill('deploy')];
1805
- mockManifests = { deploy: makeManifest(['deploy_run', 'deploy_status']) };
1806
- mockVersionHashes = { deploy: 'v1:hash-before-edit' };
1807
-
1808
- const history: Message[] = [
1809
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1810
- ];
1811
-
1812
- // Turn 1: initial projection
1813
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1814
- expect(sessionState.get('deploy')).toBe('v1:hash-before-edit');
1815
-
1816
- // Simulate tamper: file changes on disk
1817
- mockVersionHashes = { deploy: 'v1:hash-after-edit' };
1818
-
1819
- // Turn 2: re-projection picks up the new hash
1820
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1821
-
1822
- expect(result.toolDefinitions).toHaveLength(2);
1823
- expect(result.allowedToolNames).toEqual(new Set(['deploy_run', 'deploy_status']));
1824
- expect(sessionState.get('deploy')).toBe('v1:hash-after-edit');
1825
- });
1826
-
1827
- test('multiple skills with only one tampered triggers selective re-registration', () => {
1828
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
1829
- mockManifests = {
1830
- deploy: makeManifest(['deploy_run']),
1831
- oncall: makeManifest(['oncall_page']),
1832
- };
1833
- mockVersionHashes = {
1834
- deploy: 'v1:deploy-hash-v1',
1835
- oncall: 'v1:oncall-hash-v1',
1836
- };
1837
-
1838
- const history: Message[] = [
1839
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1840
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
1841
- ];
1842
-
1843
- // Turn 1: both skills registered
1844
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1845
- expect(sessionState.get('deploy')).toBe('v1:deploy-hash-v1');
1846
- expect(sessionState.get('oncall')).toBe('v1:oncall-hash-v1');
1847
-
1848
- // Tamper only deploy
1849
- mockVersionHashes = {
1850
- deploy: 'v1:deploy-hash-v2-tampered',
1851
- oncall: 'v1:oncall-hash-v1', // unchanged
1852
- };
1853
- mockUnregisteredSkillIds = [];
1854
-
1855
- // Turn 2: only deploy should be re-registered
1856
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1857
-
1858
- expect(result.toolDefinitions).toHaveLength(2);
1859
- expect(result.allowedToolNames).toEqual(new Set(['deploy_run', 'oncall_page']));
1860
-
1861
- // Only deploy was unregistered (for re-registration), oncall was untouched
1862
- expect(mockUnregisteredSkillIds).toContain('deploy');
1863
- expect(mockUnregisteredSkillIds).not.toContain('oncall');
1864
-
1865
- // Hashes updated accordingly
1866
- expect(sessionState.get('deploy')).toBe('v1:deploy-hash-v2-tampered');
1867
- expect(sessionState.get('oncall')).toBe('v1:oncall-hash-v1');
1868
- });
1869
-
1870
- test('hash failure (e.g., unreadable directory) causes fallback re-registration', () => {
1871
- mockCatalog = [makeSkill('deploy')];
1872
- mockManifests = { deploy: makeManifest(['deploy_run']) };
1873
- mockVersionHashes = { deploy: 'v1:initial-hash' };
1874
-
1875
- const history: Message[] = [
1876
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1877
- ];
1878
-
1879
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1880
- expect(sessionState.get('deploy')).toBe('v1:initial-hash');
1881
-
1882
- // Make computeSkillVersionHash throw to exercise the catch branch
1883
- // in session-skill-tools.ts that falls back to `unknown-${Date.now()}`
1884
- mockVersionHashErrors.add('deploy');
1885
- mockUnregisteredSkillIds = [];
1886
-
1887
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1888
- expect(result.toolDefinitions).toHaveLength(1);
1889
-
1890
- // The exception triggers re-registration since the fallback hash
1891
- // (`unknown-<timestamp>`) will never match the stored hash
1892
- expect(mockUnregisteredSkillIds).toContain('deploy');
1893
- expect(sessionState.get('deploy')).toMatch(/^unknown-\d+$/);
1894
- });
1895
- });
1896
-
1897
- // ---------------------------------------------------------------------------
1898
- // resetSkillToolProjection tests
1899
- // ---------------------------------------------------------------------------
1900
-
1901
- describe('resetSkillToolProjection', () => {
1902
- beforeEach(() => {
1903
- mockCatalog = [];
1904
- mockManifests = {};
1905
- mockRegisteredTools = new Map();
1906
- mockUnregisteredSkillIds = [];
1907
- mockSkillRefCount = new Map();
1908
- mockVersionHashes = {};
1909
- mockVersionHashErrors = new Set();
1910
- });
1911
-
1912
- test('unregisters all tracked skills and clears the map', () => {
1913
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
1914
- mockManifests = {
1915
- deploy: makeManifest(['deploy_run']),
1916
- oncall: makeManifest(['oncall_page']),
1917
- };
1918
-
1919
- const trackedIds = new Map<string, string>();
1920
-
1921
- // Activate both skills
1922
- const history: Message[] = [
1923
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1924
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
1925
- ];
1926
- projectSkillTools(history, { previouslyActiveSkillIds: trackedIds });
1927
- expect(trackedIds.size).toBe(2);
1928
-
1929
- mockUnregisteredSkillIds = [];
1930
- resetSkillToolProjection(trackedIds);
1931
-
1932
- expect(mockUnregisteredSkillIds).toContain('deploy');
1933
- expect(mockUnregisteredSkillIds).toContain('oncall');
1934
- expect(trackedIds.size).toBe(0);
1935
- });
1936
-
1937
- test('no-op when called with undefined', () => {
1938
- mockUnregisteredSkillIds = [];
1939
- resetSkillToolProjection(undefined);
1940
- expect(mockUnregisteredSkillIds).toHaveLength(0);
1941
- });
1942
-
1943
- test('no-op when called with empty map', () => {
1944
- mockUnregisteredSkillIds = [];
1945
- resetSkillToolProjection(new Map());
1946
- expect(mockUnregisteredSkillIds).toHaveLength(0);
1947
- });
1948
- });
1949
-
1950
- // ---------------------------------------------------------------------------
1951
- // Versioned marker integration tests
1952
- // ---------------------------------------------------------------------------
1953
-
1954
- describe('versioned markers through session projection', () => {
1955
- let sessionState: Map<string, string>;
1956
-
1957
- beforeEach(() => {
1958
- mockCatalog = [];
1959
- mockManifests = {};
1960
- mockRegisteredTools = new Map();
1961
- mockUnregisteredSkillIds = [];
1962
- mockSkillRefCount = new Map();
1963
- mockVersionHashes = {};
1964
- mockVersionHashErrors = new Set();
1965
- sessionState = new Map<string, string>();
1966
- });
1967
-
1968
- test('versioned marker activates skill tools the same as legacy marker', () => {
1969
- mockCatalog = [makeSkill('deploy')];
1970
- mockManifests = { deploy: makeManifest(['deploy_run']) };
1971
-
1972
- const history: Message[] = [
1973
- ...skillLoadMessages('<loaded_skill id="deploy" version="v1:abc123" />'),
1974
- ];
1975
-
1976
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1977
-
1978
- expect(result.toolDefinitions).toHaveLength(1);
1979
- expect(result.toolDefinitions[0].name).toBe('deploy_run');
1980
- expect(result.allowedToolNames).toEqual(new Set(['deploy_run']));
1981
- });
1982
-
1983
- test('mixed legacy and versioned markers both project tools', () => {
1984
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
1985
- mockManifests = {
1986
- deploy: makeManifest(['deploy_run']),
1987
- oncall: makeManifest(['oncall_page']),
1988
- };
1989
-
1990
- const history: Message[] = [
1991
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
1992
- ...skillLoadMessages('<loaded_skill id="oncall" version="v1:deadbeef" />'),
1993
- ];
1994
-
1995
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1996
-
1997
- expect(result.toolDefinitions).toHaveLength(2);
1998
- expect(result.allowedToolNames).toEqual(new Set(['deploy_run', 'oncall_page']));
1999
- });
2000
-
2001
- test('versioned marker skill deactivates when removed from history', () => {
2002
- mockCatalog = [makeSkill('deploy')];
2003
- mockManifests = { deploy: makeManifest(['deploy_run']) };
2004
-
2005
- // Turn 1: versioned skill active
2006
- const history1: Message[] = [
2007
- ...skillLoadMessages('<loaded_skill id="deploy" version="v1:abc123" />'),
2008
- ];
2009
- projectSkillTools(history1, { previouslyActiveSkillIds: sessionState });
2010
- expect(sessionState.has('deploy')).toBe(true);
2011
-
2012
- // Turn 2: marker removed
2013
- mockUnregisteredSkillIds = [];
2014
- const result2 = projectSkillTools([], { previouslyActiveSkillIds: sessionState });
2015
- expect(result2.toolDefinitions).toEqual([]);
2016
- expect(mockUnregisteredSkillIds).toContain('deploy');
2017
- });
2018
- });
2019
-
2020
- // ---------------------------------------------------------------------------
2021
- // Hash change re-prompt regression tests (PR 35)
2022
- // Verify that version hash changes trigger re-registration and that the
2023
- // session state accurately tracks the new hash, which downstream components
2024
- // use to decide whether cached approvals still apply.
2025
- // ---------------------------------------------------------------------------
2026
-
2027
- describe('hash change re-prompt regressions (PR 35)', () => {
2028
- let sessionState: Map<string, string>;
2029
-
2030
- beforeEach(() => {
2031
- mockCatalog = [];
2032
- mockManifests = {};
2033
- mockRegisteredTools = new Map();
2034
- mockUnregisteredSkillIds = [];
2035
- mockSkillRefCount = new Map();
2036
- mockVersionHashes = {};
2037
- mockVersionHashErrors = new Set();
2038
- sessionState = new Map<string, string>();
2039
- });
2040
-
2041
- test('approve v1, edit skill (hash changes), v2 triggers re-registration with new hash', () => {
2042
- mockCatalog = [makeSkill('deploy')];
2043
- mockManifests = { deploy: makeManifest(['deploy_run']) };
2044
- mockVersionHashes = { deploy: 'v1:approved-hash' };
2045
-
2046
- const history: Message[] = [
2047
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
2048
- ];
2049
-
2050
- // Turn 1: skill approved and registered with v1 hash
2051
- const result1 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2052
- expect(result1.toolDefinitions).toHaveLength(1);
2053
- expect(sessionState.get('deploy')).toBe('v1:approved-hash');
2054
- expect(mockSkillRefCount.get('deploy')).toBe(1);
2055
-
2056
- // Simulate skill edit — hash changes on disk
2057
- mockVersionHashes = { deploy: 'v2:edited-hash' };
2058
- mockUnregisteredSkillIds = [];
2059
-
2060
- // Turn 2: projection detects hash drift, unregisters old, re-registers new
2061
- const result2 = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2062
-
2063
- expect(result2.toolDefinitions).toHaveLength(1);
2064
- expect(result2.toolDefinitions[0].name).toBe('deploy_run');
2065
-
2066
- // Old version was unregistered
2067
- expect(mockUnregisteredSkillIds).toContain('deploy');
2068
-
2069
- // Session state updated to the new hash
2070
- expect(sessionState.get('deploy')).toBe('v2:edited-hash');
2071
-
2072
- // Ref count balanced (unregister decremented, re-register incremented)
2073
- expect(mockSkillRefCount.get('deploy')).toBe(1);
2074
- });
2075
-
2076
- test('two consecutive edits each trigger re-registration with correct hash', () => {
2077
- mockCatalog = [makeSkill('deploy')];
2078
- mockManifests = { deploy: makeManifest(['deploy_run']) };
2079
- mockVersionHashes = { deploy: 'v1:first-version' };
2080
-
2081
- const history: Message[] = [
2082
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
2083
- ];
2084
-
2085
- // Turn 1: initial registration
2086
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2087
- expect(sessionState.get('deploy')).toBe('v1:first-version');
2088
-
2089
- // Edit 1: hash changes to v2
2090
- mockVersionHashes = { deploy: 'v2:second-version' };
2091
- mockUnregisteredSkillIds = [];
2092
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2093
- expect(sessionState.get('deploy')).toBe('v2:second-version');
2094
- expect(mockUnregisteredSkillIds).toContain('deploy');
2095
-
2096
- // Edit 2: hash changes to v3
2097
- mockVersionHashes = { deploy: 'v3:third-version' };
2098
- mockUnregisteredSkillIds = [];
2099
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2100
- expect(sessionState.get('deploy')).toBe('v3:third-version');
2101
- expect(mockUnregisteredSkillIds).toContain('deploy');
2102
-
2103
- // Ref count stays at 1 through all edits
2104
- expect(mockSkillRefCount.get('deploy')).toBe(1);
2105
- });
2106
-
2107
- test('hash change in one skill does not affect co-active skill with stable hash', () => {
2108
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
2109
- mockManifests = {
2110
- deploy: makeManifest(['deploy_run']),
2111
- oncall: makeManifest(['oncall_page']),
2112
- };
2113
- mockVersionHashes = {
2114
- deploy: 'v1:deploy-stable',
2115
- oncall: 'v1:oncall-original',
2116
- };
2117
-
2118
- const history: Message[] = [
2119
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
2120
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
2121
- ];
2122
-
2123
- // Turn 1: both skills registered
2124
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2125
- expect(sessionState.get('deploy')).toBe('v1:deploy-stable');
2126
- expect(sessionState.get('oncall')).toBe('v1:oncall-original');
2127
-
2128
- // Edit only oncall
2129
- mockVersionHashes = {
2130
- deploy: 'v1:deploy-stable', // unchanged
2131
- oncall: 'v2:oncall-edited',
2132
- };
2133
- mockUnregisteredSkillIds = [];
2134
-
2135
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2136
-
2137
- // Only oncall was re-registered
2138
- expect(mockUnregisteredSkillIds).toContain('oncall');
2139
- expect(mockUnregisteredSkillIds).not.toContain('deploy');
2140
-
2141
- // Hashes updated correctly
2142
- expect(sessionState.get('deploy')).toBe('v1:deploy-stable');
2143
- expect(sessionState.get('oncall')).toBe('v2:oncall-edited');
2144
- });
2145
-
2146
- test('registered tools carry updated ownerSkillId after hash change re-registration', () => {
2147
- mockCatalog = [makeSkill('deploy')];
2148
- mockManifests = { deploy: makeManifest(['deploy_run']) };
2149
- mockVersionHashes = { deploy: 'v1:pre-edit' };
2150
-
2151
- const history: Message[] = [
2152
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
2153
- ];
2154
-
2155
- // Turn 1
2156
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2157
- const toolsV1 = mockRegisteredTools.get('deploy');
2158
- expect(toolsV1).toBeDefined();
2159
- expect(toolsV1!.length).toBe(1);
2160
- expect(toolsV1![0].ownerSkillId).toBe('deploy');
2161
-
2162
- // Edit
2163
- mockVersionHashes = { deploy: 'v2:post-edit' };
2164
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2165
-
2166
- // After re-registration, tools should still be associated with the skill
2167
- const toolsV2 = mockRegisteredTools.get('deploy');
2168
- expect(toolsV2).toBeDefined();
2169
- expect(toolsV2!.length).toBeGreaterThanOrEqual(1);
2170
- expect(toolsV2![0].ownerSkillId).toBe('deploy');
2171
- });
2172
- });
2173
-
2174
- // ---------------------------------------------------------------------------
2175
- // Version hash plumbing regression tests
2176
- // Verify that createSkillToolsFromManifest receives the computed hash and
2177
- // that projected tools carry ownerSkillVersionHash, which downstream
2178
- // components (executor.ts) use to build version-bound policy principals.
2179
- // ---------------------------------------------------------------------------
2180
-
2181
- describe('version hash plumbing to projected tools', () => {
2182
- let sessionState: Map<string, string>;
2183
-
2184
- beforeEach(() => {
2185
- mockCatalog = [];
2186
- mockManifests = {};
2187
- mockRegisteredTools = new Map();
2188
- mockUnregisteredSkillIds = [];
2189
- mockSkillRefCount = new Map();
2190
- mockVersionHashes = {};
2191
- mockVersionHashErrors = new Set();
2192
- sessionState = new Map<string, string>();
2193
- });
2194
-
2195
- test('projected tools carry ownerSkillVersionHash matching the computed hash', () => {
2196
- mockCatalog = [makeSkill('deploy')];
2197
- mockManifests = { deploy: makeManifest(['deploy_run', 'deploy_status']) };
2198
- mockVersionHashes = { deploy: 'v1:secure-hash-abc' };
2199
-
2200
- const history: Message[] = [
2201
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
2202
- ];
2203
-
2204
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2205
-
2206
- const tools = mockRegisteredTools.get('deploy');
2207
- expect(tools).toBeDefined();
2208
- expect(tools!.length).toBe(2);
2209
-
2210
- // Every tool created for this skill must carry the version hash
2211
- for (const tool of tools!) {
2212
- expect(tool.ownerSkillVersionHash).toBe('v1:secure-hash-abc');
2213
- }
2214
- });
2215
-
2216
- test('after hash change re-registration, new tools carry the updated hash', () => {
2217
- mockCatalog = [makeSkill('deploy')];
2218
- mockManifests = { deploy: makeManifest(['deploy_run']) };
2219
- mockVersionHashes = { deploy: 'v1:hash-before' };
2220
-
2221
- const history: Message[] = [
2222
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
2223
- ];
2224
-
2225
- // Turn 1: register with original hash
2226
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2227
- const toolsV1 = mockRegisteredTools.get('deploy');
2228
- expect(toolsV1).toBeDefined();
2229
- expect(toolsV1![0].ownerSkillVersionHash).toBe('v1:hash-before');
2230
-
2231
- // Simulate file edit — hash changes
2232
- mockVersionHashes = { deploy: 'v2:hash-after' };
2233
-
2234
- // Turn 2: re-registration with new hash
2235
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2236
- const toolsV2 = mockRegisteredTools.get('deploy');
2237
- expect(toolsV2).toBeDefined();
2238
-
2239
- // The most recently registered tool should carry the new hash
2240
- const lastTool = toolsV2![toolsV2!.length - 1];
2241
- expect(lastTool.ownerSkillVersionHash).toBe('v2:hash-after');
2242
- });
2243
-
2244
- test('tools for multiple co-active skills each carry their own version hash', () => {
2245
- mockCatalog = [makeSkill('deploy'), makeSkill('oncall')];
2246
- mockManifests = {
2247
- deploy: makeManifest(['deploy_run']),
2248
- oncall: makeManifest(['oncall_page']),
2249
- };
2250
- mockVersionHashes = {
2251
- deploy: 'v1:deploy-hash-123',
2252
- oncall: 'v1:oncall-hash-456',
2253
- };
2254
-
2255
- const history: Message[] = [
2256
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
2257
- ...skillLoadMessages('<loaded_skill id="oncall" />'),
2258
- ];
2259
-
2260
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2261
-
2262
- const deployTools = mockRegisteredTools.get('deploy');
2263
- expect(deployTools).toBeDefined();
2264
- expect(deployTools![0].ownerSkillVersionHash).toBe('v1:deploy-hash-123');
2265
-
2266
- const oncallTools = mockRegisteredTools.get('oncall');
2267
- expect(oncallTools).toBeDefined();
2268
- expect(oncallTools![0].ownerSkillVersionHash).toBe('v1:oncall-hash-456');
2269
- });
2270
-
2271
- test('default hash is used and plumbed when no explicit hash override is set', () => {
2272
- mockCatalog = [makeSkill('deploy')];
2273
- mockManifests = { deploy: makeManifest(['deploy_run']) };
2274
- // No mockVersionHashes override — mock returns 'v1:default-hash-deploy'
2275
-
2276
- const history: Message[] = [
2277
- ...skillLoadMessages('<loaded_skill id="deploy" />'),
2278
- ];
2279
-
2280
- projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2281
-
2282
- const tools = mockRegisteredTools.get('deploy');
2283
- expect(tools).toBeDefined();
2284
- expect(tools![0].ownerSkillVersionHash).toBe('v1:default-hash-deploy');
2285
- });
2286
- });
2287
-
2288
- // ---------------------------------------------------------------------------
2289
- // Child skill includes: no auto-activation
2290
- // ---------------------------------------------------------------------------
2291
-
2292
- describe('includes metadata does not auto-activate child skill tools', () => {
2293
- let sessionState: Map<string, string>;
2294
-
2295
- beforeEach(() => {
2296
- mockCatalog = [];
2297
- mockManifests = {};
2298
- mockRegisteredTools = new Map();
2299
- mockUnregisteredSkillIds = [];
2300
- mockSkillRefCount = new Map();
2301
- mockVersionHashes = {};
2302
- mockVersionHashErrors = new Set();
2303
- sessionState = new Map<string, string>();
2304
- });
2305
-
2306
- test('parent with includes — only parent tools projected when only parent marker present', () => {
2307
- // Parent skill declares child in its includes metadata
2308
- const parentSkill = makeSkill('parent-skill');
2309
- parentSkill.includes = ['child-skill'];
2310
-
2311
- mockCatalog = [parentSkill, makeSkill('child-skill')];
2312
- mockManifests = {
2313
- 'parent-skill': makeManifest(['parent_action']),
2314
- 'child-skill': makeManifest(['child_action']),
2315
- };
2316
-
2317
- // Only parent marker in history — child is NOT loaded
2318
- const history: Message[] = [
2319
- ...skillLoadMessages('<loaded_skill id="parent-skill" />'),
2320
- ];
2321
-
2322
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2323
-
2324
- // Only parent tools should be projected
2325
- expect(result.toolDefinitions).toHaveLength(1);
2326
- expect(result.toolDefinitions[0].name).toBe('parent_action');
2327
- expect(result.allowedToolNames).toEqual(new Set(['parent_action']));
2328
-
2329
- // Child tools must NOT be present
2330
- expect(result.allowedToolNames.has('child_action')).toBe(false);
2331
- });
2332
-
2333
- test('child tools appear only after explicit child loaded_skill marker', () => {
2334
- const parentSkill = makeSkill('parent-skill');
2335
- parentSkill.includes = ['child-skill'];
2336
-
2337
- mockCatalog = [parentSkill, makeSkill('child-skill')];
2338
- mockManifests = {
2339
- 'parent-skill': makeManifest(['parent_action']),
2340
- 'child-skill': makeManifest(['child_action']),
2341
- };
2342
-
2343
- // Both parent AND child markers present — both should be active
2344
- const history: Message[] = [
2345
- ...skillLoadMessages('<loaded_skill id="parent-skill" />'),
2346
- ...skillLoadMessages('<loaded_skill id="child-skill" />'),
2347
- ];
2348
-
2349
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2350
-
2351
- expect(result.toolDefinitions).toHaveLength(2);
2352
- expect(result.allowedToolNames).toEqual(new Set(['parent_action', 'child_action']));
2353
- });
2354
-
2355
- test('child tools are absent even with deep include chain — only markers matter', () => {
2356
- const grandparent = makeSkill('grandparent');
2357
- grandparent.includes = ['parent'];
2358
- const parent = makeSkill('parent');
2359
- parent.includes = ['child'];
2360
-
2361
- mockCatalog = [grandparent, parent, makeSkill('child')];
2362
- mockManifests = {
2363
- grandparent: makeManifest(['gp_action']),
2364
- parent: makeManifest(['parent_action']),
2365
- child: makeManifest(['child_action']),
2366
- };
2367
-
2368
- // Only grandparent marker — despite transitive includes, only grandparent tools active
2369
- const history: Message[] = [
2370
- ...skillLoadMessages('<loaded_skill id="grandparent" />'),
2371
- ];
2372
-
2373
- const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
2374
-
2375
- expect(result.toolDefinitions).toHaveLength(1);
2376
- expect(result.toolDefinitions[0].name).toBe('gp_action');
2377
- expect(result.allowedToolNames.has('parent_action')).toBe(false);
2378
- expect(result.allowedToolNames.has('child_action')).toBe(false);
2379
- });
2380
- });
2381
-
2382
- // ---------------------------------------------------------------------------
2383
- // Browser skill migration harness — validates shared test helpers
2384
- // ---------------------------------------------------------------------------
2385
-
2386
- describe('browser skill migration harness', () => {
2387
- test('buildSkillLoadHistory creates valid skill_load history', () => {
2388
- const history = buildSkillLoadHistory('browser', 'v1:abc123');
2389
- expect(history).toHaveLength(2);
2390
- expect(history[0].role).toBe('assistant');
2391
- expect(history[1].role).toBe('user');
2392
- // Verify tool_use block
2393
- const toolUse = history[0].content[0] as ToolUseContent;
2394
- expect(toolUse.type).toBe('tool_use');
2395
- expect(toolUse.name).toBe('skill_load');
2396
- // Verify tool_result has marker
2397
- const toolResult = history[1].content[0] as ToolResultContent;
2398
- expect(toolResult.type).toBe('tool_result');
2399
- expect(toolResult.content).toContain('<loaded_skill id="browser" version="v1:abc123" />');
2400
- });
2401
-
2402
- test('buildSkillLoadHistory generates unique tool_use IDs per call', () => {
2403
- const h1 = buildSkillLoadHistory('browser', 'v1:abc');
2404
- const h2 = buildSkillLoadHistory('browser', 'v1:def');
2405
- const id1 = (h1[0].content[0] as { id: string }).id;
2406
- const id2 = (h2[0].content[0] as { id: string }).id;
2407
- expect(id1).not.toBe(id2);
2408
- });
2409
-
2410
- test('BROWSER_TOOL_NAMES contains all 10 browser tools', () => {
2411
- expect(BROWSER_TOOL_NAMES).toHaveLength(10);
2412
- expect(BROWSER_TOOL_NAMES).toContain('browser_navigate');
2413
- expect(BROWSER_TOOL_NAMES).toContain('browser_fill_credential');
2414
- });
2415
-
2416
- test('assertBrowserToolsPresent passes when all tools present', () => {
2417
- expect(() => assertBrowserToolsPresent([...BROWSER_TOOL_NAMES, 'extra_tool'])).not.toThrow();
2418
- });
2419
-
2420
- test('assertBrowserToolsPresent fails when tool missing', () => {
2421
- expect(() => assertBrowserToolsPresent(['browser_navigate'])).toThrow();
2422
- });
2423
-
2424
- test('assertBrowserToolsAbsent passes when no browser tools present', () => {
2425
- expect(() => assertBrowserToolsAbsent(['file_read', 'web_search'])).not.toThrow();
2426
- });
2427
-
2428
- test('assertBrowserToolsAbsent fails when browser tool present', () => {
2429
- expect(() => assertBrowserToolsAbsent(['browser_navigate'])).toThrow();
2430
- });
2431
- });