@vellumai/assistant 0.6.0 → 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 (285) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  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/openapi.yaml +538 -3
  9. package/package.json +5 -1
  10. package/src/__tests__/anthropic-provider.test.ts +160 -95
  11. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +47 -1
  13. package/src/__tests__/app-source-watcher.test.ts +159 -0
  14. package/src/__tests__/checker.test.ts +38 -6
  15. package/src/__tests__/config-schema.test.ts +5 -0
  16. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  17. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  18. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  19. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  20. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  21. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  22. package/src/__tests__/conversation-wipe.test.ts +2 -6
  23. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  24. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  25. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  26. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  27. package/src/__tests__/date-context.test.ts +76 -210
  28. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  29. package/src/__tests__/file-list-tool.test.ts +219 -0
  30. package/src/__tests__/first-greeting.test.ts +1 -1
  31. package/src/__tests__/heartbeat-service.test.ts +180 -3
  32. package/src/__tests__/identity-routes.test.ts +328 -0
  33. package/src/__tests__/injection-block.test.ts +24 -0
  34. package/src/__tests__/install-skill-routing.test.ts +7 -6
  35. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  36. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  37. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  38. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  39. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  40. package/src/__tests__/log-export-workspace.test.ts +72 -105
  41. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  42. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  43. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  44. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  45. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  46. package/src/__tests__/mock-fetch.ts +87 -0
  47. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  48. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  49. package/src/__tests__/parser.test.ts +32 -0
  50. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  51. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  52. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  53. package/src/__tests__/permission-mode-store.test.ts +277 -0
  54. package/src/__tests__/permission-mode.test.ts +101 -0
  55. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  56. package/src/__tests__/profiler-routes.test.ts +502 -0
  57. package/src/__tests__/profiler-run-store.test.ts +441 -0
  58. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  59. package/src/__tests__/registry.test.ts +1 -1
  60. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  61. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  62. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  63. package/src/__tests__/search-skills-unified.test.ts +4 -3
  64. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  65. package/src/__tests__/set-permission-mode.test.ts +274 -0
  66. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  67. package/src/__tests__/skill-memory.test.ts +2 -783
  68. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  69. package/src/__tests__/subagent-detail.test.ts +84 -0
  70. package/src/__tests__/subagent-disposal.test.ts +308 -0
  71. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  72. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  73. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  74. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  75. package/src/__tests__/subagent-tools.test.ts +464 -4
  76. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  77. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  78. package/src/__tests__/terminal-tools.test.ts +17 -27
  79. package/src/__tests__/test-preload.ts +4 -0
  80. package/src/__tests__/tool-executor.test.ts +4 -26
  81. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  82. package/src/__tests__/top-level-renderer.test.ts +10 -13
  83. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  84. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  85. package/src/agent/loop.ts +6 -0
  86. package/src/approvals/guardian-request-resolvers.ts +24 -0
  87. package/src/avatar/traits-png-sync.ts +3 -3
  88. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  89. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  90. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  91. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  92. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  93. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  94. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  95. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  96. package/src/cli/commands/conversations.ts +1 -8
  97. package/src/cli/commands/email.ts +584 -835
  98. package/src/cli/commands/memory.ts +1 -34
  99. package/src/cli/commands/notifications.ts +7 -2
  100. package/src/cli/commands/oauth/connect.ts +14 -5
  101. package/src/cli/commands/routes.ts +396 -0
  102. package/src/cli/commands/skills.ts +130 -20
  103. package/src/cli/program.ts +2 -0
  104. package/src/cli.ts +1 -120
  105. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  106. package/src/config/bundled-skills/gmail/SKILL.md +2 -2
  107. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  108. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  109. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  111. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  112. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  113. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  114. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  115. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  116. package/src/config/env-registry.ts +63 -0
  117. package/src/config/feature-flag-registry.json +17 -1
  118. package/src/config/schema.ts +8 -0
  119. package/src/config/schemas/filing.ts +51 -0
  120. package/src/config/schemas/heartbeat.ts +15 -12
  121. package/src/config/schemas/memory-lifecycle.ts +12 -0
  122. package/src/config/schemas/security.ts +14 -0
  123. package/src/daemon/app-source-watcher.ts +93 -0
  124. package/src/daemon/config-watcher.ts +79 -1
  125. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  126. package/src/daemon/conversation-agent-loop.ts +158 -65
  127. package/src/daemon/conversation-history.ts +4 -19
  128. package/src/daemon/conversation-lifecycle.ts +8 -14
  129. package/src/daemon/conversation-process.ts +13 -7
  130. package/src/daemon/conversation-runtime-assembly.ts +300 -306
  131. package/src/daemon/conversation-tool-setup.ts +44 -14
  132. package/src/daemon/conversation-workspace.ts +1 -2
  133. package/src/daemon/conversation.ts +18 -0
  134. package/src/daemon/date-context.ts +26 -53
  135. package/src/daemon/first-greeting.ts +1 -1
  136. package/src/daemon/handlers/conversations.ts +4 -7
  137. package/src/daemon/handlers/shared.test.ts +143 -0
  138. package/src/daemon/handlers/shared.ts +63 -5
  139. package/src/daemon/handlers/skills.ts +11 -18
  140. package/src/daemon/lifecycle.ts +199 -157
  141. package/src/daemon/message-types/conversations.ts +25 -6
  142. package/src/daemon/message-types/messages.ts +9 -1
  143. package/src/daemon/message-types/schedules.ts +1 -0
  144. package/src/daemon/message-types/settings.ts +6 -0
  145. package/src/daemon/profiler-run-store.ts +557 -0
  146. package/src/daemon/server.ts +89 -9
  147. package/src/daemon/shutdown-handlers.ts +5 -0
  148. package/src/daemon/tool-side-effects.ts +23 -3
  149. package/src/export/transcript-formatter.ts +148 -0
  150. package/src/filing/filing-service.ts +228 -0
  151. package/src/heartbeat/heartbeat-service.ts +96 -7
  152. package/src/mcp/client.ts +6 -0
  153. package/src/mcp/mcp-oauth-provider.ts +149 -27
  154. package/src/memory/admin.ts +33 -32
  155. package/src/memory/app-store.ts +69 -0
  156. package/src/memory/conversation-bootstrap.ts +1 -1
  157. package/src/memory/conversation-crud.ts +136 -107
  158. package/src/memory/conversation-group-migration.ts +1 -1
  159. package/src/memory/conversation-queries.ts +58 -12
  160. package/src/memory/conversation-title-service.ts +1 -0
  161. package/src/memory/db-init.ts +182 -376
  162. package/src/memory/graph/bootstrap.ts +75 -66
  163. package/src/memory/graph/capability-seed.ts +167 -15
  164. package/src/memory/graph/consolidation.ts +38 -4
  165. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  166. package/src/memory/graph/extraction-job.ts +9 -4
  167. package/src/memory/graph/extraction.ts +66 -23
  168. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  169. package/src/memory/graph/graph-search.ts +29 -15
  170. package/src/memory/graph/injection.ts +38 -8
  171. package/src/memory/graph/inspect.ts +12 -3
  172. package/src/memory/graph/retriever.ts +365 -262
  173. package/src/memory/graph/store.test.ts +48 -0
  174. package/src/memory/graph/store.ts +150 -11
  175. package/src/memory/graph/tool-handlers.ts +84 -209
  176. package/src/memory/graph/tools.ts +8 -52
  177. package/src/memory/graph/types.ts +24 -0
  178. package/src/memory/job-handlers/cleanup.ts +44 -1
  179. package/src/memory/jobs-store.ts +70 -60
  180. package/src/memory/jobs-worker.ts +44 -28
  181. package/src/memory/llm-request-log-store.ts +96 -12
  182. package/src/memory/memory-recall-log-store.ts +49 -5
  183. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  184. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  185. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  186. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  187. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  188. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  189. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  190. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  191. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  192. package/src/memory/migrations/index.ts +8 -0
  193. package/src/memory/migrations/registry.ts +8 -0
  194. package/src/memory/schema/conversations.ts +14 -0
  195. package/src/memory/schema/infrastructure.ts +8 -1
  196. package/src/memory/schema/memory-core.ts +0 -51
  197. package/src/memory/schema/memory-graph.ts +15 -0
  198. package/src/memory/task-memory-cleanup.ts +30 -11
  199. package/src/notifications/copy-composer.ts +86 -0
  200. package/src/notifications/decision-engine.ts +35 -0
  201. package/src/permissions/checker.ts +12 -1
  202. package/src/permissions/permission-mode-store.ts +180 -0
  203. package/src/permissions/permission-mode.ts +31 -0
  204. package/src/permissions/workspace-policy.ts +9 -0
  205. package/src/prompts/system-prompt.ts +59 -7
  206. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  207. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  208. package/src/prompts/templates/HEARTBEAT.md +3 -1
  209. package/src/prompts/templates/SOUL.md +25 -4
  210. package/src/prompts/templates/UPDATES.md +8 -0
  211. package/src/providers/anthropic/client.ts +107 -219
  212. package/src/runtime/auth/route-policy.ts +23 -0
  213. package/src/runtime/http-server.ts +32 -2
  214. package/src/runtime/http-types.ts +12 -1
  215. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  216. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  217. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  218. package/src/runtime/routes/app-management-routes.ts +1 -11
  219. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  220. package/src/runtime/routes/archive-utils.ts +29 -0
  221. package/src/runtime/routes/avatar-routes.ts +2 -9
  222. package/src/runtime/routes/btw-routes.ts +14 -1
  223. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  224. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  225. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  226. package/src/runtime/routes/conversation-routes.ts +264 -44
  227. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  228. package/src/runtime/routes/identity-routes.ts +53 -18
  229. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  230. package/src/runtime/routes/log-export-routes.ts +23 -275
  231. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  232. package/src/runtime/routes/migration-routes.ts +18 -7
  233. package/src/runtime/routes/profiler-routes.ts +350 -0
  234. package/src/runtime/routes/schedule-routes.ts +27 -12
  235. package/src/runtime/routes/settings-routes.ts +95 -8
  236. package/src/runtime/routes/subagents-routes.ts +28 -7
  237. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  238. package/src/runtime/routes/user-routes.ts +41 -0
  239. package/src/runtime/routes/workspace-routes.ts +0 -1
  240. package/src/schedule/schedule-store.ts +30 -0
  241. package/src/schedule/scheduler.ts +45 -18
  242. package/src/skills/catalog-install.ts +10 -2
  243. package/src/skills/managed-store.ts +2 -2
  244. package/src/skills/skill-memory.ts +1 -293
  245. package/src/subagent/index.ts +13 -3
  246. package/src/subagent/manager.ts +308 -29
  247. package/src/subagent/types.ts +68 -0
  248. package/src/tasks/task-runner.ts +4 -4
  249. package/src/tools/apps/executors.ts +29 -4
  250. package/src/tools/filesystem/list.ts +93 -0
  251. package/src/tools/permission-checker.ts +78 -0
  252. package/src/tools/registry.ts +4 -0
  253. package/src/tools/schedule/create.ts +3 -0
  254. package/src/tools/schedule/list.ts +1 -0
  255. package/src/tools/schedule/update.ts +6 -0
  256. package/src/tools/shared/filesystem/errors.ts +5 -0
  257. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  258. package/src/tools/shared/filesystem/types.ts +17 -0
  259. package/src/tools/shared/shell-output.ts +31 -2
  260. package/src/tools/subagent/abort.ts +12 -2
  261. package/src/tools/subagent/message.ts +9 -2
  262. package/src/tools/subagent/notify-parent.ts +79 -0
  263. package/src/tools/subagent/read.ts +29 -8
  264. package/src/tools/subagent/resolve.ts +21 -0
  265. package/src/tools/subagent/spawn.ts +2 -0
  266. package/src/tools/subagent/status.ts +11 -1
  267. package/src/tools/system/avatar-generator.ts +3 -3
  268. package/src/tools/system/register.ts +23 -0
  269. package/src/tools/system/set-permission-mode.ts +103 -0
  270. package/src/tools/terminal/parser.ts +30 -5
  271. package/src/tools/terminal/safe-env.ts +16 -1
  272. package/src/tools/tool-manifest.ts +6 -0
  273. package/src/tools/types.ts +2 -0
  274. package/src/util/logger.ts +1 -1
  275. package/src/util/platform.ts +50 -17
  276. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  277. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  278. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  279. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  280. package/src/workspace/migrations/registry.ts +4 -0
  281. package/src/workspace/top-level-renderer.ts +5 -9
  282. package/src/__tests__/cli-memory.test.ts +0 -377
  283. package/src/__tests__/clipboard.test.ts +0 -88
  284. package/src/cli/cli-memory.ts +0 -179
  285. package/src/util/clipboard.ts +0 -34
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { isHttpAuthDisabled } from "../config/env.js";
10
+ import { getIsPlatform } from "../config/env-registry.js";
10
11
  import type { CesClient } from "../credential-execution/client.js";
11
12
  import { getBindingByConversation } from "../memory/external-conversation-store.js";
12
13
  import {
@@ -192,6 +193,7 @@ export function createToolExecutor(
192
193
  toolUseId,
193
194
  hostBashProxy: ctx.hostBashProxy,
194
195
  hostFileProxy: ctx.hostFileProxy,
196
+ isPlatformHosted: getIsPlatform(),
195
197
  cesClient: ctx.cesClient,
196
198
  onToolLifecycleEvent: handleToolLifecycleEvent,
197
199
  sendToClient: (msg) => {
@@ -320,8 +322,7 @@ export function createProxyApprovalCallback(
320
322
  input.matching_patterns = decision.matchingPatterns;
321
323
  }
322
324
 
323
- const riskLevel =
324
- decision.kind === "ask_missing_credential" ? "high" : "medium";
325
+ const riskLevel: string = "medium";
325
326
 
326
327
  // Check trust store before prompting — build candidates that mirror
327
328
  // buildCommandCandidates() in checker.ts for network_request.
@@ -342,10 +343,7 @@ export function createProxyApprovalCallback(
342
343
  );
343
344
  if (existingRule && existingRule.decision !== "ask") {
344
345
  if (existingRule.decision === "deny") return false;
345
- // For high-risk proxy decisions, a plain allow rule (without allowHighRisk)
346
- // must fall through to prompting — mirroring the checker's behavior.
347
- if (riskLevel !== "high" || existingRule.allowHighRisk === true)
348
- return true;
346
+ return true;
349
347
  }
350
348
 
351
349
  // Use the checker's built-in allowlist generation for network_request
@@ -384,12 +382,13 @@ export function createProxyApprovalCallback(
384
382
  response.selectedPattern &&
385
383
  response.selectedScope
386
384
  ) {
385
+ const allowHighRisk = response.decision === "always_allow_high_risk";
387
386
  log.info(
388
387
  {
389
388
  toolName,
390
389
  pattern: response.selectedPattern,
391
390
  scope: response.selectedScope,
392
- highRisk: response.decision === "always_allow_high_risk",
391
+ allowHighRisk,
393
392
  },
394
393
  "Persisting always-allow trust rule (proxy)",
395
394
  );
@@ -399,9 +398,7 @@ export function createProxyApprovalCallback(
399
398
  response.selectedScope,
400
399
  "allow",
401
400
  100,
402
- response.decision === "always_allow_high_risk"
403
- ? { allowHighRisk: true }
404
- : undefined,
401
+ allowHighRisk ? { allowHighRisk: true } : undefined,
405
402
  );
406
403
  }
407
404
  if (
@@ -436,7 +433,7 @@ export function createProxyApprovalCallback(
436
433
  * history or explicit preactivation. Without this, their tools are
437
434
  * unavailable in fresh conversations until `skill_load` is called.
438
435
  */
439
- const DEFAULT_PREACTIVATED_SKILL_IDS = ["tasks", "notifications"];
436
+ const DEFAULT_PREACTIVATED_SKILL_IDS = ["tasks", "notifications", "subagent"];
440
437
 
441
438
  /**
442
439
  * Subset of Conversation state that the resolveTools callback reads at each
@@ -454,9 +451,14 @@ export interface SkillProjectionContext {
454
451
  readonly channelCapabilities?: {
455
452
  channel: string;
456
453
  supportsDynamicUi: boolean;
454
+ clientOS?: string;
457
455
  };
458
456
  /** True when no client is connected (HTTP-only). */
459
457
  readonly hasNoClient?: boolean;
458
+ /** When set, only tools in this set are included in the resolved tool list (subagent delegation). */
459
+ subagentAllowedTools?: Set<string>;
460
+ /** True when this conversation belongs to a subagent spawned by SubagentManager. */
461
+ readonly isSubagent?: boolean;
460
462
  }
461
463
 
462
464
  // ── Conditional tool sets ────────────────────────────────────────────
@@ -471,6 +473,16 @@ const HOST_TOOL_NAMES = new Set([
471
473
  const CLIENT_CAPABILITY_TOOL_NAMES = new Set(["app_open"]);
472
474
  const PLATFORM_TOOL_NAMES = new Set(["request_system_permission"]);
473
475
 
476
+ /**
477
+ * Tools that should only be visible to subagent conversations. Main (parent)
478
+ * conversations never see these in the LLM tool definitions. Subsequent PRs
479
+ * will populate this set; it starts empty so there is no behavioral change.
480
+ */
481
+ export const SUBAGENT_ONLY_TOOL_NAMES = new Set<string>([
482
+ "file_list",
483
+ "notify_parent",
484
+ ]);
485
+
474
486
  /**
475
487
  * Determine whether a tool should be included in the LLM tool definitions
476
488
  * for the current turn based on conversation context. Tools not active for the
@@ -495,7 +507,12 @@ export function isToolActiveForContext(
495
507
  return !ctx.hasNoClient;
496
508
  }
497
509
  if (PLATFORM_TOOL_NAMES.has(name)) {
498
- return process.platform === "darwin" && !ctx.hasNoClient;
510
+ // Check the *client's* platform, not the daemon's process.platform.
511
+ // In Docker the daemon runs on Linux but the connected client may be macOS.
512
+ return ctx.channelCapabilities?.clientOS === "macos" && !ctx.hasNoClient;
513
+ }
514
+ if (SUBAGENT_ONLY_TOOL_NAMES.has(name)) {
515
+ return ctx.isSubagent === true;
499
516
  }
500
517
  return true;
501
518
  }
@@ -548,18 +565,27 @@ export function createResolveToolsCallback(
548
565
  isToolActiveForContext(d.name, ctx),
549
566
  );
550
567
 
568
+ // When the conversation is acting as a subagent, restrict core tools to
569
+ // only those explicitly allowed by the parent orchestrator.
570
+ const scopedCoreDefs = ctx.subagentAllowedTools
571
+ ? filteredCoreDefs.filter((d) => ctx.subagentAllowedTools!.has(d.name))
572
+ : filteredCoreDefs;
573
+
551
574
  // Re-read MCP tool definitions from the registry each turn so conversations
552
575
  // automatically pick up tools added/removed by `vellum mcp reload`.
553
576
  const currentMcpDefs = getMcpToolDefinitions();
554
577
  log.debug(
555
578
  {
556
- coreCount: filteredCoreDefs.length,
579
+ coreCount: scopedCoreDefs.length,
557
580
  mcpCount: currentMcpDefs.length,
558
581
  mcpTools: currentMcpDefs.map((d) => d.name),
559
582
  },
560
583
  "MCP tools resolved for turn",
561
584
  );
562
- const allBaseDefs = [...filteredCoreDefs, ...currentMcpDefs];
585
+ const scopedMcpDefs = ctx.subagentAllowedTools
586
+ ? currentMcpDefs.filter((d) => ctx.subagentAllowedTools!.has(d.name))
587
+ : currentMcpDefs;
588
+ const allBaseDefs = [...scopedCoreDefs, ...scopedMcpDefs];
563
589
 
564
590
  const effectivePreactivated = [
565
591
  ...DEFAULT_PREACTIVATED_SKILL_IDS,
@@ -572,6 +598,10 @@ export function createResolveToolsCallback(
572
598
  });
573
599
  const turnAllowed = new Set(allBaseDefs.map((d) => d.name));
574
600
  for (const name of projection.allowedToolNames) {
601
+ // When a subagent allowlist is active, exclude skill tools not on it.
602
+ if (ctx.subagentAllowedTools && !ctx.subagentAllowedTools.has(name)) {
603
+ continue;
604
+ }
575
605
  turnAllowed.add(name);
576
606
  }
577
607
  ctx.allowedToolNames = turnAllowed;
@@ -33,8 +33,7 @@ export function refreshWorkspaceTopLevelContextIfNeeded(
33
33
  currentConversationPath = `conversations/${resolvedDirName}/`;
34
34
  }
35
35
  ctx.workspaceTopLevelContext = renderWorkspaceTopLevelContext(snapshot, {
36
- currentConversationPath,
37
- currentConversationAttachmentsPath: currentConversationPath
36
+ conversationAttachmentsPath: currentConversationPath
38
37
  ? `${currentConversationPath}attachments/`
39
38
  : null,
40
39
  });
@@ -160,6 +160,7 @@ export class Conversation {
160
160
  /** @internal */ allowedToolNames?: Set<string>;
161
161
  /** @internal */ toolsDisabledDepth = 0;
162
162
  /** @internal */ preactivatedSkillIds?: string[];
163
+ /** @internal */ subagentAllowedTools?: Set<string>;
163
164
  /** @internal */ coreToolNames: Set<string>;
164
165
  /** @internal */ readonly skillProjectionState = new Map<string, string>();
165
166
  /** @internal */ readonly skillProjectionCache: SkillProjectionCache = {};
@@ -174,6 +175,7 @@ export class Conversation {
174
175
  /** @internal */ contextCompactedAt: number | null = null;
175
176
  /** @internal */ currentRequestId?: string;
176
177
  /** @internal */ hasNoClient = false;
178
+ /** @internal */ isSubagent = false;
177
179
  /** @internal */ headlessLock = false;
178
180
  /** @internal */ taskRunId?: string;
179
181
  /** @internal */ callSessionId?: string;
@@ -248,6 +250,8 @@ export class Conversation {
248
250
  public readonly hasSystemPromptOverride: boolean;
249
251
  public memoryPolicy: ConversationMemoryPolicy;
250
252
  /** @internal */ readonly graphMemory: ConversationGraphMemory;
253
+ /** @internal */ activeContextNodeIds?: string[];
254
+ /** @internal */ memoryScopeId?: string;
251
255
  /** @internal */ streamThinking: boolean;
252
256
  /** @internal */ turnCount = 0;
253
257
  public lastAssistantAttachments: AssistantAttachmentDraft[] = [];
@@ -277,6 +281,7 @@ export class Conversation {
277
281
  memoryPolicy?: ConversationMemoryPolicy,
278
282
  sharedCesClient?: CesClient,
279
283
  speedOverride?: Speed,
284
+ cacheTtl?: "5m" | "1h",
280
285
  ) {
281
286
  this.conversationId = conversationId;
282
287
  this.systemPrompt = systemPrompt;
@@ -415,6 +420,7 @@ export class Conversation {
415
420
  ...(fastModeEnabled && resolvedSpeed === "fast"
416
421
  ? { speed: resolvedSpeed }
417
422
  : {}),
423
+ ...(cacheTtl ? { cacheTtl } : {}),
418
424
  },
419
425
  toolDefs.length > 0 ? toolDefs : undefined,
420
426
  toolDefs.length > 0 ? toolExecutor : undefined,
@@ -439,6 +445,7 @@ export class Conversation {
439
445
  async loadFromDb(): Promise<void> {
440
446
  await loadFromDbImpl(this);
441
447
  this.restoreSurfaceStateFromHistory();
448
+ this.graphMemory.restoreState();
442
449
  }
443
450
 
444
451
  /**
@@ -537,6 +544,14 @@ export class Conversation {
537
544
  this.sandboxOverride = enabled;
538
545
  }
539
546
 
547
+ setSubagentAllowedTools(tools: Set<string> | undefined): void {
548
+ this.subagentAllowedTools = tools;
549
+ }
550
+
551
+ setIsSubagent(value: boolean): void {
552
+ this.isSubagent = value;
553
+ }
554
+
540
555
  isProcessing(): boolean {
541
556
  return this.processing;
542
557
  }
@@ -564,6 +579,9 @@ export class Conversation {
564
579
  // CES client is owned by DaemonServer — just drop the reference.
565
580
  // Do NOT close it here; the server manages the CES lifecycle.
566
581
  this.cesClient = undefined;
582
+ this.activeContextNodeIds = this.graphMemory.tracker.getActiveNodeIds();
583
+ this.memoryScopeId = this.memoryPolicy.scopeId;
584
+ this.graphMemory.persistState();
567
585
  disposeConversation(this);
568
586
  }
569
587
 
@@ -290,31 +290,14 @@ function formatLocalDate(date: Date, timeZone: string): string {
290
290
  }
291
291
 
292
292
  /**
293
- * Format HH:MM and UTC offset for the given instant in the given timezone.
294
- */
295
- function formatCompactTimeAndOffset(
296
- date: Date,
297
- timeZone: string,
298
- ): { time: string; offset: string } {
299
- const fmt = new Intl.DateTimeFormat("en-US", {
300
- timeZone,
301
- hour: "2-digit",
302
- minute: "2-digit",
303
- hourCycle: "h23",
304
- timeZoneName: "shortOffset",
305
- });
306
- const parts = fmt.formatToParts(date);
307
- const get = (t: string) => parts.find((p) => p.type === t)?.value ?? "";
308
- const hour = get("hour");
309
- const minute = get("minute");
310
- const offset = normalizeOffsetToken(get("timeZoneName"));
311
- return { time: `${hour}:${minute}`, offset };
312
- }
313
-
314
- /**
315
- * Build a compact temporal context string for model injection.
293
+ * Format time as HH:MM:SS with UTC offset and timezone name.
294
+ *
295
+ * Uses the timezone resolution cascade:
296
+ * explicit override → configured user tz → profile user tz → host fallback.
297
+ *
298
+ * Returns format: `2026-04-02 (Thu) 01:52:33 -05:00 (America/Chicago)`
316
299
  */
317
- export function buildTemporalContext(
300
+ export function formatTurnTimestamp(
318
301
  options: TemporalContextOptions = {},
319
302
  ): string {
320
303
  const now = new Date(options.nowMs ?? Date.now());
@@ -336,36 +319,26 @@ export function buildTemporalContext(
336
319
  resolvedConfiguredUserTimeZone ??
337
320
  resolvedUserTimeZone ??
338
321
  resolvedHostTimeZone;
339
- const userTimeZone = resolvedConfiguredUserTimeZone ?? resolvedUserTimeZone;
340
- const timeZoneSource = resolvedTimeZone
341
- ? "explicit_override"
342
- : resolvedConfiguredUserTimeZone
343
- ? "user_settings"
344
- : resolvedUserTimeZone
345
- ? "user_profile_memory"
346
- : "assistant_host_fallback";
347
- const todayParts = localDateParts(now, timeZone);
348
- const todayStr = formatLocalDate(now, timeZone);
349
- const todayWeekday = WEEKDAY_SHORT[todayParts.weekday];
350
- const { time, offset } = formatCompactTimeAndOffset(now, timeZone);
351
-
352
- const tzSuffix =
353
- timeZoneSource === "assistant_host_fallback" ? " (host fallback)" : "";
354
322
 
355
- const lines = [
356
- `<temporal_context>`,
357
- `Today: ${todayStr} (${todayWeekday}) ${time} ${offset}`,
358
- `TZ: ${timeZone}${tzSuffix}`,
359
- ];
360
-
361
- if (userTimeZone && userTimeZone !== timeZone) {
362
- lines.push(`User TZ: ${userTimeZone}`);
363
- }
364
- if (resolvedHostTimeZone !== timeZone) {
365
- lines.push(`Host TZ: ${resolvedHostTimeZone}`);
366
- }
323
+ const dateStr = formatLocalDate(now, timeZone);
324
+ const todayParts = localDateParts(now, timeZone);
325
+ const dayName = WEEKDAY_SHORT[todayParts.weekday];
367
326
 
368
- lines.push(`</temporal_context>`);
327
+ const fmt = new Intl.DateTimeFormat("en-US", {
328
+ timeZone,
329
+ hour: "2-digit",
330
+ minute: "2-digit",
331
+ second: "2-digit",
332
+ hourCycle: "h23",
333
+ timeZoneName: "shortOffset",
334
+ });
335
+ const parts = fmt.formatToParts(now);
336
+ const get = (t: string) => parts.find((p) => p.type === t)?.value ?? "";
337
+ const hour = get("hour");
338
+ const minute = get("minute");
339
+ const second = get("second");
340
+ const offset = normalizeOffsetToken(get("timeZoneName"));
369
341
 
370
- return lines.join("\n");
342
+ return `${dateStr} (${dayName}) ${hour}:${minute}:${second} ${offset} (${timeZone})`;
371
343
  }
344
+
@@ -8,7 +8,7 @@ import { getWorkspacePromptPath } from "../util/platform.js";
8
8
  * time," "I'm ready to be useful," and "you're in control."
9
9
  */
10
10
  export const CANNED_FIRST_GREETING =
11
- "Hey. I'm brand new, no name, no memories, nothing yet. The more we work together, the more context and memory I build, and the better I get. But let's not wait around. Throw a question at me, give me a task, or ask what I can do.";
11
+ "Hey, I'm brand new. No name, no memories, nothing yet. Think of me like a new colleague on their first day: I'll get better the more we work together. First things first, let's figure out how we work best. What should I call you?";
12
12
 
13
13
  /**
14
14
  * Returns `true` when all of the following are true:
@@ -103,13 +103,10 @@ export function makeEventSender(params: {
103
103
  guardianPrincipalId: trustContext?.guardianPrincipalId ?? undefined,
104
104
  toolName: event.toolName,
105
105
  commandPreview:
106
- redactSecrets(
107
- summarizeToolInput(event.toolName, inputRecord),
108
- ) || undefined,
106
+ redactSecrets(summarizeToolInput(event.toolName, inputRecord)) ||
107
+ undefined,
109
108
  riskLevel: event.riskLevel,
110
- activityText: activityRaw
111
- ? redactSecrets(activityRaw)
112
- : undefined,
109
+ activityText: activityRaw ? redactSecrets(activityRaw) : undefined,
113
110
  executionTarget: event.executionTarget,
114
111
  status: "pending",
115
112
  requestCode: generateCanonicalRequestCode(),
@@ -304,7 +301,7 @@ export async function handleConversationCreate(
304
301
  // Only create the host bash proxy for desktop client interfaces that can
305
302
  // execute commands on the user's machine. Set before updateClient so
306
303
  // updateClient's call to hostBashProxy.updateSender targets the new proxy.
307
- if (transportInterface === "macos" || transportInterface === "ios") {
304
+ if (transportInterface === "macos") {
308
305
  const proxy = new HostBashProxy(sendEvent, (requestId) => {
309
306
  pendingInteractions.resolve(requestId);
310
307
  });
@@ -0,0 +1,143 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { compareSemver } from "./shared.js";
4
+
5
+ describe("compareSemver", () => {
6
+ // ── Basic numeric comparison ──────────────────────────────────────
7
+ test("equal versions return 0", () => {
8
+ expect(compareSemver("1.2.3", "1.2.3")).toBe(0);
9
+ });
10
+
11
+ test("higher major returns positive", () => {
12
+ expect(compareSemver("2.0.0", "1.0.0")).toBeGreaterThan(0);
13
+ });
14
+
15
+ test("lower major returns negative", () => {
16
+ expect(compareSemver("1.0.0", "2.0.0")).toBeLessThan(0);
17
+ });
18
+
19
+ test("higher minor returns positive", () => {
20
+ expect(compareSemver("1.3.0", "1.2.0")).toBeGreaterThan(0);
21
+ });
22
+
23
+ test("higher patch returns positive", () => {
24
+ expect(compareSemver("1.2.4", "1.2.3")).toBeGreaterThan(0);
25
+ });
26
+
27
+ // ── v/V prefix handling ───────────────────────────────────────────
28
+ test("strips v prefix", () => {
29
+ expect(compareSemver("v1.2.3", "1.2.3")).toBe(0);
30
+ });
31
+
32
+ test("strips V prefix", () => {
33
+ expect(compareSemver("V1.2.3", "1.2.3")).toBe(0);
34
+ });
35
+
36
+ test("compares with mixed v prefix", () => {
37
+ expect(compareSemver("v2.0.0", "1.0.0")).toBeGreaterThan(0);
38
+ });
39
+
40
+ // ── Pre-release vs release ────────────────────────────────────────
41
+ test("pre-release sorts lower than release", () => {
42
+ expect(compareSemver("0.6.0-staging.1", "0.6.0")).toBeLessThan(0);
43
+ });
44
+
45
+ test("release sorts higher than pre-release", () => {
46
+ expect(compareSemver("0.6.0", "0.6.0-staging.1")).toBeGreaterThan(0);
47
+ });
48
+
49
+ test("both without pre-release and same version return 0", () => {
50
+ expect(compareSemver("0.6.0", "0.6.0")).toBe(0);
51
+ });
52
+
53
+ // ── Pre-release numeric comparison ────────────────────────────────
54
+ test("staging.1 < staging.2", () => {
55
+ expect(compareSemver("0.6.0-staging.1", "0.6.0-staging.2")).toBeLessThan(0);
56
+ });
57
+
58
+ test("staging.10 > staging.2 (numeric, not lexical)", () => {
59
+ expect(
60
+ compareSemver("0.6.0-staging.10", "0.6.0-staging.2"),
61
+ ).toBeGreaterThan(0);
62
+ });
63
+
64
+ test("equal pre-release returns 0", () => {
65
+ expect(compareSemver("0.6.0-staging.5", "0.6.0-staging.5")).toBe(0);
66
+ });
67
+
68
+ // ── Pre-release lexical comparison ────────────────────────────────
69
+ test("alpha < beta (lexical)", () => {
70
+ expect(compareSemver("1.0.0-alpha", "1.0.0-beta")).toBeLessThan(0);
71
+ });
72
+
73
+ test("rc < staging (lexical)", () => {
74
+ expect(compareSemver("1.0.0-rc", "1.0.0-staging")).toBeLessThan(0);
75
+ });
76
+
77
+ // ── Pre-release fewer identifiers sorts earlier ───────────────────
78
+ test("fewer pre-release identifiers sorts earlier", () => {
79
+ expect(compareSemver("1.0.0-alpha", "1.0.0-alpha.1")).toBeLessThan(0);
80
+ });
81
+
82
+ // ── Mixed numeric vs non-numeric per §11.4.4 ─────────────────────
83
+ test("numeric identifier sorts lower than non-numeric", () => {
84
+ expect(compareSemver("1.0.0-1", "1.0.0-alpha")).toBeLessThan(0);
85
+ });
86
+
87
+ test("non-numeric identifier sorts higher than numeric", () => {
88
+ expect(compareSemver("1.0.0-alpha", "1.0.0-1")).toBeGreaterThan(0);
89
+ });
90
+
91
+ // ── Multi-segment pre-release ─────────────────────────────────────
92
+ test("multi-segment pre-release comparison", () => {
93
+ expect(
94
+ compareSemver("1.0.0-alpha.beta.1", "1.0.0-alpha.beta.2"),
95
+ ).toBeLessThan(0);
96
+ });
97
+
98
+ // ── Hyphenated pre-release identifiers ────────────────────────────
99
+ test("pre-release with multiple hyphens", () => {
100
+ expect(
101
+ compareSemver("1.0.0-pre-release-1", "1.0.0-pre-release-2"),
102
+ ).toBeLessThan(0);
103
+ });
104
+
105
+ // ── Different major.minor.patch trumps pre-release ────────────────
106
+ test("higher patch wins regardless of pre-release", () => {
107
+ expect(compareSemver("0.6.1", "0.6.0-staging.99")).toBeGreaterThan(0);
108
+ });
109
+
110
+ test("lower patch loses regardless of pre-release absence", () => {
111
+ expect(compareSemver("0.5.9", "0.6.0-staging.1")).toBeLessThan(0);
112
+ });
113
+
114
+ // ── Edge cases ────────────────────────────────────────────────────
115
+ test("missing segments default to 0", () => {
116
+ expect(compareSemver("1", "1.0.0")).toBe(0);
117
+ });
118
+
119
+ test("two-segment version", () => {
120
+ expect(compareSemver("1.2", "1.2.0")).toBe(0);
121
+ });
122
+
123
+ // ── Sort integration ──────────────────────────────────────────────
124
+ test("Array.sort produces correct semver order", () => {
125
+ const versions = [
126
+ "0.6.0",
127
+ "0.6.0-staging.2",
128
+ "0.5.9",
129
+ "0.6.0-staging.10",
130
+ "v0.6.0-staging.1",
131
+ "0.6.1",
132
+ ];
133
+ const sorted = [...versions].sort(compareSemver);
134
+ expect(sorted).toEqual([
135
+ "0.5.9",
136
+ "v0.6.0-staging.1",
137
+ "0.6.0-staging.2",
138
+ "0.6.0-staging.10",
139
+ "0.6.0",
140
+ "0.6.1",
141
+ ]);
142
+ });
143
+ });
@@ -497,13 +497,71 @@ export function ensureSkillEntry(
497
497
  return entries[name] as Record<string, unknown>;
498
498
  }
499
499
 
500
- /** Compare two semver strings. Returns negative if a < b, 0 if equal, positive if a > b. */
500
+ /**
501
+ * Parse a version string into its core numeric parts and optional pre-release tag.
502
+ * Handles optional `v`/`V` prefix (e.g. "v0.6.0-staging.5").
503
+ */
504
+ function parseSemverParts(v: string): {
505
+ nums: [number, number, number];
506
+ pre: string | null;
507
+ } {
508
+ const stripped = v.replace(/^[vV]/, "");
509
+ const [core, ...rest] = stripped.split("-");
510
+ const pre = rest.length > 0 ? rest.join("-") : null;
511
+ const segs = (core ?? "").split(".").map(Number);
512
+ return {
513
+ nums: [segs[0] || 0, segs[1] || 0, segs[2] || 0],
514
+ pre,
515
+ };
516
+ }
517
+
518
+ /**
519
+ * Compare two pre-release strings per semver §11:
520
+ * - Dot-separated identifiers compared left to right.
521
+ * - Both numeric → compare as integers.
522
+ * - Both non-numeric → compare lexically.
523
+ * - Numeric vs non-numeric → numeric sorts lower (§11.4.4).
524
+ * - Fewer identifiers sorts earlier when all preceding are equal.
525
+ */
526
+ function comparePreRelease(a: string, b: string): number {
527
+ const pa = a.split(".");
528
+ const pb = b.split(".");
529
+ const len = Math.max(pa.length, pb.length);
530
+ for (let i = 0; i < len; i++) {
531
+ if (i >= pa.length) return -1; // a has fewer fields → a < b
532
+ if (i >= pb.length) return 1;
533
+ const aIsNum = /^\d+$/.test(pa[i]);
534
+ const bIsNum = /^\d+$/.test(pb[i]);
535
+ if (aIsNum && bIsNum) {
536
+ const diff = Number(pa[i]) - Number(pb[i]);
537
+ if (diff !== 0) return diff;
538
+ } else if (aIsNum !== bIsNum) {
539
+ return aIsNum ? -1 : 1; // numeric < non-numeric per §11.4.4
540
+ } else {
541
+ const cmp = (pa[i] ?? "").localeCompare(pb[i] ?? "");
542
+ if (cmp !== 0) return cmp;
543
+ }
544
+ }
545
+ return 0;
546
+ }
547
+
548
+ /**
549
+ * Compare two semver strings. Returns negative if a < b, 0 if equal, positive if a > b.
550
+ *
551
+ * Handles pre-release suffixes per semver spec:
552
+ * - `0.6.0-staging.1 < 0.6.0` (pre-release < release)
553
+ * - `0.6.0-staging.1 < 0.6.0-staging.2` (numeric postfix comparison)
554
+ */
501
555
  export function compareSemver(a: string, b: string): number {
502
- const pa = a.split(".").map(Number);
503
- const pb = b.split(".").map(Number);
556
+ const pa = parseSemverParts(a);
557
+ const pb = parseSemverParts(b);
504
558
  for (let i = 0; i < 3; i++) {
505
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
559
+ const diff = pa.nums[i] - pb.nums[i];
506
560
  if (diff !== 0) return diff;
507
561
  }
508
- return 0;
562
+ // Same major.minor.patch — compare pre-release
563
+ if (pa.pre === null && pb.pre === null) return 0;
564
+ if (pa.pre !== null && pb.pre === null) return -1; // pre-release < release
565
+ if (pa.pre === null && pb.pre !== null) return 1;
566
+ return comparePreRelease(pa.pre!, pb.pre!);
509
567
  }
@@ -20,6 +20,11 @@ import {
20
20
  } from "../../config/loader.js";
21
21
  import { resolveSkillStates, skillFlagKey } from "../../config/skill-state.js";
22
22
  import { loadSkillCatalog, type SkillSummary } from "../../config/skills.js";
23
+ import {
24
+ deleteSkillCapabilityNode,
25
+ seedSkillGraphNodes,
26
+ seedUninstalledCatalogSkillMemories,
27
+ } from "../../memory/graph/capability-seed.js";
23
28
  import {
24
29
  createTimeout,
25
30
  extractText,
@@ -51,11 +56,6 @@ import {
51
56
  removeSkillsIndexEntry,
52
57
  validateManagedSkillId,
53
58
  } from "../../skills/managed-store.js";
54
- import {
55
- deleteSkillCapabilityMemory,
56
- seedCatalogSkillMemories,
57
- seedUninstalledCatalogSkillMemories,
58
- } from "../../skills/skill-memory.js";
59
59
  import {
60
60
  installExternalSkill,
61
61
  resolveSkillSource,
@@ -242,7 +242,7 @@ export function postInstallSkill(
242
242
  }
243
243
 
244
244
  // Seed skill memories
245
- seedCatalogSkillMemories();
245
+ seedSkillGraphNodes();
246
246
  void seedUninstalledCatalogSkillMemories().catch(() => {});
247
247
  }
248
248
 
@@ -603,7 +603,7 @@ export function enableSkill(
603
603
  name: skillId,
604
604
  state: "enabled",
605
605
  });
606
- seedCatalogSkillMemories();
606
+ seedSkillGraphNodes();
607
607
  void seedUninstalledCatalogSkillMemories().catch(() => {});
608
608
  return { success: true };
609
609
  } catch (err) {
@@ -626,7 +626,7 @@ export function disableSkill(
626
626
  name: skillId,
627
627
  state: "disabled",
628
628
  });
629
- seedCatalogSkillMemories();
629
+ seedSkillGraphNodes();
630
630
  void seedUninstalledCatalogSkillMemories().catch(() => {});
631
631
  return { success: true };
632
632
  } catch (err) {
@@ -719,7 +719,7 @@ export async function installSkill(
719
719
  "Failed to auto-enable bundled skill",
720
720
  );
721
721
  }
722
- seedCatalogSkillMemories();
722
+ seedSkillGraphNodes();
723
723
  void seedUninstalledCatalogSkillMemories().catch(() => {});
724
724
  return { success: true };
725
725
  }
@@ -847,14 +847,7 @@ export async function uninstallSkill(
847
847
  }
848
848
  // Best-effort cleanup of capability memory for uninstalled skill
849
849
  // (managed path handles this internally via deleteManagedSkill)
850
- deleteSkillCapabilityMemory(skillId);
851
- try {
852
- const { deleteSkillCapabilityNode } =
853
- await import("../../memory/graph/capability-seed.js");
854
- deleteSkillCapabilityNode(skillId);
855
- } catch {
856
- /* best effort */
857
- }
850
+ deleteSkillCapabilityNode(skillId);
858
851
  }
859
852
 
860
853
  // Clean config entry
@@ -1255,7 +1248,7 @@ export async function createSkill(
1255
1248
  );
1256
1249
  }
1257
1250
 
1258
- seedCatalogSkillMemories();
1251
+ seedSkillGraphNodes();
1259
1252
  void seedUninstalledCatalogSkillMemories().catch(() => {});
1260
1253
  return { success: true };
1261
1254
  } catch (err) {