@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
@@ -3,6 +3,9 @@ import { join } from "node:path";
3
3
 
4
4
  import type { Command } from "commander";
5
5
 
6
+ import { getConfig } from "../../config/loader.js";
7
+ import { resolveSkillStates } from "../../config/skill-state.js";
8
+ import { loadSkillCatalog } from "../../config/skills.js";
6
9
  import type { CatalogSkill } from "../../skills/catalog-install.js";
7
10
  import {
8
11
  fetchCatalog,
@@ -56,13 +59,13 @@ Examples:
56
59
 
57
60
  skills
58
61
  .command("list")
59
- .description("List available catalog skills")
62
+ .description("List all skills (bundled, installed, and catalog)")
60
63
  .option("--json", "Machine-readable JSON output")
61
64
  .addHelpText(
62
65
  "after",
63
66
  `
64
- Lists all skills available in the Vellum catalog with their ID, name,
65
- description, and dependency information.
67
+ Lists all skills: bundled (compiled-in), installed (user-added), and
68
+ available catalog skills with their ID, name, and description.
66
69
 
67
70
  Examples:
68
71
  $ assistant skills list
@@ -70,34 +73,103 @@ Examples:
70
73
  )
71
74
  .action(async (opts: { json?: boolean }) => {
72
75
  try {
73
- // In dev mode, use the local catalog as the source of truth
74
- // and skip the remote Platform API entirely.
76
+ // ── Bundled + installed skills (from loadSkillCatalog) ────────
77
+ const localCatalog = loadSkillCatalog();
78
+ const config = getConfig();
79
+ const resolved = resolveSkillStates(localCatalog, config);
80
+ const bundled = resolved.filter(
81
+ (r) => r.summary.source === "bundled",
82
+ );
83
+ const installed = resolved.filter(
84
+ (r) =>
85
+ r.summary.source === "managed" ||
86
+ r.summary.source === "workspace" ||
87
+ r.summary.source === "extra",
88
+ );
89
+
90
+ // ── Remote catalog skills ────────────────────────────────────
75
91
  const repoSkillsDir = getRepoSkillsDir();
76
- let catalog: CatalogSkill[];
92
+ let remoteCatalog: CatalogSkill[];
77
93
  if (repoSkillsDir) {
78
- catalog = readLocalCatalog(repoSkillsDir);
94
+ remoteCatalog = readLocalCatalog(repoSkillsDir);
79
95
  } else {
80
- catalog = await fetchCatalog();
96
+ remoteCatalog = await fetchCatalog();
81
97
  }
98
+ // Exclude catalog skills that are already installed/bundled
99
+ const localIds = new Set(localCatalog.map((s) => s.id));
100
+ const availableCatalog = remoteCatalog.filter(
101
+ (s) => !localIds.has(s.id),
102
+ );
103
+
104
+ const totalCount =
105
+ bundled.length + installed.length + availableCatalog.length;
82
106
 
83
107
  if (opts.json) {
84
- console.log(JSON.stringify({ ok: true, skills: catalog }));
108
+ const bundledJson = bundled.map((r) => ({
109
+ id: r.summary.id,
110
+ name: r.summary.displayName,
111
+ description: r.summary.description,
112
+ emoji: r.summary.emoji,
113
+ state: r.state,
114
+ }));
115
+ const installedJson = installed.map((r) => ({
116
+ id: r.summary.id,
117
+ name: r.summary.displayName,
118
+ description: r.summary.description,
119
+ emoji: r.summary.emoji,
120
+ state: r.state,
121
+ }));
122
+ console.log(
123
+ JSON.stringify({
124
+ ok: true,
125
+ skills: [...bundledJson, ...installedJson, ...availableCatalog],
126
+ bundled: bundledJson,
127
+ installed: installedJson,
128
+ catalog: availableCatalog,
129
+ }),
130
+ );
85
131
  return;
86
132
  }
87
133
 
88
- if (catalog.length === 0) {
89
- log.info("No skills available in the catalog.");
134
+ if (totalCount === 0) {
135
+ log.info("No skills available.");
90
136
  return;
91
137
  }
92
138
 
93
- log.info(`Available skills (${catalog.length}):\n`);
94
- for (const s of catalog) {
95
- const emoji = s.emoji ? `${s.emoji} ` : "";
96
- const deps = s.includes?.length
97
- ? ` (requires: ${s.includes.join(", ")})`
98
- : "";
99
- log.info(` ${emoji}${s.id}`);
100
- log.info(` ${s.name} — ${s.description}${deps}`);
139
+ if (bundled.length > 0) {
140
+ log.info(`Bundled skills (${bundled.length}):\n`);
141
+ for (const r of bundled) {
142
+ const s = r.summary;
143
+ const emoji = s.emoji ? `${s.emoji} ` : "";
144
+ const state = r.state === "disabled" ? " [disabled]" : "";
145
+ log.info(` ${emoji}${s.id}${state}`);
146
+ log.info(` ${s.displayName} — ${s.description}`);
147
+ }
148
+ log.info("");
149
+ }
150
+
151
+ if (installed.length > 0) {
152
+ log.info(`Installed skills (${installed.length}):\n`);
153
+ for (const r of installed) {
154
+ const s = r.summary;
155
+ const emoji = s.emoji ? `${s.emoji} ` : "";
156
+ const state = r.state === "disabled" ? " [disabled]" : "";
157
+ log.info(` ${emoji}${s.id}${state}`);
158
+ log.info(` ${s.displayName} — ${s.description}`);
159
+ }
160
+ log.info("");
161
+ }
162
+
163
+ if (availableCatalog.length > 0) {
164
+ log.info(`Available catalog skills (${availableCatalog.length}):\n`);
165
+ for (const s of availableCatalog) {
166
+ const emoji = s.emoji ? `${s.emoji} ` : "";
167
+ const deps = s.includes?.length
168
+ ? ` (requires: ${s.includes.join(", ")})`
169
+ : "";
170
+ log.info(` ${emoji}${s.id}`);
171
+ log.info(` ${s.name} — ${s.description}${deps}`);
172
+ }
101
173
  }
102
174
  } catch (err) {
103
175
  const msg = err instanceof Error ? err.message : String(err);
@@ -139,6 +211,14 @@ Examples:
139
211
  const limit = parseInt(opts.limit, 10) || 10;
140
212
 
141
213
  try {
214
+ // ── Bundled + installed skill search ─────────────────────────
215
+ const localCatalog = loadSkillCatalog();
216
+ const bundledMatches = filterByQuery(localCatalog, query, [
217
+ (s) => s.id,
218
+ (s) => s.displayName,
219
+ (s) => s.description,
220
+ ]);
221
+
142
222
  // ── Vellum catalog search ────────────────────────────────────
143
223
  const repoSkillsDir = getRepoSkillsDir();
144
224
  let catalog: CatalogSkill[];
@@ -151,8 +231,11 @@ Examples:
151
231
  catalog = [];
152
232
  }
153
233
  }
234
+ // Exclude catalog entries that match a bundled/installed skill
235
+ const localIds = new Set(localCatalog.map((s) => s.id));
236
+ const filteredCatalog = catalog.filter((s) => !localIds.has(s.id));
154
237
 
155
- const catalogMatches = filterByQuery(catalog, query, [
238
+ const catalogMatches = filterByQuery(filteredCatalog, query, [
156
239
  (s) => s.id,
157
240
  (s) => s.name,
158
241
  (s) => s.description,
@@ -202,6 +285,7 @@ Examples:
202
285
  ]);
203
286
 
204
287
  if (
288
+ bundledMatches.length === 0 &&
205
289
  catalogMatches.length === 0 &&
206
290
  registryResults.length === 0 &&
207
291
  clawhubResults.length === 0
@@ -210,6 +294,7 @@ Examples:
210
294
  console.log(
211
295
  JSON.stringify({
212
296
  ok: true,
297
+ bundled: [],
213
298
  catalog: [],
214
299
  community: [],
215
300
  clawhub: [],
@@ -255,6 +340,13 @@ Examples:
255
340
  console.log(
256
341
  JSON.stringify({
257
342
  ok: true,
343
+ bundled: bundledMatches.map((s) => ({
344
+ id: s.id,
345
+ name: s.displayName,
346
+ description: s.description,
347
+ emoji: s.emoji,
348
+ source: s.source,
349
+ })),
258
350
  catalog: catalogMatches,
259
351
  community: registryResults,
260
352
  clawhub: clawhubResults,
@@ -271,6 +363,24 @@ Examples:
271
363
  const isInstalled = (id: string) =>
272
364
  existsSync(join(skillsDir, id, "SKILL.md"));
273
365
 
366
+ // ── Display bundled/installed results ─────────────────────────
367
+ if (bundledMatches.length > 0) {
368
+ log.info(
369
+ `Bundled & installed skills (${bundledMatches.length}):\n`,
370
+ );
371
+ for (const s of bundledMatches) {
372
+ const emoji = s.emoji ? `${s.emoji} ` : "";
373
+ const tag = s.source === "bundled" ? " [bundled]" : " [installed]";
374
+ log.info(` ${emoji}${s.displayName}${tag}`);
375
+ if (s.displayName !== s.id) {
376
+ log.info(` ID: ${s.id}`);
377
+ }
378
+ log.info(` ${s.description}`);
379
+ log.info(` Load: skill_load skill=${s.id}`);
380
+ log.info("");
381
+ }
382
+ }
383
+
274
384
  // ── Display catalog results ──────────────────────────────────
275
385
  if (catalogMatches.length > 0) {
276
386
  log.info(`Vellum catalog (${catalogMatches.length}):\n`);
@@ -26,6 +26,7 @@ import { registerMemoryCommand } from "./commands/memory.js";
26
26
  import { registerNotificationsCommand } from "./commands/notifications.js";
27
27
  import { registerOAuthCommand } from "./commands/oauth/index.js";
28
28
  import { registerPlatformCommand } from "./commands/platform/index.js";
29
+ import { registerRoutesCommand } from "./commands/routes.js";
29
30
  import { registerSequenceCommand } from "./commands/sequence.js";
30
31
  import { registerShotgunCommand } from "./commands/shotgun.js";
31
32
  import { registerSkillsCommand } from "./commands/skills.js";
@@ -75,6 +76,7 @@ Examples:
75
76
  registerNotificationsCommand(program);
76
77
  registerPlatformCommand(program);
77
78
  registerOAuthCommand(program);
79
+ registerRoutesCommand(program);
78
80
  registerSkillsCommand(program);
79
81
  registerBrowserRelayCommand(program);
80
82
  registerUsageCommand(program);
package/src/cli.ts CHANGED
@@ -33,11 +33,6 @@ import {
33
33
  type EventStreamWatcher,
34
34
  watchEventStream,
35
35
  } from "./signals/event-stream.js";
36
- import {
37
- copyToClipboard,
38
- extractLastCodeBlock,
39
- formatConversationForExport,
40
- } from "./util/clipboard.js";
41
36
  import { formatDiff, formatNewFileDiff } from "./util/diff.js";
42
37
  import { getHistoryPath, getSignalsDir } from "./util/platform.js";
43
38
  import { Spinner } from "./util/spinner.js";
@@ -143,7 +138,6 @@ export async function startCli(): Promise<void> {
143
138
  let conversationId = "";
144
139
  let pendingUserContent: string | null = null;
145
140
  let generating = false;
146
- let lastResponse = "";
147
141
  let lastUsage: {
148
142
  inputTokens: number;
149
143
  outputTokens: number;
@@ -153,7 +147,6 @@ export async function startCli(): Promise<void> {
153
147
  model: string;
154
148
  } | null = null;
155
149
  let pendingSessionPick = false;
156
- let pendingCopySession = false;
157
150
  let toolStreaming = false;
158
151
  let lastDisplayedError: string | null = null;
159
152
  let eventSubscription: EventStreamWatcher | null = null;
@@ -398,7 +391,6 @@ export async function startCli(): Promise<void> {
398
391
  if (pendingUserContent) {
399
392
  const content = pendingUserContent;
400
393
  pendingUserContent = null;
401
- lastResponse = "";
402
394
  sendUserMessage(content).then((result) => {
403
395
  if (result.ok) {
404
396
  generating = true;
@@ -408,7 +400,6 @@ export async function startCli(): Promise<void> {
408
400
  process.stdout.write(`${result.message}\n`);
409
401
  rl.question("Send anyway? (y/N): ", (answer) => {
410
402
  if (answer.trim().toLowerCase() === "y") {
411
- lastResponse = "";
412
403
  sendUserMessage(content, {
413
404
  bypassSecretCheck: true,
414
405
  }).then((retryResult) => {
@@ -439,7 +430,6 @@ export async function startCli(): Promise<void> {
439
430
 
440
431
  case "assistant_text_delta":
441
432
  spinner.stop();
442
- lastResponse += msg.text;
443
433
  process.stdout.write(msg.text);
444
434
  break;
445
435
 
@@ -595,9 +585,8 @@ export async function startCli(): Promise<void> {
595
585
  case "error":
596
586
  spinner.stop();
597
587
  generating = false;
598
- if (pendingSessionPick || pendingCopySession) {
588
+ if (pendingSessionPick) {
599
589
  pendingSessionPick = false;
600
- pendingCopySession = false;
601
590
  rl.removeAllListeners("line");
602
591
  rl.on("line", handleLine);
603
592
  }
@@ -646,26 +635,6 @@ export async function startCli(): Promise<void> {
646
635
  break;
647
636
 
648
637
  case "history_response":
649
- if (pendingCopySession) {
650
- pendingCopySession = false;
651
- if (msg.messages.length === 0) {
652
- process.stdout.write("\n No messages to copy.\n\n");
653
- } else {
654
- try {
655
- const formatted = formatConversationForExport(msg.messages);
656
- copyToClipboard(formatted);
657
- process.stdout.write(
658
- `\n Copied conversation (${msg.messages.length} messages) to clipboard.\n\n`,
659
- );
660
- } catch (err) {
661
- process.stdout.write(
662
- `\n Clipboard error: ${(err as Error).message}\n\n`,
663
- );
664
- }
665
- }
666
- prompt();
667
- break;
668
- }
669
638
  process.stdout.write("\n");
670
639
  if (msg.messages.length === 0) {
671
640
  process.stdout.write(" No messages in this conversation.\n");
@@ -686,7 +655,6 @@ export async function startCli(): Promise<void> {
686
655
  if (msg.removedCount === 0) {
687
656
  process.stdout.write("\n Nothing to undo.\n\n");
688
657
  } else {
689
- lastResponse = "";
690
658
  process.stdout.write(
691
659
  `\n Removed last exchange (${msg.removedCount} messages).\n\n`,
692
660
  );
@@ -754,21 +722,6 @@ export async function startCli(): Promise<void> {
754
722
  /* ignore */
755
723
  }
756
724
 
757
- if (content === "/copy") {
758
- if (!lastResponse) {
759
- process.stdout.write("No response to copy.\n");
760
- } else {
761
- try {
762
- copyToClipboard(lastResponse);
763
- process.stdout.write("Copied to clipboard.\n");
764
- } catch (err) {
765
- process.stdout.write(`Clipboard error: ${(err as Error).message}\n`);
766
- }
767
- }
768
- prompt();
769
- return;
770
- }
771
-
772
725
  if (content === "/conversations") {
773
726
  pendingSessionPick = true;
774
727
  try {
@@ -787,65 +740,6 @@ export async function startCli(): Promise<void> {
787
740
  return;
788
741
  }
789
742
 
790
- if (content === "/copy-code") {
791
- const code = extractLastCodeBlock(lastResponse);
792
- if (code == null) {
793
- process.stdout.write("No code block found.\n");
794
- } else {
795
- try {
796
- copyToClipboard(code);
797
- process.stdout.write("Copied code block to clipboard.\n");
798
- } catch (err) {
799
- process.stdout.write(`Clipboard error: ${(err as Error).message}\n`);
800
- }
801
- }
802
- prompt();
803
- return;
804
- }
805
-
806
- if (content === "/copy-conversation") {
807
- try {
808
- const mapping = getConversationByKey(conversationKey);
809
- if (!mapping) {
810
- process.stdout.write("\n No messages to copy.\n\n");
811
- prompt();
812
- return;
813
- }
814
- const rawMessages = getMessages(mapping.conversationId);
815
- if (rawMessages.length === 0) {
816
- process.stdout.write("\n No messages to copy.\n\n");
817
- } else {
818
- const rendered = rawMessages.map((msg) => {
819
- let parsedContent: unknown;
820
- try {
821
- parsedContent = JSON.parse(msg.content);
822
- } catch {
823
- parsedContent = msg.content;
824
- }
825
- return {
826
- role: msg.role as "user" | "assistant",
827
- text: renderHistoryContent(parsedContent).text,
828
- };
829
- });
830
- try {
831
- const formatted = formatConversationForExport(rendered);
832
- copyToClipboard(formatted);
833
- process.stdout.write(
834
- `\n Copied conversation (${rawMessages.length} messages) to clipboard.\n\n`,
835
- );
836
- } catch (err) {
837
- process.stdout.write(
838
- `\n Clipboard error: ${(err as Error).message}\n\n`,
839
- );
840
- }
841
- }
842
- } catch {
843
- process.stdout.write("[Failed to fetch history]\n");
844
- }
845
- prompt();
846
- return;
847
- }
848
-
849
743
  if (content === "/new") {
850
744
  // Create a new conversation by using a unique key
851
745
  conversationKey = `builtin-cli:${randomUUID()}`;
@@ -859,7 +753,6 @@ export async function startCli(): Promise<void> {
859
753
  }
860
754
 
861
755
  if (content === "/clear") {
862
- lastResponse = "";
863
756
  process.stdout.write("\x1b[r");
864
757
  process.stdout.write("\x1b[2J\x1b[H");
865
758
  mainScreenLayout = renderMainScreen();
@@ -955,7 +848,6 @@ export async function startCli(): Promise<void> {
955
848
  if (result.removedCount === 0) {
956
849
  process.stdout.write("\n Nothing to undo.\n\n");
957
850
  } else {
958
- lastResponse = "";
959
851
  process.stdout.write(
960
852
  `\n Removed last exchange (${result.removedCount} messages).\n\n`,
961
853
  );
@@ -1016,15 +908,6 @@ export async function startCli(): Promise<void> {
1016
908
  " /undo Remove last message exchange\n",
1017
909
  );
1018
910
  process.stdout.write(" /usage Show token usage and cost\n");
1019
- process.stdout.write(
1020
- " /copy Copy last response to clipboard\n",
1021
- );
1022
- process.stdout.write(
1023
- " /copy-code Copy last code block to clipboard\n",
1024
- );
1025
- process.stdout.write(
1026
- " /copy-conversation Copy entire conversation to clipboard\n",
1027
- );
1028
911
  process.stdout.write(" /help Show this help\n");
1029
912
  process.stdout.write("\n");
1030
913
  prompt();
@@ -1032,14 +915,12 @@ export async function startCli(): Promise<void> {
1032
915
  }
1033
916
 
1034
917
  // Regular user message
1035
- lastResponse = "";
1036
918
  sendUserMessage(content).then((result) => {
1037
919
  if (!result.ok) {
1038
920
  if (result.error === "secret_blocked" && result.message) {
1039
921
  process.stdout.write(`${result.message}\n`);
1040
922
  rl.question("Send anyway? (y/N): ", (answer) => {
1041
923
  if (answer.trim().toLowerCase() === "y") {
1042
- lastResponse = "";
1043
924
  sendUserMessage(content, { bypassSecretCheck: true }).then(
1044
925
  (retryResult) => {
1045
926
  if (retryResult.ok) {
@@ -238,7 +238,7 @@ export const Header: FunctionComponent<HeaderProps> = ({ title, count }) => (
238
238
 
239
239
  file_write("{workspaceDir}/data/apps/project-tracker/src/styles.css", `.app { padding: var(--v-spacing-lg); }
240
240
  .header { display: flex; justify-content: space-between; align-items: center; }
241
- .badge { background: var(--v-accent); color: white; padding: var(--v-spacing-xs) var(--v-spacing-sm); border-radius: var(--v-radius-pill); }`)
241
+ .badge { background: var(--v-accent); color: var(--v-aux-white); padding: var(--v-spacing-xs) var(--v-spacing-sm); border-radius: var(--v-radius-pill); }`)
242
242
 
243
243
  # After all files are written, compile and refresh:
244
244
  app_refresh(app_id)
@@ -290,9 +290,12 @@ Available design tokens:
290
290
  | **Typography** | `--v-font-family`, `--v-font-mono`, `--v-font-size-xs` (10px) / `-sm` (11px) / `-base` (14px) / `-lg` (17px) / `-xl` (22px) / `-2xl` (26px), `--v-line-height` |
291
291
  | **Animation** | `--v-duration-fast` (0.15s) / `-standard` (0.25s) / `-slow` (0.4s) |
292
292
  | **Palettes** | `--v-slate-{950..50}`, `--v-emerald-*`, `--v-violet-*`, `--v-indigo-*`, `--v-rose-*`, `--v-amber-*` |
293
+ | **Constant** | `--v-aux-white` (always `#FFFFFF` in both modes — use for text on filled/accent backgrounds) |
293
294
 
294
295
  Utility classes: `.v-button` (`.secondary`/`.danger`/`.ghost`), `.v-card`, `.v-list`/`.v-list-item`, `.v-badge` (`.success`/`.warning`/`.danger`), `.v-input-row`, `.v-empty-state`, `.v-toggle`.
295
296
 
297
+ **Never hardcode `color: white` or `color: #fff`.** Use `var(--v-aux-white)` for text on filled/accent backgrounds, or `var(--v-text)` / `var(--v-text-secondary)` for text on surface backgrounds. Hardcoded white causes invisible text on light surfaces.
298
+
296
299
  **Custom themes:** When the user wants a specific branded look, write complete CSS with hardcoded colors and `@media (prefers-color-scheme: dark)` for dark variants. Don't mix `--v-*` auto-switching variables with hardcoded colors in the same element.
297
300
 
298
301
  **Theme detection in JavaScript:**
@@ -156,9 +156,9 @@ Scan tools (`gmail_sender_digest`, `gmail_outreach_scan`) return a `scan_id` tha
156
156
 
157
157
  Before composing any email that references a date or time:
158
158
 
159
- 1. Check the `<temporal_context>` block in the current turn for today's date and timezone
159
+ 1. Check the `timestamp:` field in the `<turn_context>` block for today's date and timezone
160
160
  2. Verify that "tomorrow" means the day after today's date, "next week" means the upcoming Monday–Friday, etc.
161
- 3. If the email references a date from another message, cross-check it against the temporal context to ensure it's in the future
161
+ 3. If the email references a date from another message, cross-check it against the turn context to ensure it's in the future
162
162
 
163
163
  ## Confidence Scores
164
164
 
@@ -148,6 +148,7 @@ Telegram is supported as a messaging provider with limited capabilities compared
148
148
  - `send_notification` is provided by the **notifications** skill (always active) -- use it when the user asks for an alert/notification (for example "send this as a desktop notification").
149
149
  - Use `messaging_send` when the user asks to send a message into a specific chat/email destination.
150
150
  - `send_notification` channel routing is LLM-driven; `preferred_channels` are hints, not hard channel forcing.
151
+ - Before using `messaging_send` or `send_notification`, look up the recipient's contact record with `contact_search` to inform tone and content (see **Recipient Context** below).
151
152
 
152
153
  ## Personalized Drafting
153
154
 
@@ -157,6 +158,12 @@ If no style items exist and the user asks you to draft a message, suggest runnin
157
158
 
158
159
  > "I can analyze your sent messages to learn your writing style so drafts sound like you. Want me to do that?"
159
160
 
161
+ ## Recipient Context
162
+
163
+ Before composing or sending a message to someone, look up their contact record with `contact_search` using their name or channel address. If the contact has notes (e.g. relationship context, communication preferences, response expectations), use that context to inform the message's tone, level of detail, and content. This ensures outbound messages are personalized to the recipient — not just the sender's style.
164
+
165
+ If no contact record exists, proceed without recipient context.
166
+
160
167
  ## Confidence Scores
161
168
 
162
169
  Medium and high risk tools require a confidence score between 0 and 1:
@@ -87,6 +87,14 @@ The `mode` parameter controls what happens when a schedule fires:
87
87
 
88
88
  Use `notify` for simple reminders ("remind me to take medicine at 9am") and `execute` for tasks that need assistant action ("check my calendar at 8am and send me a digest").
89
89
 
90
+ ## Conversation Reuse
91
+
92
+ By default, each schedule run creates a new conversation. For recurring schedules that benefit from accumulating context across runs (e.g. polling-style jobs, daily digests that reference prior results), set `reuse_conversation: true`. When enabled, subsequent runs reuse the conversation from the last successful run instead of creating a new one.
93
+
94
+ - Only applies to **recurring** schedules; ignored for one-shot schedules.
95
+ - If the prior conversation has been deleted, a new one is created automatically.
96
+ - On the first run (no prior conversation), a new conversation is created as usual.
97
+
90
98
  ## Routing (notify mode)
91
99
 
92
100
  Control how notify-mode schedules are delivered at trigger time with `routing_intent`:
@@ -101,9 +109,20 @@ Optionally pass `routing_hints` (a JSON object) to influence routing decisions (
101
109
 
102
110
  - **Default to `all_channels`** for most notifications. Users usually want to be notified wherever they are.
103
111
  - **Use `single_channel`** only when the user explicitly specifies a single channel (e.g. "remind me on Telegram").
104
- - **Check `user_message_channel` (or `channel` when all channels are the same)** from the turn context. If the user is currently active on a specific channel, include it as a routing hint:
112
+ - **Determine the originating channel** for routing hints using this priority:
113
+ 1. **`source_channel`** from `<turn_context>` — use directly if present. This is the authoritative channel name.
114
+ 2. **`interface` fallback** — if `source_channel` is absent (common for guardian/direct users), map the `interface` value to a channel name:
115
+ | `interface` value | Channel name |
116
+ | --- | --- |
117
+ | `macos`, `ios` | `vellum` |
118
+ | `telegram` | `telegram` |
119
+ | `slack` | `slack` |
120
+ | `cli` | _(omit — no routable channel)_ |
121
+ 3. If neither field is present or the interface is `cli`, omit `preferred_channels`.
122
+
123
+ When a channel is determined, include it as a routing hint:
105
124
  ```
106
- routing_hints: { preferred_channels: ["vellum"] }
125
+ routing_hints: { preferred_channels: ["<resolved channel>"] }
107
126
  routing_intent: "all_channels"
108
127
  ```
109
128
 
@@ -119,6 +138,7 @@ Use `syntax` + `expression` to specify the schedule type explicitly, or just `ex
119
138
 
120
139
  ## Tips
121
140
 
141
+ - **When the user specifies a name for the schedule, use it exactly as given.** Do not paraphrase, embellish, or generate a descriptive name.
122
142
  - Use `schedule_create` for both recurring automation ("every day at 9am") and one-time reminders ("remind me at 3pm").
123
143
  - For task tracking ("add to my tasks", "add to my queue"), use task_list_add instead.
124
144
  - `fire_at` must be a strict ISO 8601 timestamp with timezone offset or Z (e.g. `2025-03-15T09:00:00-05:00`).
@@ -56,6 +56,10 @@
56
56
  "type": "boolean",
57
57
  "description": "When true, suppress completion notifications for this schedule. The job still runs and produces output, but no notification or conversation message is sent on completion. Useful for high-frequency recurring jobs that report findings separately. Defaults to false."
58
58
  },
59
+ "reuse_conversation": {
60
+ "type": "boolean",
61
+ "description": "When true, reuse the same conversation across recurring schedule runs instead of creating a new one each time. Useful for polling-style schedules that accumulate context over time. Ignored for one-shot schedules. Defaults to false."
62
+ },
59
63
  "activity": {
60
64
  "type": "string",
61
65
  "description": "Brief non-technical explanation of why this tool is being called"
@@ -147,6 +151,10 @@
147
151
  "type": "boolean",
148
152
  "description": "When true, suppress completion notifications for this schedule. Useful for high-frequency jobs that report findings separately."
149
153
  },
154
+ "reuse_conversation": {
155
+ "type": "boolean",
156
+ "description": "When true, reuse the same conversation across recurring schedule runs instead of creating a new one each time. Useful for polling-style schedules that accumulate context over time. Ignored for one-shot schedules."
157
+ },
150
158
  "activity": {
151
159
  "type": "string",
152
160
  "description": "Brief non-technical explanation of why this tool is being called"
@@ -7,27 +7,17 @@ import type {
7
7
  ToolContext,
8
8
  ToolExecutionResult,
9
9
  } from "../../../../tools/types.js";
10
- import { getWorkspaceDir } from "../../../../util/platform.js";
10
+ import { getAvatarDir, getAvatarImagePath } from "../../../../util/platform.js";
11
11
 
12
12
  export async function run(
13
13
  _input: Record<string, unknown>,
14
14
  _context: ToolContext,
15
15
  ): Promise<ToolExecutionResult> {
16
- const avatarPath = join(
17
- getWorkspaceDir(),
18
- "data",
19
- "avatar",
20
- "avatar-image.png",
21
- );
16
+ const avatarPath = getAvatarImagePath();
22
17
 
23
18
  if (!existsSync(avatarPath)) {
24
19
  // Check for native character traits and regenerate the static PNG
25
- const traitsPath = join(
26
- getWorkspaceDir(),
27
- "data",
28
- "avatar",
29
- "character-traits.json",
30
- );
20
+ const traitsPath = join(getAvatarDir(), "character-traits.json");
31
21
  if (existsSync(traitsPath)) {
32
22
  try {
33
23
  const traits = JSON.parse(readFileSync(traitsPath, "utf-8"));
@@ -1,5 +1,4 @@
1
1
  import { existsSync, unlinkSync } from "node:fs";
2
- import { join } from "node:path";
3
2
 
4
3
  import { buildAssistantEvent } from "../../../../runtime/assistant-event.js";
5
4
  import { assistantEventHub } from "../../../../runtime/assistant-event-hub.js";
@@ -9,7 +8,7 @@ import type {
9
8
  ToolExecutionResult,
10
9
  } from "../../../../tools/types.js";
11
10
  import { getLogger } from "../../../../util/logger.js";
12
- import { getWorkspaceDir } from "../../../../util/platform.js";
11
+ import { getAvatarImagePath } from "../../../../util/platform.js";
13
12
  import { updateIdentityAvatarSection } from "./identity-avatar.js";
14
13
 
15
14
  const log = getLogger("avatar-remove");
@@ -18,8 +17,7 @@ export async function run(
18
17
  _input: Record<string, unknown>,
19
18
  _context: ToolContext,
20
19
  ): Promise<ToolExecutionResult> {
21
- const avatarDir = join(getWorkspaceDir(), "data", "avatar");
22
- const avatarPath = join(avatarDir, "avatar-image.png");
20
+ const avatarPath = getAvatarImagePath();
23
21
 
24
22
  if (!existsSync(avatarPath)) {
25
23
  return {