@vellumai/assistant 0.5.16 → 0.6.1

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 (592) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +69 -16
  3. package/Dockerfile +2 -5
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +32 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/knip.json +2 -1
  9. package/openapi.yaml +1198 -83
  10. package/package.json +5 -1
  11. package/src/__tests__/actor-token-service.test.ts +68 -0
  12. package/src/__tests__/agent-loop.test.ts +0 -32
  13. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  14. package/src/__tests__/anthropic-provider.test.ts +217 -98
  15. package/src/__tests__/app-compiler.test.ts +120 -0
  16. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  17. package/src/__tests__/app-executors.test.ts +47 -1
  18. package/src/__tests__/app-source-watcher.test.ts +159 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  20. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  21. package/src/__tests__/call-domain.test.ts +2 -6
  22. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  23. package/src/__tests__/call-recovery.test.ts +2 -6
  24. package/src/__tests__/call-routes-http.test.ts +2 -6
  25. package/src/__tests__/call-store.test.ts +2 -6
  26. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  27. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  28. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  29. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  30. package/src/__tests__/checker.test.ts +63 -9
  31. package/src/__tests__/clawhub.test.ts +54 -24
  32. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  33. package/src/__tests__/config-schema.test.ts +6 -1
  34. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  35. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  36. package/src/__tests__/contacts-tools.test.ts +31 -0
  37. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  38. package/src/__tests__/context-token-estimator.test.ts +175 -10
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +13 -6
  40. package/src/__tests__/conversation-agent-loop.test.ts +13 -51
  41. package/src/__tests__/conversation-attachments.test.ts +2 -6
  42. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  43. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  44. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  45. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  46. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  47. package/src/__tests__/conversation-error.test.ts +33 -2
  48. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  49. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  50. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  53. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  54. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  55. package/src/__tests__/conversation-store.test.ts +2 -6
  56. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  57. package/src/__tests__/conversation-usage.test.ts +2 -6
  58. package/src/__tests__/conversation-wipe.test.ts +13 -414
  59. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  60. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  61. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  62. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  63. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  64. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  65. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  66. package/src/__tests__/date-context.test.ts +76 -210
  67. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  68. package/src/__tests__/file-list-tool.test.ts +219 -0
  69. package/src/__tests__/first-greeting.test.ts +1 -1
  70. package/src/__tests__/followup-tools.test.ts +2 -6
  71. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  72. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  73. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  74. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  75. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  76. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  77. package/src/__tests__/guardian-action-store.test.ts +2 -6
  78. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  79. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  80. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  81. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  82. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  83. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  84. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  85. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  86. package/src/__tests__/heartbeat-service.test.ts +180 -3
  87. package/src/__tests__/identity-routes.test.ts +328 -0
  88. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  89. package/src/__tests__/injection-block.test.ts +178 -0
  90. package/src/__tests__/install-meta.test.ts +506 -0
  91. package/src/__tests__/install-skill-routing.test.ts +293 -0
  92. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  93. package/src/__tests__/invite-routes-http.test.ts +2 -6
  94. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +17 -28
  95. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  96. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  97. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  98. package/src/__tests__/llm-context-route-provider.test.ts +103 -6
  99. package/src/__tests__/llm-request-log-turn-query.test.ts +164 -6
  100. package/src/__tests__/llm-usage-store.test.ts +2 -6
  101. package/src/__tests__/log-export-workspace.test.ts +74 -111
  102. package/src/__tests__/managed-store.test.ts +38 -11
  103. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  104. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  105. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  106. package/src/__tests__/memory-recall-log-store.test.ts +134 -6
  107. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  108. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  109. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  110. package/src/__tests__/mock-fetch.ts +87 -0
  111. package/src/__tests__/non-member-access-request.test.ts +2 -6
  112. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  113. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  114. package/src/__tests__/oauth-cli.test.ts +364 -2
  115. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  116. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  117. package/src/__tests__/outlook-attachments.test.ts +301 -0
  118. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  119. package/src/__tests__/outlook-categories.test.ts +212 -0
  120. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  121. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  122. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  123. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  124. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  125. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  126. package/src/__tests__/outlook-trash.test.ts +77 -0
  127. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  128. package/src/__tests__/parser.test.ts +32 -0
  129. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  130. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  131. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  132. package/src/__tests__/permission-mode-store.test.ts +277 -0
  133. package/src/__tests__/permission-mode.test.ts +101 -0
  134. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  135. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  136. package/src/__tests__/playbook-execution.test.ts +76 -80
  137. package/src/__tests__/playbook-tools.test.ts +5 -7
  138. package/src/__tests__/profiler-routes.test.ts +502 -0
  139. package/src/__tests__/profiler-run-store.test.ts +441 -0
  140. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  141. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  142. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  143. package/src/__tests__/registry.test.ts +3 -3
  144. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  145. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  146. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  147. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  148. package/src/__tests__/schedule-store.test.ts +2 -6
  149. package/src/__tests__/schedule-tools.test.ts +2 -6
  150. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  151. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  152. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  153. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  154. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  155. package/src/__tests__/search-skills-unified.test.ts +422 -0
  156. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  157. package/src/__tests__/send-endpoint-busy.test.ts +44 -9
  158. package/src/__tests__/sequence-store.test.ts +2 -6
  159. package/src/__tests__/server-history-render.test.ts +2 -6
  160. package/src/__tests__/set-permission-mode.test.ts +274 -0
  161. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  162. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  163. package/src/__tests__/skill-load-feature-flag.test.ts +23 -11
  164. package/src/__tests__/skill-memory.test.ts +2 -741
  165. package/src/__tests__/skills-uninstall.test.ts +2 -2
  166. package/src/__tests__/skills.test.ts +1 -1
  167. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  168. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  169. package/src/__tests__/subagent-detail.test.ts +84 -0
  170. package/src/__tests__/subagent-disposal.test.ts +308 -0
  171. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  172. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  173. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  174. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  175. package/src/__tests__/subagent-tools.test.ts +464 -4
  176. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  177. package/src/__tests__/task-compiler.test.ts +2 -6
  178. package/src/__tests__/task-management-tools.test.ts +2 -6
  179. package/src/__tests__/task-memory-cleanup.test.ts +185 -241
  180. package/src/__tests__/task-runner.test.ts +2 -6
  181. package/src/__tests__/task-scheduler.test.ts +2 -6
  182. package/src/__tests__/terminal-tools.test.ts +17 -27
  183. package/src/__tests__/test-preload.ts +7 -0
  184. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  185. package/src/__tests__/tool-executor.test.ts +4 -26
  186. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  187. package/src/__tests__/tool-side-effects-slack-dm.test.ts +277 -0
  188. package/src/__tests__/top-level-renderer.test.ts +10 -13
  189. package/src/__tests__/trust-store.test.ts +1 -1
  190. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  191. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +118 -8
  192. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  193. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  194. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  195. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  196. package/src/__tests__/usage-routes.test.ts +2 -6
  197. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  198. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  199. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  200. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  201. package/src/__tests__/volume-security-guard.test.ts +2 -0
  202. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  203. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  204. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  205. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  206. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  207. package/src/__tests__/workspace-policy.test.ts +1 -1
  208. package/src/agent/attachments.ts +7 -2
  209. package/src/agent/image-optimize.ts +165 -0
  210. package/src/agent/loop.ts +7 -15
  211. package/src/approvals/guardian-request-resolvers.ts +24 -0
  212. package/src/avatar/traits-png-sync.ts +3 -3
  213. package/src/bundler/app-compiler.ts +179 -2
  214. package/src/bundler/package-resolver.ts +3 -5
  215. package/src/cli/__tests__/notifications.test.ts +1 -2
  216. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  217. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  218. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  219. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  220. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  221. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  222. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  223. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  224. package/src/cli/commands/avatar.ts +3 -3
  225. package/src/cli/commands/config.ts +26 -13
  226. package/src/cli/commands/conversations.ts +1 -8
  227. package/src/cli/commands/doctor.ts +2 -2
  228. package/src/cli/commands/email.ts +584 -835
  229. package/src/cli/commands/memory.ts +37 -84
  230. package/src/cli/commands/notifications.ts +7 -2
  231. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  232. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  233. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  234. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  235. package/src/cli/commands/oauth/connect.ts +25 -11
  236. package/src/cli/commands/oauth/mode.ts +7 -0
  237. package/src/cli/commands/oauth/shared.ts +39 -3
  238. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  239. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  240. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  241. package/src/cli/commands/platform/index.ts +16 -16
  242. package/src/cli/commands/routes.ts +396 -0
  243. package/src/cli/commands/skills.ts +218 -36
  244. package/src/cli/commands/trust.ts +2 -2
  245. package/src/cli/lib/daemon-credential-client.ts +2 -3
  246. package/src/cli/program.ts +2 -0
  247. package/src/cli.ts +1 -120
  248. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  249. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  250. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  251. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  252. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  253. package/src/config/bundled-skills/gmail/SKILL.md +4 -12
  254. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  255. package/src/config/bundled-skills/messaging/SKILL.md +17 -18
  256. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  257. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  258. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  259. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  260. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  261. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  262. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  263. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  264. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  265. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  266. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  267. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  268. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  269. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  270. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  271. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  272. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  273. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  274. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  275. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  276. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  277. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  278. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  279. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  280. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  281. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  282. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  283. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  284. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  285. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  286. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  287. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  288. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  289. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  290. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  291. package/src/config/bundled-skills/slack/SKILL.md +3 -7
  292. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  293. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  294. package/src/config/bundled-tool-registry.ts +56 -4
  295. package/src/config/env-registry.ts +78 -8
  296. package/src/config/feature-flag-registry.json +38 -125
  297. package/src/config/schema.ts +8 -0
  298. package/src/config/schemas/filing.ts +51 -0
  299. package/src/config/schemas/heartbeat.ts +15 -12
  300. package/src/config/schemas/memory-lifecycle.ts +12 -0
  301. package/src/config/schemas/platform.ts +8 -0
  302. package/src/config/schemas/security.ts +14 -0
  303. package/src/config/schemas/timeouts.ts +1 -1
  304. package/src/config/skills.ts +18 -7
  305. package/src/context/token-estimator.ts +25 -18
  306. package/src/context/window-manager.ts +6 -2
  307. package/src/credential-execution/process-manager.ts +3 -1
  308. package/src/daemon/app-source-watcher.ts +93 -0
  309. package/src/daemon/config-watcher.ts +79 -1
  310. package/src/daemon/context-overflow-reducer.ts +46 -2
  311. package/src/daemon/conversation-agent-loop-handlers.ts +143 -82
  312. package/src/daemon/conversation-agent-loop.ts +236 -108
  313. package/src/daemon/conversation-error.ts +31 -8
  314. package/src/daemon/conversation-history.ts +4 -19
  315. package/src/daemon/conversation-lifecycle.ts +36 -9
  316. package/src/daemon/conversation-media-retry.ts +85 -7
  317. package/src/daemon/conversation-notifiers.ts +4 -1
  318. package/src/daemon/conversation-process.ts +13 -7
  319. package/src/daemon/conversation-runtime-assembly.ts +305 -306
  320. package/src/daemon/conversation-tool-setup.ts +44 -14
  321. package/src/daemon/conversation-workspace.ts +1 -2
  322. package/src/daemon/conversation.ts +59 -2
  323. package/src/daemon/daemon-control.ts +8 -2
  324. package/src/daemon/date-context.ts +26 -53
  325. package/src/daemon/first-greeting.ts +1 -1
  326. package/src/daemon/handlers/conversations.ts +4 -7
  327. package/src/daemon/handlers/shared.test.ts +143 -0
  328. package/src/daemon/handlers/shared.ts +85 -17
  329. package/src/daemon/handlers/skills.ts +416 -209
  330. package/src/daemon/lifecycle.ts +212 -131
  331. package/src/daemon/main.ts +5 -1
  332. package/src/daemon/message-types/conversations.ts +29 -7
  333. package/src/daemon/message-types/messages.ts +12 -2
  334. package/src/daemon/message-types/schedules.ts +1 -0
  335. package/src/daemon/message-types/settings.ts +6 -0
  336. package/src/daemon/message-types/skills.ts +97 -36
  337. package/src/daemon/profiler-run-store.ts +557 -0
  338. package/src/daemon/providers-setup.ts +5 -0
  339. package/src/daemon/server.ts +100 -11
  340. package/src/daemon/shutdown-handlers.ts +5 -0
  341. package/src/daemon/tool-side-effects.ts +50 -8
  342. package/src/export/transcript-formatter.ts +148 -0
  343. package/src/filing/filing-service.ts +228 -0
  344. package/src/heartbeat/heartbeat-service.ts +97 -7
  345. package/src/hooks/cli.ts +2 -2
  346. package/src/hooks/runner.ts +15 -38
  347. package/src/inbound/platform-callback-registration.ts +14 -14
  348. package/src/mcp/client.ts +6 -0
  349. package/src/mcp/mcp-oauth-provider.ts +149 -27
  350. package/src/memory/admin.ts +42 -75
  351. package/src/memory/app-store.ts +69 -0
  352. package/src/memory/conversation-bootstrap.ts +3 -1
  353. package/src/memory/conversation-crud.ts +211 -288
  354. package/src/memory/conversation-group-migration.ts +157 -0
  355. package/src/memory/conversation-queries.ts +61 -13
  356. package/src/memory/conversation-title-service.ts +1 -0
  357. package/src/memory/db-init.ts +194 -361
  358. package/src/memory/embed.ts +73 -0
  359. package/src/memory/embedding-backend.ts +8 -14
  360. package/src/memory/embedding-runtime-manager.ts +12 -114
  361. package/src/memory/fingerprint.ts +2 -2
  362. package/src/memory/graph/bootstrap.ts +521 -0
  363. package/src/memory/graph/capability-seed.ts +449 -0
  364. package/src/memory/graph/consolidation.ts +725 -0
  365. package/src/memory/graph/conversation-graph-memory.ts +659 -0
  366. package/src/memory/graph/decay.test.ts +208 -0
  367. package/src/memory/graph/decay.ts +195 -0
  368. package/src/memory/graph/extraction-job.ts +74 -0
  369. package/src/memory/graph/extraction.test.ts +936 -0
  370. package/src/memory/graph/extraction.ts +1297 -0
  371. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  372. package/src/memory/graph/graph-search.ts +280 -0
  373. package/src/memory/graph/image-ref-utils.ts +29 -0
  374. package/src/memory/graph/injection.test.ts +513 -0
  375. package/src/memory/graph/injection.ts +469 -0
  376. package/src/memory/graph/inspect.ts +543 -0
  377. package/src/memory/graph/narrative.ts +267 -0
  378. package/src/memory/graph/pattern-scan.ts +269 -0
  379. package/src/memory/graph/retriever.ts +1111 -0
  380. package/src/memory/graph/scoring.test.ts +548 -0
  381. package/src/memory/graph/scoring.ts +232 -0
  382. package/src/memory/graph/serendipity.ts +65 -0
  383. package/src/memory/graph/store.test.ts +1098 -0
  384. package/src/memory/graph/store.ts +838 -0
  385. package/src/memory/graph/tool-handlers.ts +301 -0
  386. package/src/memory/graph/tools.ts +97 -0
  387. package/src/memory/graph/triggers.test.ts +487 -0
  388. package/src/memory/graph/triggers.ts +223 -0
  389. package/src/memory/graph/types.ts +295 -0
  390. package/src/memory/group-crud.ts +191 -0
  391. package/src/memory/indexer.ts +37 -19
  392. package/src/memory/job-handlers/cleanup.ts +32 -42
  393. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  394. package/src/memory/job-handlers/embedding.ts +5 -31
  395. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  396. package/src/memory/job-handlers/summarization.ts +32 -17
  397. package/src/memory/job-utils.ts +1 -1
  398. package/src/memory/jobs-store.ts +21 -31
  399. package/src/memory/jobs-worker.ts +180 -129
  400. package/src/memory/llm-request-log-store.ts +96 -12
  401. package/src/memory/memory-recall-log-store.ts +49 -5
  402. package/src/memory/message-content.ts +1 -0
  403. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  404. package/src/memory/migrations/203-drop-memory-items-tables.ts +55 -0
  405. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  406. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  407. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  408. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  409. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  410. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  411. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  412. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  413. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  414. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  415. package/src/memory/migrations/index.ts +12 -0
  416. package/src/memory/migrations/registry.ts +16 -0
  417. package/src/memory/qdrant-client.ts +44 -17
  418. package/src/memory/schema/conversations.ts +14 -0
  419. package/src/memory/schema/index.ts +1 -0
  420. package/src/memory/schema/infrastructure.ts +8 -1
  421. package/src/memory/schema/memory-core.ts +0 -51
  422. package/src/memory/schema/memory-graph.ts +154 -0
  423. package/src/memory/search/semantic.ts +47 -91
  424. package/src/memory/task-memory-cleanup.ts +58 -61
  425. package/src/messaging/providers/outlook/adapter.ts +8 -1
  426. package/src/messaging/providers/outlook/client.ts +299 -0
  427. package/src/messaging/providers/outlook/types.ts +118 -0
  428. package/src/notifications/adapters/macos.ts +1 -0
  429. package/src/notifications/copy-composer.ts +95 -0
  430. package/src/notifications/decision-engine.ts +35 -0
  431. package/src/notifications/signal.ts +16 -0
  432. package/src/oauth/seed-providers.ts +2 -1
  433. package/src/permissions/checker.ts +36 -4
  434. package/src/permissions/defaults.ts +4 -4
  435. package/src/permissions/permission-mode-store.ts +180 -0
  436. package/src/permissions/permission-mode.ts +31 -0
  437. package/src/permissions/workspace-policy.ts +10 -1
  438. package/src/playbooks/playbook-compiler.ts +19 -18
  439. package/src/playbooks/types.ts +4 -3
  440. package/src/prompts/system-prompt.ts +62 -36
  441. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  442. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  443. package/src/prompts/templates/HEARTBEAT.md +3 -1
  444. package/src/prompts/templates/SOUL.md +25 -4
  445. package/src/prompts/templates/UPDATES.md +8 -0
  446. package/src/providers/anthropic/client.ts +136 -220
  447. package/src/providers/gemini/client.ts +1 -1
  448. package/src/providers/openai/client.ts +1 -1
  449. package/src/providers/registry.ts +1 -1
  450. package/src/providers/retry.ts +19 -3
  451. package/src/runtime/actor-trust-resolver.ts +5 -1
  452. package/src/runtime/auth/route-policy.ts +30 -0
  453. package/src/runtime/guardian-reply-router.ts +5 -1
  454. package/src/runtime/http-server.ts +55 -5
  455. package/src/runtime/http-types.ts +12 -1
  456. package/src/runtime/middleware/auth.ts +20 -0
  457. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  458. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  459. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  460. package/src/runtime/routes/app-management-routes.ts +1 -11
  461. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  462. package/src/runtime/routes/archive-utils.ts +29 -0
  463. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  464. package/src/runtime/routes/attachment-routes.ts +106 -16
  465. package/src/runtime/routes/avatar-routes.ts +2 -9
  466. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  467. package/src/runtime/routes/btw-routes.ts +22 -1
  468. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  469. package/src/runtime/routes/conversation-management-routes.ts +3 -14
  470. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  471. package/src/runtime/routes/conversation-routes.ts +264 -44
  472. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  473. package/src/runtime/routes/debug-routes.ts +1 -1
  474. package/src/runtime/routes/global-search-routes.ts +21 -19
  475. package/src/runtime/routes/group-routes.ts +207 -0
  476. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  477. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  478. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  479. package/src/runtime/routes/identity-routes.ts +53 -18
  480. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  481. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  482. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  483. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  484. package/src/runtime/routes/log-export-routes.ts +23 -275
  485. package/src/runtime/routes/memory-item-routes.test.ts +170 -247
  486. package/src/runtime/routes/memory-item-routes.ts +341 -388
  487. package/src/runtime/routes/migration-routes.ts +18 -7
  488. package/src/runtime/routes/profiler-routes.ts +350 -0
  489. package/src/runtime/routes/schedule-routes.ts +28 -11
  490. package/src/runtime/routes/settings-routes.ts +95 -8
  491. package/src/runtime/routes/skills-routes.ts +103 -37
  492. package/src/runtime/routes/subagents-routes.ts +28 -7
  493. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  494. package/src/runtime/routes/user-routes.ts +41 -0
  495. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  496. package/src/runtime/routes/workspace-routes.ts +0 -1
  497. package/src/schedule/schedule-store.ts +30 -0
  498. package/src/schedule/scheduler.ts +52 -18
  499. package/src/security/oauth2.ts +1 -1
  500. package/src/security/secure-keys.ts +4 -8
  501. package/src/shared/provider-env-vars.ts +19 -0
  502. package/src/skills/catalog-cache.ts +5 -0
  503. package/src/skills/catalog-install.ts +25 -16
  504. package/src/skills/clawhub.ts +134 -154
  505. package/src/skills/install-meta.ts +208 -0
  506. package/src/skills/managed-store.ts +29 -18
  507. package/src/skills/skill-memory.ts +12 -229
  508. package/src/skills/skillssh-registry.ts +19 -17
  509. package/src/subagent/index.ts +13 -3
  510. package/src/subagent/manager.ts +308 -29
  511. package/src/subagent/types.ts +68 -0
  512. package/src/tasks/task-runner.ts +7 -5
  513. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  514. package/src/tools/apps/executors.ts +29 -4
  515. package/src/tools/browser/runtime-check.ts +3 -1
  516. package/src/tools/filesystem/list.ts +93 -0
  517. package/src/tools/memory/register.ts +63 -46
  518. package/src/tools/permission-checker.ts +85 -1
  519. package/src/tools/registry.ts +4 -0
  520. package/src/tools/schedule/create.ts +3 -0
  521. package/src/tools/schedule/list.ts +1 -0
  522. package/src/tools/schedule/update.ts +6 -0
  523. package/src/tools/shared/filesystem/errors.ts +5 -0
  524. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  525. package/src/tools/shared/filesystem/image-read.ts +22 -85
  526. package/src/tools/shared/filesystem/types.ts +17 -0
  527. package/src/tools/shared/shell-output.ts +31 -2
  528. package/src/tools/subagent/abort.ts +12 -2
  529. package/src/tools/subagent/message.ts +9 -2
  530. package/src/tools/subagent/notify-parent.ts +79 -0
  531. package/src/tools/subagent/read.ts +29 -8
  532. package/src/tools/subagent/resolve.ts +21 -0
  533. package/src/tools/subagent/spawn.ts +2 -0
  534. package/src/tools/subagent/status.ts +11 -1
  535. package/src/tools/system/avatar-generator.ts +3 -3
  536. package/src/tools/system/register.ts +23 -0
  537. package/src/tools/system/set-permission-mode.ts +103 -0
  538. package/src/tools/terminal/parser.ts +30 -5
  539. package/src/tools/terminal/safe-env.ts +17 -1
  540. package/src/tools/tool-manifest.ts +9 -3
  541. package/src/tools/types.ts +2 -0
  542. package/src/util/browser.ts +25 -10
  543. package/src/util/bun-runtime.ts +172 -0
  544. package/src/util/logger.ts +1 -1
  545. package/src/util/platform.ts +50 -17
  546. package/src/watcher/providers/outlook-calendar.ts +343 -0
  547. package/src/watcher/providers/outlook.ts +198 -0
  548. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  549. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  550. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  551. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  552. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  553. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  554. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  555. package/src/workspace/migrations/registry.ts +10 -0
  556. package/src/workspace/top-level-renderer.ts +5 -9
  557. package/src/__tests__/cli-memory.test.ts +0 -372
  558. package/src/__tests__/clipboard.test.ts +0 -88
  559. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  560. package/src/__tests__/journal-context.test.ts +0 -268
  561. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  562. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  563. package/src/__tests__/memory-query-builder.test.ts +0 -59
  564. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  565. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  566. package/src/__tests__/memory-regressions.test.ts +0 -3696
  567. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  568. package/src/cli/cli-memory.ts +0 -176
  569. package/src/daemon/conversation-memory.ts +0 -207
  570. package/src/memory/conversation-starters-cadence.ts +0 -74
  571. package/src/memory/items-extractor.ts +0 -860
  572. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  573. package/src/memory/job-handlers/extraction.ts +0 -40
  574. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  575. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  576. package/src/memory/journal-memory.ts +0 -224
  577. package/src/memory/query-builder.ts +0 -47
  578. package/src/memory/query-expansion.ts +0 -83
  579. package/src/memory/retriever.test.ts +0 -1592
  580. package/src/memory/retriever.ts +0 -1331
  581. package/src/memory/search/formatting.test.ts +0 -140
  582. package/src/memory/search/formatting.ts +0 -262
  583. package/src/memory/search/mmr.ts +0 -139
  584. package/src/memory/search/ranking.ts +0 -15
  585. package/src/memory/search/staleness.ts +0 -40
  586. package/src/memory/search/tier-classifier.ts +0 -18
  587. package/src/memory/search/types.ts +0 -121
  588. package/src/prompts/journal-context.ts +0 -154
  589. package/src/tools/memory/definitions.ts +0 -69
  590. package/src/tools/memory/handlers.test.ts +0 -562
  591. package/src/tools/memory/handlers.ts +0 -434
  592. package/src/util/clipboard.ts +0 -34
@@ -1,3 +1,4 @@
1
+ import { execSync } from "node:child_process";
1
2
  import {
2
3
  existsSync,
3
4
  lstatSync,
@@ -7,6 +8,7 @@ import {
7
8
  rmSync,
8
9
  statSync,
9
10
  } from "node:fs";
11
+ import { homedir } from "node:os";
10
12
  import { join, relative } from "node:path";
11
13
 
12
14
  import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
@@ -18,6 +20,11 @@ import {
18
20
  } from "../../config/loader.js";
19
21
  import { resolveSkillStates, skillFlagKey } from "../../config/skill-state.js";
20
22
  import { loadSkillCatalog, type SkillSummary } from "../../config/skills.js";
23
+ import {
24
+ deleteSkillCapabilityNode,
25
+ seedSkillGraphNodes,
26
+ seedUninstalledCatalogSkillMemories,
27
+ } from "../../memory/graph/capability-seed.js";
21
28
  import {
22
29
  createTimeout,
23
30
  extractText,
@@ -26,7 +33,10 @@ import {
26
33
  } from "../../providers/provider-send-message.js";
27
34
  import { isTextMimeType as isTextMime } from "../../runtime/routes/workspace-utils.js";
28
35
  import { getCatalog } from "../../skills/catalog-cache.js";
29
- import { installSkillLocally } from "../../skills/catalog-install.js";
36
+ import {
37
+ installSkillLocally,
38
+ upsertSkillsIndex,
39
+ } from "../../skills/catalog-install.js";
30
40
  import { filterByQuery } from "../../skills/catalog-search.js";
31
41
  import {
32
42
  clawhubCheckUpdates,
@@ -36,6 +46,10 @@ import {
36
46
  clawhubSearch,
37
47
  clawhubUpdate,
38
48
  } from "../../skills/clawhub.js";
49
+ import {
50
+ readInstallMeta,
51
+ type SkillInstallMeta,
52
+ } from "../../skills/install-meta.js";
39
53
  import {
40
54
  createManagedSkill,
41
55
  deleteManagedSkill,
@@ -43,10 +57,15 @@ import {
43
57
  validateManagedSkillId,
44
58
  } from "../../skills/managed-store.js";
45
59
  import {
46
- deleteSkillCapabilityMemory,
47
- seedCatalogSkillMemories,
48
- } from "../../skills/skill-memory.js";
60
+ installExternalSkill,
61
+ resolveSkillSource,
62
+ searchSkillsRegistry,
63
+ } from "../../skills/skillssh-registry.js";
49
64
  import { getWorkspaceSkillsDir } from "../../util/platform.js";
65
+ import type {
66
+ SkillDetailResponse,
67
+ SlimSkillResponse,
68
+ } from "../message-types/skills.js";
50
69
  import {
51
70
  CONFIG_RELOAD_DEBOUNCE_MS,
52
71
  ensureSkillEntry,
@@ -72,52 +91,6 @@ export interface SkillOperationContext {
72
91
  broadcast: HandlerContext["broadcast"];
73
92
  }
74
93
 
75
- // ─── Provenance resolution ──────────────────────────────────────────────────
76
-
77
- interface SkillProvenance {
78
- kind: "first-party" | "third-party" | "local";
79
- provider?: string;
80
- originId?: string;
81
- sourceUrl?: string;
82
- }
83
-
84
- const CLAWHUB_BASE_URL = "https://skills.sh";
85
-
86
- function resolveProvenance(summary: SkillSummary): SkillProvenance {
87
- // Bundled skills are always first-party (shipped with Vellum)
88
- if (summary.source === "bundled") {
89
- return { kind: "first-party", provider: "Vellum" };
90
- }
91
-
92
- // Managed skills are third-party (installed from clawhub). The homepage field
93
- // confirms provenance.
94
- if (summary.source === "managed") {
95
- if (
96
- summary.homepage?.includes("skills.sh") ||
97
- summary.homepage?.includes("clawhub")
98
- ) {
99
- return {
100
- kind: "third-party",
101
- provider: "skills.sh",
102
- originId: summary.id,
103
- sourceUrl:
104
- summary.homepage ??
105
- `${CLAWHUB_BASE_URL}/skills/${encodeURIComponent(summary.id)}`,
106
- };
107
- }
108
- // No positive evidence of clawhub origin -- likely user-authored.
109
- // Default to "local" to avoid mislabeling.
110
- return { kind: "local" };
111
- }
112
-
113
- // Workspace and extra skills are user-provided
114
- if (summary.source === "workspace" || summary.source === "extra") {
115
- return { kind: "local" };
116
- }
117
-
118
- return { kind: "local" };
119
- }
120
-
121
94
  // ─── Frontmatter parsing ─────────────────────────────────────────────────────
122
95
 
123
96
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
@@ -232,52 +205,148 @@ function saveConfigWithSuppression(
232
205
  ctx.updateConfigFingerprint();
233
206
  }
234
207
 
235
- export interface SkillListItem {
236
- id: string;
237
- name: string;
238
- description: string;
239
- emoji?: string;
240
- homepage?: string;
241
- source: "bundled" | "managed" | "workspace" | "clawhub" | "extra" | "catalog";
242
- state: "enabled" | "disabled";
243
- installStatus: "bundled" | "installed" | "available";
244
- updateAvailable: boolean;
245
- provenance: SkillProvenance;
208
+ /**
209
+ * Shared post-install logic for catalog, skillssh, and clawhub install paths
210
+ * in the daemon. Handles catalog reload, auto-enable, broadcast, and memory
211
+ * seeding.
212
+ *
213
+ * SKILLS.md indexing and dependency installation are handled separately:
214
+ * `installSkillLocally` and `installExternalSkill` handle them internally
215
+ * (so both CLI and daemon callers get correct behavior), while the clawhub
216
+ * path handles them inline in `installSkill()` since `clawhubInstall` only
217
+ * runs the clawhub CLI and writes metadata.
218
+ *
219
+ * NOT used for bundled skills — those have a simpler inline path in
220
+ * `installSkill()` that only auto-enables, broadcasts, and seeds memories.
221
+ */
222
+ export function postInstallSkill(
223
+ skillId: string,
224
+ _skillDir: string,
225
+ ctx: SkillOperationContext,
226
+ ): void {
227
+ // Reload skill catalog so the newly installed skill is picked up
228
+ loadSkillCatalog();
229
+
230
+ // Auto-enable the skill in config
231
+ try {
232
+ const raw = loadRawConfig();
233
+ ensureSkillEntry(raw, skillId).enabled = true;
234
+ saveConfigWithSuppression(raw, ctx);
235
+ ctx.broadcast({
236
+ type: "skills_state_changed",
237
+ name: skillId,
238
+ state: "enabled",
239
+ });
240
+ } catch (err) {
241
+ log.warn({ err, skillId }, "Failed to auto-enable installed skill");
242
+ }
243
+
244
+ // Seed skill memories
245
+ seedSkillGraphNodes();
246
+ void seedUninstalledCatalogSkillMemories().catch(() => {});
247
+ }
248
+
249
+ // ─── Kind / origin / status derivation ───────────────────────────────────────
250
+
251
+ /** Map the old `source` field to the new `kind` axis. */
252
+ function deriveKind(
253
+ source: "bundled" | "managed" | "workspace" | "extra" | "catalog",
254
+ ): SlimSkillResponse["kind"] {
255
+ if (source === "bundled") return "bundled";
256
+ if (source === "catalog") return "catalog";
257
+ return "installed"; // managed, workspace, extra
258
+ }
259
+
260
+ /** Map a resolved skill to its `origin`, using install-meta.json when available. */
261
+ function deriveOrigin(
262
+ kind: SlimSkillResponse["kind"],
263
+ directoryPath: string,
264
+ installMeta?: SkillInstallMeta | null,
265
+ ): SlimSkillResponse["origin"] {
266
+ if (kind === "bundled") return "vellum";
267
+ if (kind === "catalog") return "vellum";
268
+ // For installed skills, use provided install-meta or read from disk.
269
+ // null means "already read, nothing found" — don't re-read.
270
+ const meta =
271
+ installMeta !== undefined ? installMeta : readInstallMeta(directoryPath);
272
+ return meta?.origin ?? "custom";
246
273
  }
247
274
 
248
- /** Sorting rank for provenance-based ordering: first-party first, local last. */
249
- function provenanceSortRank(p: SkillProvenance): number {
250
- if (p.kind === "first-party") return 0;
251
- if (p.kind === "third-party" && p.provider) return 1;
252
- if (p.kind === "third-party") return 2;
253
- return 3; // local
275
+ /** Sort rank by kind: bundled first, then catalog, then installed. */
276
+ function kindSortRank(kind: SlimSkillResponse["kind"]): number {
277
+ if (kind === "bundled") return 0;
278
+ if (kind === "catalog") return 1;
279
+ return 2; // installed
254
280
  }
255
281
 
256
- export function listSkills(_ctx: SkillOperationContext): SkillListItem[] {
282
+ /** Convert a resolved skill to a SlimSkillResponse. */
283
+ function toSlimSkillResponse(
284
+ summary: SkillSummary,
285
+ state: "enabled" | "disabled",
286
+ ): SlimSkillResponse {
287
+ const kind = deriveKind(summary.source);
288
+ // Read install-meta once and pass it through to avoid redundant file I/O.
289
+ // Use undefined to mean "not yet read"; null means "read but no metadata found".
290
+ const installMeta =
291
+ kind === "installed" ? readInstallMeta(summary.directoryPath) : undefined;
292
+ const origin = deriveOrigin(kind, summary.directoryPath, installMeta);
293
+ const status: SlimSkillResponse["status"] = state;
294
+
295
+ const base = {
296
+ id: summary.id,
297
+ name: summary.displayName,
298
+ description: summary.description,
299
+ emoji: summary.emoji,
300
+ kind,
301
+ status,
302
+ } as const;
303
+
304
+ switch (origin) {
305
+ case "vellum":
306
+ return { ...base, origin };
307
+ case "clawhub": {
308
+ const meta =
309
+ installMeta !== undefined
310
+ ? installMeta
311
+ : readInstallMeta(summary.directoryPath);
312
+ return {
313
+ ...base,
314
+ origin,
315
+ slug: meta?.slug ?? summary.id,
316
+ author: "",
317
+ stars: 0,
318
+ installs: 0,
319
+ reports: 0,
320
+ };
321
+ }
322
+ case "skillssh": {
323
+ const meta =
324
+ installMeta !== undefined
325
+ ? installMeta
326
+ : readInstallMeta(summary.directoryPath);
327
+ return {
328
+ ...base,
329
+ origin,
330
+ slug: meta?.slug ?? summary.id,
331
+ sourceRepo: meta?.sourceRepo ?? "",
332
+ installs: 0,
333
+ };
334
+ }
335
+ case "custom":
336
+ return { ...base, origin };
337
+ }
338
+ }
339
+
340
+ export function listSkills(_ctx: SkillOperationContext): SlimSkillResponse[] {
257
341
  const config = getConfig();
258
342
  const catalog = loadSkillCatalog();
259
343
  const resolved = resolveSkillStates(catalog, config);
260
344
 
261
- const items = resolved.map((r) => ({
262
- id: r.summary.id,
263
- name: r.summary.displayName,
264
- description: r.summary.description,
265
- emoji: r.summary.emoji,
266
- homepage: r.summary.homepage,
267
- source: r.summary.source,
268
- state: r.state,
269
- installStatus: (r.summary.source === "bundled"
270
- ? "bundled"
271
- : "installed") as SkillListItem["installStatus"],
272
- updateAvailable: false,
273
- provenance: resolveProvenance(r.summary),
274
- }));
275
-
276
- // Sort: first-party > third-party with provider > third-party without > local,
277
- // alphabetical by name within each tier.
345
+ const items = resolved.map((r) => toSlimSkillResponse(r.summary, r.state));
346
+
347
+ // Sort by kind rank, then alphabetical by name within each tier.
278
348
  items.sort((a, b) => {
279
- const rankDiff =
280
- provenanceSortRank(a.provenance) - provenanceSortRank(b.provenance);
349
+ const rankDiff = kindSortRank(a.kind) - kindSortRank(b.kind);
281
350
  if (rankDiff !== 0) return rankDiff;
282
351
  return a.name.localeCompare(b.name);
283
352
  });
@@ -291,7 +360,7 @@ export function listSkills(_ctx: SkillOperationContext): SkillListItem[] {
291
360
  */
292
361
  export async function listSkillsWithCatalog(
293
362
  ctx: SkillOperationContext,
294
- ): Promise<SkillListItem[]> {
363
+ ): Promise<SlimSkillResponse[]> {
295
364
  const installed = listSkills(ctx);
296
365
  const installedIds = new Set(installed.map((s) => s.id));
297
366
 
@@ -304,28 +373,24 @@ export async function listSkillsWithCatalog(
304
373
  }
305
374
 
306
375
  // All entries from the Vellum platform API are first-party.
307
- // Create SkillListItems for catalog skills not already installed.
308
- const available: SkillListItem[] = catalogSkills
376
+ // Create SlimSkillResponses for catalog skills not already installed.
377
+ const available: SlimSkillResponse[] = catalogSkills
309
378
  .filter((cs) => !installedIds.has(cs.id))
310
379
  .map((cs) => ({
311
380
  id: cs.id,
312
381
  name: cs.metadata?.vellum?.["display-name"] ?? cs.name,
313
382
  description: cs.description,
314
383
  emoji: cs.emoji,
315
- homepage: undefined,
316
- source: "catalog" as const,
317
- state: "disabled" as const,
318
- installStatus: "available" as const,
319
- updateAvailable: false,
320
- provenance: { kind: "first-party" as const, provider: "Vellum" },
384
+ kind: "catalog" as const,
385
+ origin: "vellum" as const,
386
+ status: "available" as const,
321
387
  }));
322
388
 
323
389
  const merged = [...installed, ...available];
324
390
 
325
- // Sort using the same provenance sort + alphabetical
391
+ // Sort by kind rank, then alphabetical by name
326
392
  merged.sort((a, b) => {
327
- const rankDiff =
328
- provenanceSortRank(a.provenance) - provenanceSortRank(b.provenance);
393
+ const rankDiff = kindSortRank(a.kind) - kindSortRank(b.kind);
329
394
  if (rankDiff !== 0) return rankDiff;
330
395
  return a.name.localeCompare(b.name);
331
396
  });
@@ -333,10 +398,10 @@ export async function listSkillsWithCatalog(
333
398
  return merged;
334
399
  }
335
400
 
336
- /** Look up a single skill by ID from the resolved catalog, returning its SkillListItem. */
401
+ /** Look up a single skill by ID from the resolved catalog, returning its SlimSkillResponse. */
337
402
  function findSkillById(
338
403
  skillId: string,
339
- ): { item: SkillListItem; summary: SkillSummary } | undefined {
404
+ ): { item: SlimSkillResponse; summary: SkillSummary } | undefined {
340
405
  const config = getConfig();
341
406
  const catalog = loadSkillCatalog();
342
407
  const resolved = resolveSkillStates(catalog, config);
@@ -344,30 +409,87 @@ function findSkillById(
344
409
  if (!match) return undefined;
345
410
 
346
411
  const r = match;
347
- const item: SkillListItem = {
348
- id: r.summary.id,
349
- name: r.summary.displayName,
350
- description: r.summary.description,
351
- emoji: r.summary.emoji,
352
- homepage: r.summary.homepage,
353
- source: r.summary.source,
354
- state: r.state,
355
- installStatus: r.summary.source === "bundled" ? "bundled" : "installed",
356
- updateAvailable: false,
357
- provenance: resolveProvenance(r.summary),
358
- };
412
+ const item = toSlimSkillResponse(r.summary, r.state);
359
413
  return { item, summary: r.summary };
360
414
  }
361
415
 
362
- export function getSkill(
416
+ export async function getSkill(
363
417
  skillId: string,
364
418
  _ctx: SkillOperationContext,
365
- ): { skill: SkillListItem } | { error: string; status: number } {
419
+ ): Promise<{ skill: SkillDetailResponse } | { error: string; status: number }> {
366
420
  const found = findSkillById(skillId);
367
421
  if (!found) {
368
422
  return { error: `Skill "${skillId}" not found`, status: 404 };
369
423
  }
370
- return { skill: found.item };
424
+
425
+ const slim = found.item;
426
+
427
+ // Build the detail response as a flat discriminated union on origin.
428
+ // Origin-specific fields are spread directly at the top level.
429
+ if (slim.origin === "clawhub") {
430
+ // Start with slim clawhub fields, then enrich with inspect data.
431
+ const detail: SkillDetailResponse = {
432
+ id: slim.id,
433
+ name: slim.name,
434
+ description: slim.description,
435
+ emoji: slim.emoji,
436
+ kind: slim.kind,
437
+ origin: slim.origin,
438
+ status: slim.status,
439
+ slug: slim.slug,
440
+ author: slim.author,
441
+ stars: slim.stars,
442
+ installs: slim.installs,
443
+ reports: slim.reports,
444
+ publishedAt: slim.publishedAt,
445
+ };
446
+ try {
447
+ const inspectResult = await clawhubInspect(slim.slug);
448
+ if (inspectResult.data) {
449
+ const data = inspectResult.data;
450
+ (detail as { owner?: typeof data.owner }).owner = data.owner;
451
+ (detail as { stats?: typeof data.stats }).stats = data.stats;
452
+ (
453
+ detail as { latestVersion?: typeof data.latestVersion }
454
+ ).latestVersion = data.latestVersion;
455
+ (detail as { createdAt?: typeof data.createdAt }).createdAt =
456
+ data.createdAt;
457
+ (detail as { updatedAt?: typeof data.updatedAt }).updatedAt =
458
+ data.updatedAt;
459
+ }
460
+ } catch (err) {
461
+ log.warn({ err, skillId }, "Failed to enrich clawhub skill detail");
462
+ }
463
+ return { skill: detail };
464
+ }
465
+
466
+ if (slim.origin === "skillssh") {
467
+ const detail: SkillDetailResponse = {
468
+ id: slim.id,
469
+ name: slim.name,
470
+ description: slim.description,
471
+ emoji: slim.emoji,
472
+ kind: slim.kind,
473
+ origin: slim.origin,
474
+ status: slim.status,
475
+ slug: slim.slug,
476
+ sourceRepo: slim.sourceRepo,
477
+ installs: slim.installs,
478
+ };
479
+ return { skill: detail };
480
+ }
481
+
482
+ // vellum or custom origin — base fields only
483
+ const detail: SkillDetailResponse = {
484
+ id: slim.id,
485
+ name: slim.name,
486
+ description: slim.description,
487
+ emoji: slim.emoji,
488
+ kind: slim.kind,
489
+ origin: slim.origin,
490
+ status: slim.status,
491
+ };
492
+ return { skill: detail };
371
493
  }
372
494
 
373
495
  // ─── Skill file listing ──────────────────────────────────────────────────────
@@ -450,7 +572,7 @@ export function getSkillFiles(
450
572
  skillId: string,
451
573
  _ctx: SkillOperationContext,
452
574
  ):
453
- | { skill: SkillListItem; files: SkillFileEntry[] }
575
+ | { skill: SlimSkillResponse; files: SkillFileEntry[] }
454
576
  | { error: string; status: number } {
455
577
  const found = findSkillById(skillId);
456
578
  if (!found) {
@@ -481,7 +603,8 @@ export function enableSkill(
481
603
  name: skillId,
482
604
  state: "enabled",
483
605
  });
484
- seedCatalogSkillMemories();
606
+ seedSkillGraphNodes();
607
+ void seedUninstalledCatalogSkillMemories().catch(() => {});
485
608
  return { success: true };
486
609
  } catch (err) {
487
610
  const message = err instanceof Error ? err.message : String(err);
@@ -503,6 +626,8 @@ export function disableSkill(
503
626
  name: skillId,
504
627
  state: "disabled",
505
628
  });
629
+ seedSkillGraphNodes();
630
+ void seedUninstalledCatalogSkillMemories().catch(() => {});
506
631
  return { success: true };
507
632
  } catch (err) {
508
633
  const message = err instanceof Error ? err.message : String(err);
@@ -535,8 +660,21 @@ export function configureSkill(
535
660
  }
536
661
  }
537
662
 
663
+ /**
664
+ * Check whether a slug looks like a skills.sh multi-segment format
665
+ * (e.g. `owner/repo/skill-name` — three or more `/`-separated segments).
666
+ */
667
+ function looksLikeSkillsShSlug(slug: string): boolean {
668
+ return slug.split("/").length >= 3;
669
+ }
670
+
538
671
  export async function installSkill(
539
- spec: { slug: string; version?: string },
672
+ spec: {
673
+ slug: string;
674
+ version?: string;
675
+ origin?: "clawhub" | "skillssh";
676
+ contactId?: string;
677
+ },
540
678
  ctx: SkillOperationContext,
541
679
  ): Promise<{ success: true } | { success: false; error: string }> {
542
680
  try {
@@ -560,7 +698,12 @@ export async function installSkill(
560
698
  (s) => s.id === spec.slug && s.source === "bundled",
561
699
  );
562
700
  if (bundled) {
563
- // Auto-enable the bundled skill so it's immediately usable
701
+ // Intentional divergence from postInstallSkill(): bundled skills are
702
+ // shipped with the assistant binary and are already on disk. They skip
703
+ // SKILLS.md indexing (they're discovered via the bundled catalog, not
704
+ // the workspace index), dependency installation (deps are pre-bundled),
705
+ // and catalog reload (the catalog already includes them). Only
706
+ // auto-enable, broadcast, and seed memories are needed.
564
707
  try {
565
708
  const raw = loadRawConfig();
566
709
  ensureSkillEntry(raw, spec.slug).enabled = true;
@@ -576,74 +719,85 @@ export async function installSkill(
576
719
  "Failed to auto-enable bundled skill",
577
720
  );
578
721
  }
579
- seedCatalogSkillMemories();
722
+ seedSkillGraphNodes();
723
+ void seedUninstalledCatalogSkillMemories().catch(() => {});
580
724
  return { success: true };
581
725
  }
582
726
 
583
- // Check the Vellum catalog (first-party skills hosted on the platform)
584
- try {
585
- const vellumCatalog = await getCatalog();
586
- const catalogEntry = vellumCatalog.find((s) => s.id === spec.slug);
587
- if (catalogEntry) {
588
- await installSkillLocally(spec.slug, catalogEntry, true);
589
-
590
- // Reload skill catalog so the newly installed skill is picked up
591
- loadSkillCatalog();
592
-
593
- // Auto-enable the newly installed catalog skill
594
- try {
595
- const raw = loadRawConfig();
596
- ensureSkillEntry(raw, spec.slug).enabled = true;
597
- saveConfigWithSuppression(raw, ctx);
598
- ctx.broadcast({
599
- type: "skills_state_changed",
600
- name: spec.slug,
601
- state: "enabled",
602
- });
603
- } catch (err) {
604
- log.warn(
605
- { err, skillId: spec.slug },
606
- "Failed to auto-enable installed catalog skill",
727
+ // Check the Vellum catalog (first-party skills hosted on the platform).
728
+ // Skip when the caller explicitly specified a community origin — this
729
+ // prevents slug collisions where a catalog skill shadows a community
730
+ // skill the user selected from search results.
731
+ if (spec.origin !== "clawhub" && spec.origin !== "skillssh")
732
+ try {
733
+ const vellumCatalog = await getCatalog();
734
+ const catalogEntry = vellumCatalog.find((s) => s.id === spec.slug);
735
+ if (catalogEntry) {
736
+ await installSkillLocally(
737
+ spec.slug,
738
+ catalogEntry,
739
+ true,
740
+ spec.contactId,
607
741
  );
608
- }
609
742
 
610
- seedCatalogSkillMemories();
611
- return { success: true };
743
+ const skillDir = join(getWorkspaceSkillsDir(), spec.slug);
744
+ postInstallSkill(spec.slug, skillDir, ctx);
745
+ return { success: true };
746
+ }
747
+ } catch (err) {
748
+ // If catalog lookup/install fails, fall through to community registries
749
+ log.warn(
750
+ { err, skillId: spec.slug },
751
+ "Vellum catalog install failed, falling back to community registry",
752
+ );
612
753
  }
613
- } catch (err) {
614
- // If catalog lookup/install fails, fall through to clawhub
615
- log.warn(
616
- { err, skillId: spec.slug },
617
- "Vellum catalog install failed, falling back to community registry",
754
+
755
+ // skills.sh install path: route here when origin is explicitly "skillssh"
756
+ // or when the slug looks like a skills.sh multi-segment format (owner/repo/skill)
757
+ if (
758
+ spec.origin === "skillssh" ||
759
+ (spec.origin !== "clawhub" && looksLikeSkillsShSlug(spec.slug))
760
+ ) {
761
+ const resolved = resolveSkillSource(spec.slug);
762
+ await installExternalSkill(
763
+ resolved.owner,
764
+ resolved.repo,
765
+ resolved.skillSlug,
766
+ true /* overwrite */,
767
+ resolved.ref ?? spec.version,
768
+ spec.contactId,
618
769
  );
770
+
771
+ const skillDir = join(getWorkspaceSkillsDir(), resolved.skillSlug);
772
+ postInstallSkill(resolved.skillSlug, skillDir, ctx);
773
+ return { success: true };
619
774
  }
620
775
 
621
776
  // Install from clawhub (community)
622
- const result = await clawhubInstall(spec.slug, { version: spec.version });
777
+ const result = await clawhubInstall(spec.slug, {
778
+ version: spec.version,
779
+ contactId: spec.contactId,
780
+ });
623
781
  if (!result.success) {
624
782
  return { success: false, error: result.error ?? "Unknown error" };
625
783
  }
626
784
  const rawId = result.skillName ?? spec.slug;
627
785
  const skillId = rawId.includes("/") ? rawId.split("/").pop()! : rawId;
628
786
 
629
- // Reload skill catalog so the newly installed skill is picked up
630
- loadSkillCatalog();
631
-
632
- // Auto-enable the newly installed skill
633
- try {
634
- const raw = loadRawConfig();
635
- ensureSkillEntry(raw, skillId).enabled = true;
636
- saveConfigWithSuppression(raw, ctx);
637
- ctx.broadcast({
638
- type: "skills_state_changed",
639
- name: skillId,
640
- state: "enabled",
787
+ // clawhubInstall uses the clawhub CLI which doesn't handle bun install
788
+ // or SKILLS.md indexing, so we do those here before post-install.
789
+ const skillDir = join(getWorkspaceSkillsDir(), skillId);
790
+ if (existsSync(join(skillDir, "package.json"))) {
791
+ const bunPath = `${homedir()}/.bun/bin`;
792
+ execSync("bun install", {
793
+ cwd: skillDir,
794
+ stdio: "inherit",
795
+ env: { ...process.env, PATH: `${bunPath}:${process.env.PATH}` },
641
796
  });
642
- } catch (err) {
643
- log.warn({ err, skillId }, "Failed to auto-enable installed skill");
644
797
  }
798
+ upsertSkillsIndex(skillId);
645
799
 
646
- seedCatalogSkillMemories();
800
+ postInstallSkill(skillId, skillDir, ctx);
647
801
  return { success: true };
648
802
  } catch (err) {
649
803
  const message = err instanceof Error ? err.message : String(err);
@@ -693,7 +847,7 @@ export async function uninstallSkill(
693
847
  }
694
848
  // Best-effort cleanup of capability memory for uninstalled skill
695
849
  // (managed path handles this internally via deleteManagedSkill)
696
- deleteSkillCapabilityMemory(skillId);
850
+ deleteSkillCapabilityNode(skillId);
697
851
  }
698
852
 
699
853
  // Clean config entry
@@ -757,62 +911,112 @@ export async function searchSkills(
757
911
  query: string,
758
912
  _ctx: SkillOperationContext,
759
913
  ): Promise<
760
- { success: true; data: unknown } | { success: false; error: string }
914
+ | { success: true; skills: SlimSkillResponse[] }
915
+ | { success: false; error: string }
761
916
  > {
762
917
  try {
763
- // Search the loaded skill catalog (bundled + installed) for matches
918
+ // Search the loaded skill catalog (bundled + installed) for matches.
919
+ // Use resolveSkillStates + toSlimSkillResponse so that already-installed
920
+ // or bundled skills get their correct kind/origin/status instead of being
921
+ // hard-coded as catalog/available.
764
922
  const catalog = loadSkillCatalog();
923
+ const config = getConfig();
924
+ const resolved = resolveSkillStates(catalog, config);
925
+ const resolvedById = new Map(resolved.map((r) => [r.summary.id, r]));
926
+
765
927
  const catalogMatches = filterByQuery(catalog, query, [
766
928
  (s) => s.id,
767
929
  (s) => s.displayName,
768
930
  (s) => s.description,
769
931
  ]);
770
932
 
771
- // Shape that matches ClawhubSearchResultItem so the client
772
- // (Swift ClawhubSkillItem) can decode results uniformly.
773
- interface SearchItem {
774
- name: string;
775
- slug: string;
776
- description: string;
777
- author: string;
778
- stars: number;
779
- installs: number;
780
- version: string;
781
- createdAt: number;
782
- source: "vellum" | "clawhub";
783
- }
933
+ const catalogItems: SlimSkillResponse[] = catalogMatches.map((s) => {
934
+ const r = resolvedById.get(s.id);
935
+ if (r) {
936
+ return toSlimSkillResponse(r.summary, r.state);
937
+ }
938
+ // Fallback for catalog entries not in resolvedSkillStates (shouldn't
939
+ // normally happen, but defensive)
940
+ return {
941
+ id: s.id,
942
+ name: s.displayName,
943
+ description: s.description,
944
+ emoji: s.emoji,
945
+ kind: "catalog" as const,
946
+ origin: "vellum" as const,
947
+ status: "available" as const,
948
+ };
949
+ });
784
950
 
785
- const catalogItems: SearchItem[] = catalogMatches.map((s) => ({
786
- name: s.displayName,
787
- slug: s.id,
788
- description: s.description,
789
- author: "Vellum",
790
- stars: 0,
791
- installs: 0,
792
- version: "",
793
- createdAt: 0,
794
- source: "vellum" as const,
795
- }));
951
+ // Search both community registries in parallel (non-fatal on failure)
952
+ const [clawhubResult, skillsshResult] = await Promise.allSettled([
953
+ clawhubSearch(query),
954
+ searchSkillsRegistry(query, 25),
955
+ ]);
796
956
 
797
- // Search the community registry (non-fatal on failure)
798
- let communitySkills: SearchItem[] = [];
799
- try {
800
- const communityResult = await clawhubSearch(query);
801
- communitySkills = communityResult.skills;
802
- } catch (err) {
957
+ let clawhubSkills: SlimSkillResponse[] = [];
958
+ if (clawhubResult.status === "fulfilled") {
959
+ clawhubSkills = clawhubResult.value.skills.map((s) => ({
960
+ id: s.slug,
961
+ name: s.name,
962
+ description: s.description,
963
+ kind: "catalog" as const,
964
+ origin: "clawhub" as const,
965
+ status: "available" as const,
966
+ slug: s.slug,
967
+ author: s.author,
968
+ stars: s.stars,
969
+ installs: s.installs,
970
+ reports: 0,
971
+ publishedAt: s.createdAt
972
+ ? new Date(s.createdAt * 1000).toISOString()
973
+ : undefined,
974
+ }));
975
+ } else {
803
976
  log.warn(
804
- { err },
805
- "clawhub search failed, returning catalog-only results",
977
+ { err: clawhubResult.reason },
978
+ "clawhub search failed, continuing without clawhub results",
806
979
  );
807
980
  }
808
981
 
809
- // Deduplicate: catalog takes precedence when slugs collide
810
- const catalogSlugs = new Set(catalogItems.map((s) => s.slug));
811
- const deduped = communitySkills.filter((s) => !catalogSlugs.has(s.slug));
982
+ let skillsshSkills: SlimSkillResponse[] = [];
983
+ if (skillsshResult.status === "fulfilled") {
984
+ skillsshSkills = skillsshResult.value.map((r) => ({
985
+ id: r.id,
986
+ name: r.name,
987
+ description: "",
988
+ kind: "catalog" as const,
989
+ origin: "skillssh" as const,
990
+ status: "available" as const,
991
+ slug: r.id,
992
+ sourceRepo: r.source,
993
+ installs: r.installs,
994
+ }));
995
+ } else {
996
+ log.warn(
997
+ { err: skillsshResult.reason },
998
+ "skills.sh search failed, continuing without skills.sh results",
999
+ );
1000
+ }
1001
+
1002
+ // Deduplicate: catalog > clawhub > skills.sh (first occurrence wins)
1003
+ const seenSlugs = new Set(catalogItems.map((s) => s.id));
1004
+
1005
+ const dedupedClawhub = clawhubSkills.filter((s) => {
1006
+ if (seenSlugs.has(s.id)) return false;
1007
+ seenSlugs.add(s.id);
1008
+ return true;
1009
+ });
1010
+
1011
+ const dedupedSkillssh = skillsshSkills.filter((s) => {
1012
+ if (seenSlugs.has(s.id)) return false;
1013
+ seenSlugs.add(s.id);
1014
+ return true;
1015
+ });
812
1016
 
813
1017
  return {
814
1018
  success: true,
815
- data: { skills: [...catalogItems, ...deduped] },
1019
+ skills: [...catalogItems, ...dedupedClawhub, ...dedupedSkillssh],
816
1020
  };
817
1021
  } catch (err) {
818
1022
  const message = err instanceof Error ? err.message : String(err);
@@ -1002,6 +1206,7 @@ export interface CreateSkillParams {
1002
1206
  emoji?: string;
1003
1207
  bodyMarkdown: string;
1004
1208
  overwrite?: boolean;
1209
+ contactId?: string;
1005
1210
  }
1006
1211
 
1007
1212
  export async function createSkill(
@@ -1016,6 +1221,7 @@ export async function createSkill(
1016
1221
  emoji: params.emoji,
1017
1222
  bodyMarkdown: params.bodyMarkdown,
1018
1223
  overwrite: params.overwrite,
1224
+ contactId: params.contactId,
1019
1225
  });
1020
1226
 
1021
1227
  if (!result.created) {
@@ -1042,7 +1248,8 @@ export async function createSkill(
1042
1248
  );
1043
1249
  }
1044
1250
 
1045
- seedCatalogSkillMemories();
1251
+ seedSkillGraphNodes();
1252
+ void seedUninstalledCatalogSkillMemories().catch(() => {});
1046
1253
  return { success: true };
1047
1254
  } catch (err) {
1048
1255
  const message = err instanceof Error ? err.message : String(err);