@vellumai/assistant 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/docker-entrypoint.sh +0 -1
  2. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  3. package/openapi.yaml +610 -16
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  6. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  7. package/src/__tests__/agent-loop.test.ts +88 -3
  8. package/src/__tests__/anthropic-provider.test.ts +272 -0
  9. package/src/__tests__/approval-cascade.test.ts +1 -1
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  11. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  12. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  13. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  14. package/src/__tests__/compaction-events.test.ts +1 -1
  15. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  16. package/src/__tests__/config-watcher.test.ts +1 -1
  17. package/src/__tests__/context-token-estimator.test.ts +91 -1
  18. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  19. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +25 -7
  22. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  23. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  24. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  25. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  26. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  27. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  28. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  29. package/src/__tests__/conversation-pairing.test.ts +2 -2
  30. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  31. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  32. package/src/__tests__/conversation-queue.test.ts +1 -1
  33. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  34. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  35. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  36. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  37. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  38. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  39. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  40. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  42. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  43. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  45. package/src/__tests__/dm-backfill.test.ts +64 -0
  46. package/src/__tests__/dm-persistence.test.ts +33 -0
  47. package/src/__tests__/document-find-replace.test.ts +501 -0
  48. package/src/__tests__/first-greeting.test.ts +23 -2
  49. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  50. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  51. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  52. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  53. package/src/__tests__/host-file-proxy.test.ts +8 -1
  54. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  55. package/src/__tests__/identity-routes.test.ts +57 -0
  56. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  57. package/src/__tests__/injector-chain.test.ts +2 -0
  58. package/src/__tests__/injector-document-comments.test.ts +378 -0
  59. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  60. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  61. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  62. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  63. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  64. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  65. package/src/__tests__/llm-resolver.test.ts +85 -1
  66. package/src/__tests__/log-export-routes.test.ts +99 -2
  67. package/src/__tests__/message-queue-steer.test.ts +114 -0
  68. package/src/__tests__/openai-provider.test.ts +105 -0
  69. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  70. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  72. package/src/__tests__/platform.test.ts +0 -3
  73. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  74. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  75. package/src/__tests__/process-message-display-content.test.ts +21 -16
  76. package/src/__tests__/server-history-render.test.ts +83 -4
  77. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  78. package/src/__tests__/system-prompt.test.ts +51 -28
  79. package/src/__tests__/terminal-tools.test.ts +11 -1
  80. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  81. package/src/__tests__/thread-backfill.test.ts +370 -22
  82. package/src/__tests__/tool-executor.test.ts +90 -1
  83. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  84. package/src/__tests__/twilio-routes.test.ts +1 -1
  85. package/src/__tests__/web-fetch.test.ts +2 -2
  86. package/src/__tests__/workspace-git-service.test.ts +88 -5
  87. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  88. package/src/agent/attachments.ts +1 -0
  89. package/src/agent/loop.ts +57 -20
  90. package/src/background-wake/next-wake.test.ts +289 -0
  91. package/src/background-wake/next-wake.ts +172 -0
  92. package/src/browser/operations.ts +15 -0
  93. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  94. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  95. package/src/cli/commands/conversations.ts +128 -1
  96. package/src/cli/commands/inference-providers.ts +147 -1
  97. package/src/cli/commands/memory-v2.ts +308 -0
  98. package/src/cli/commands/notifications.ts +24 -2
  99. package/src/cli/utils/conversation-id.ts +17 -5
  100. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  101. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  102. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  103. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  104. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  105. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  106. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  107. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  108. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  109. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  110. package/src/config/bundled-tool-registry.ts +22 -12
  111. package/src/config/call-site-defaults.ts +19 -0
  112. package/src/config/feature-flag-registry.json +99 -3
  113. package/src/config/llm-resolver.ts +16 -2
  114. package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
  115. package/src/config/schemas/call-site-catalog.ts +21 -0
  116. package/src/config/schemas/llm.ts +3 -0
  117. package/src/config/schemas/memory-v2.ts +48 -1
  118. package/src/context/compactor.ts +8 -1
  119. package/src/context/token-estimator.ts +47 -4
  120. package/src/context/window-manager.ts +25 -0
  121. package/src/credential-health/credential-health-service.ts +34 -19
  122. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  123. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  124. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  125. package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
  126. package/src/daemon/conversation-agent-loop.ts +223 -54
  127. package/src/daemon/conversation-lifecycle.ts +142 -116
  128. package/src/daemon/conversation-messaging.ts +3 -0
  129. package/src/daemon/conversation-process.ts +273 -0
  130. package/src/daemon/conversation-queue-manager.ts +14 -0
  131. package/src/daemon/conversation-runtime-assembly.ts +135 -75
  132. package/src/daemon/conversation-slash.ts +37 -5
  133. package/src/daemon/conversation-surfaces.ts +45 -2
  134. package/src/daemon/conversation-tool-setup.ts +7 -0
  135. package/src/daemon/conversation.ts +42 -5
  136. package/src/daemon/first-greeting.ts +10 -0
  137. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  138. package/src/daemon/handlers/config-a2a.ts +160 -0
  139. package/src/daemon/handlers/config-model.test.ts +1 -0
  140. package/src/daemon/handlers/conversations.ts +79 -0
  141. package/src/daemon/handlers/shared.ts +92 -29
  142. package/src/daemon/host-bash-proxy.ts +1 -1
  143. package/src/daemon/host-cu-proxy.ts +1 -1
  144. package/src/daemon/host-file-proxy.ts +1 -1
  145. package/src/daemon/host-transfer-proxy.ts +1 -1
  146. package/src/daemon/lifecycle.ts +18 -4
  147. package/src/daemon/message-protocol.ts +4 -0
  148. package/src/daemon/message-types/conversations.ts +8 -0
  149. package/src/daemon/message-types/document-comments.ts +50 -0
  150. package/src/daemon/message-types/messages.ts +68 -1
  151. package/src/daemon/message-types/surfaces.ts +3 -1
  152. package/src/daemon/message-types/web-activity.ts +57 -0
  153. package/src/daemon/plugin-source-watcher.ts +135 -3
  154. package/src/daemon/process-message.ts +69 -12
  155. package/src/daemon/query-complexity-router.ts +75 -0
  156. package/src/daemon/trust-context.ts +6 -0
  157. package/src/documents/document-comments-store.test.ts +338 -0
  158. package/src/documents/document-comments-store.ts +237 -0
  159. package/src/documents/document-store.ts +202 -0
  160. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  161. package/src/heartbeat/heartbeat-service.ts +1 -0
  162. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  163. package/src/home/feed-types.ts +6 -1
  164. package/src/home/home-content-refresh.ts +52 -0
  165. package/src/home/home-greeting-cache.ts +69 -0
  166. package/src/home/home-greeting.ts +94 -0
  167. package/src/home/suggested-prompts.ts +177 -9
  168. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  169. package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
  170. package/src/memory/conversation-crud.ts +133 -43
  171. package/src/memory/db-init.ts +16 -0
  172. package/src/memory/delivery-crud.ts +41 -0
  173. package/src/memory/delivery-status.ts +141 -15
  174. package/src/memory/external-conversation-store.ts +32 -1
  175. package/src/memory/jobs-worker.ts +21 -1
  176. package/src/memory/memory-retrospective-constants.ts +28 -0
  177. package/src/memory/memory-retrospective-enqueue.ts +3 -2
  178. package/src/memory/memory-retrospective-job.ts +408 -18
  179. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  180. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  181. package/src/memory/migrations/100-core-tables.ts +1 -0
  182. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  183. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  184. package/src/memory/migrations/253-document-comments.ts +47 -0
  185. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  186. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  187. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  188. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  189. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  190. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  191. package/src/memory/migrations/index.ts +17 -0
  192. package/src/memory/migrations/registry.ts +25 -0
  193. package/src/memory/onboarding-events-store.ts +7 -0
  194. package/src/memory/schema/calls.ts +1 -0
  195. package/src/memory/schema/conversations.ts +3 -0
  196. package/src/memory/schema/infrastructure.ts +1 -0
  197. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  198. package/src/memory/v2/__tests__/injection.test.ts +31 -14
  199. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  200. package/src/memory/v2/__tests__/router.test.ts +489 -1
  201. package/src/memory/v2/consolidation-job.ts +14 -0
  202. package/src/memory/v2/injection-events.ts +101 -0
  203. package/src/memory/v2/injection.ts +21 -10
  204. package/src/memory/v2/page-index.ts +209 -7
  205. package/src/memory/v2/page-store.ts +18 -0
  206. package/src/memory/v2/router.ts +209 -55
  207. package/src/messaging/providers/index.ts +7 -1
  208. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  209. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  210. package/src/messaging/providers/slack/adapter.ts +178 -25
  211. package/src/messaging/providers/slack/api.test.ts +54 -0
  212. package/src/messaging/providers/slack/api.ts +119 -3
  213. package/src/messaging/providers/slack/client.ts +12 -0
  214. package/src/messaging/providers/slack/deep-link.ts +20 -1
  215. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  216. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  217. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  218. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  219. package/src/messaging/providers/slack/send.test.ts +77 -0
  220. package/src/messaging/providers/slack/send.ts +8 -2
  221. package/src/messaging/providers/slack/types.ts +14 -0
  222. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  223. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  224. package/src/notifications/conversation-seed-composer.ts +14 -2
  225. package/src/notifications/deferred-emit.ts +135 -0
  226. package/src/notifications/emit-signal.ts +9 -1
  227. package/src/notifications/home-feed-side-effect.ts +60 -30
  228. package/src/oauth/connect-orchestrator.ts +3 -0
  229. package/src/oauth/credential-token-resolver.ts +2 -0
  230. package/src/oauth/manual-token-connection.ts +19 -0
  231. package/src/oauth/oauth-store.ts +12 -0
  232. package/src/oauth/seed-providers.ts +22 -0
  233. package/src/permissions/prompter.ts +5 -2
  234. package/src/permissions/secret-prompter.ts +4 -1
  235. package/src/plugins/defaults/injectors.ts +82 -9
  236. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  237. package/src/prompts/normalize-onboarding.ts +40 -0
  238. package/src/prompts/sections.ts +32 -14
  239. package/src/prompts/system-prompt.ts +105 -68
  240. package/src/prompts/template-detection.ts +37 -0
  241. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  242. package/src/prompts/templates/BOOTSTRAP.md +8 -0
  243. package/src/prompts/templates/VOICE.md +3 -0
  244. package/src/prompts/templates/system-sections.ts +53 -3
  245. package/src/providers/anthropic/client.ts +132 -5
  246. package/src/providers/fireworks/client.ts +20 -2
  247. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  248. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  249. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  250. package/src/providers/inference/adapter-factory.ts +15 -1
  251. package/src/providers/inference/auth.ts +3 -3
  252. package/src/providers/inference/codex-token-refresh.ts +128 -0
  253. package/src/providers/inference/resolve-auth.ts +49 -6
  254. package/src/providers/model-catalog.ts +48 -1
  255. package/src/providers/openai/chat-completions-provider.ts +57 -20
  256. package/src/providers/openai/responses-provider.ts +9 -3
  257. package/src/providers/openrouter/client.ts +5 -1
  258. package/src/providers/types.ts +25 -0
  259. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  260. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  261. package/src/runtime/agent-wake.ts +151 -56
  262. package/src/runtime/auth/route-policy.ts +7 -3
  263. package/src/runtime/background-job-runner.ts +26 -0
  264. package/src/runtime/channel-reply-delivery.ts +182 -47
  265. package/src/runtime/channel-retry-sweep.ts +141 -16
  266. package/src/runtime/http-types.ts +7 -4
  267. package/src/runtime/pending-interactions.ts +51 -8
  268. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  269. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
  270. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  271. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  272. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  273. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  274. package/src/runtime/routes/approval-routes.ts +4 -1
  275. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  276. package/src/runtime/routes/content-source-routes.ts +78 -0
  277. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  278. package/src/runtime/routes/conversation-query-routes.ts +60 -1
  279. package/src/runtime/routes/conversation-routes.ts +281 -76
  280. package/src/runtime/routes/document-comments-routes.ts +287 -0
  281. package/src/runtime/routes/documents-routes.ts +33 -0
  282. package/src/runtime/routes/home-feed-routes.ts +6 -3
  283. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  284. package/src/runtime/routes/host-browser-routes.ts +8 -1
  285. package/src/runtime/routes/identity-routes.ts +21 -0
  286. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  287. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  288. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  289. package/src/runtime/routes/index.ts +12 -4
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  291. package/src/runtime/routes/integrations/a2a.ts +60 -1
  292. package/src/runtime/routes/log-export-routes.ts +39 -0
  293. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  294. package/src/runtime/routes/notification-routes.ts +19 -2
  295. package/src/runtime/routes/question-routes.ts +4 -1
  296. package/src/runtime/routes/sanity-routes.ts +159 -0
  297. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  298. package/src/runtime/services/conversation-serializer.ts +30 -4
  299. package/src/schedule/integration-status.ts +3 -1
  300. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  301. package/src/security/oauth2-device-code.ts +307 -0
  302. package/src/security/oauth2.ts +26 -9
  303. package/src/security/secure-keys.ts +5 -0
  304. package/src/skills/catalog-install.ts +6 -2
  305. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  306. package/src/tools/browser/browser-execution.ts +93 -0
  307. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  308. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  309. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  310. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  311. package/src/tools/browser/cdp-client/factory.ts +87 -3
  312. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  313. package/src/tools/browser/cdp-client/types.ts +36 -0
  314. package/src/tools/browser/pinned-tabs.ts +90 -0
  315. package/src/tools/document/document-comment-tool.test.ts +379 -0
  316. package/src/tools/document/document-comment-tool.ts +156 -0
  317. package/src/tools/document/document-tool.ts +128 -2
  318. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  319. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  320. package/src/tools/network/domain-normalize.ts +17 -0
  321. package/src/tools/network/web-fetch.ts +213 -64
  322. package/src/tools/network/web-search.ts +191 -66
  323. package/src/tools/terminal/safe-env.ts +3 -2
  324. package/src/tools/tool-approval-handler.ts +19 -12
  325. package/src/tools/types.ts +4 -0
  326. package/src/tools/ui-surface/definitions.ts +3 -1
  327. package/src/types/onboarding-context.ts +4 -0
  328. package/src/util/__tests__/favicon.test.ts +84 -0
  329. package/src/util/favicon.ts +40 -0
  330. package/src/util/platform.ts +0 -5
  331. package/src/workspace/git-service.ts +75 -4
  332. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  333. package/src/workspace/migrations/registry.ts +2 -0
  334. package/src/config/bundled-skills/document/SKILL.md +0 -54
  335. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  336. package/src/daemon/seed-files.ts +0 -18
  337. package/src/runtime/routes/interface-routes.ts +0 -43
  338. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  339. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  340. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  341. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  342. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -2,10 +2,15 @@ import { existsSync, readFileSync } from "node:fs";
2
2
 
3
3
  import type { Command } from "commander";
4
4
 
5
- import { cliIpcCall, exitFromIpcResult } from "../../ipc/cli-client.js";
5
+ import {
6
+ cliIpcCall,
7
+ exitCodeFromIpcResult,
8
+ exitFromIpcResult,
9
+ } from "../../ipc/cli-client.js";
6
10
  import { registerCommand } from "../lib/register-command.js";
7
11
  import { timeAgo } from "../lib/time-ago.js";
8
12
  import { log } from "../logger.js";
13
+ import { tryResolveConversationId } from "../utils/conversation-id.js";
9
14
  import { registerConversationsDeferCommand } from "./conversations-defer.js";
10
15
  import { registerConversationsImportCommand } from "./conversations-import.js";
11
16
 
@@ -14,6 +19,23 @@ type ConversationSeedMessage = {
14
19
  content: string;
15
20
  };
16
21
 
22
+ type SlackDetachCliResult = {
23
+ detached: boolean;
24
+ channelId: string;
25
+ threadTs: string;
26
+ source: "explicit" | "conversation_binding";
27
+ conversationId?: string;
28
+ };
29
+
30
+ function outputSlackDetachError(message: string, json?: boolean): void {
31
+ if (json) {
32
+ process.stdout.write(JSON.stringify({ ok: false, error: message }) + "\n");
33
+ } else {
34
+ log.error(`Error: ${message}`);
35
+ }
36
+ process.exitCode = 1;
37
+ }
38
+
17
39
  function readSeedMessages(
18
40
  contentFile?: string,
19
41
  ): ConversationSeedMessage[] | undefined {
@@ -318,6 +340,111 @@ Examples:
318
340
  },
319
341
  );
320
342
 
343
+ // -------------------------------------------------------------------
344
+ // slack
345
+ // -------------------------------------------------------------------
346
+
347
+ const slack = conversations
348
+ .command("slack")
349
+ .description("Manage Slack conversation bindings");
350
+
351
+ slack
352
+ .command("detach [conversationId]")
353
+ .alias("mute")
354
+ .description("Detach the assistant from a Slack thread")
355
+ .option("--channel <id>", "Slack channel ID")
356
+ .option("--thread <ts>", "Slack thread timestamp")
357
+ .option("--json", "Output result as JSON")
358
+ .addHelpText(
359
+ "after",
360
+ `
361
+ Arguments:
362
+ conversationId Optional conversation ID. Defaults to the current skill or
363
+ tool conversation when available.
364
+
365
+ Detaches the assistant from Socket Mode listening for the Slack thread bound
366
+ to a conversation, or for explicit --channel and --thread identifiers.
367
+
368
+ Examples:
369
+ $ assistant conversations slack detach
370
+ $ assistant conversations slack detach conv-123
371
+ $ assistant conversations slack mute --channel C123 --thread 1700000000.000100
372
+ $ assistant conversations slack detach --json`,
373
+ )
374
+ .action(
375
+ async (
376
+ conversationIdArg: string | undefined,
377
+ opts: { channel?: string; thread?: string; json?: boolean },
378
+ ) => {
379
+ const channelId = opts.channel?.trim();
380
+ const threadTs = opts.thread?.trim();
381
+ const hasExplicitSlackTarget =
382
+ opts.channel !== undefined || opts.thread !== undefined;
383
+ const body: Record<string, string> = {};
384
+
385
+ if (hasExplicitSlackTarget) {
386
+ if (!channelId || !threadTs) {
387
+ outputSlackDetachError(
388
+ "Both --channel and --thread are required when using explicit Slack identifiers.",
389
+ opts.json,
390
+ );
391
+ return;
392
+ }
393
+ body.channelId = channelId;
394
+ body.threadTs = threadTs;
395
+ } else {
396
+ const conversationId = tryResolveConversationId({
397
+ explicit: conversationIdArg,
398
+ });
399
+ if (!conversationId) {
400
+ outputSlackDetachError(
401
+ "No conversation ID available. Pass a conversation ID, provide --channel and --thread, or run this command from a skill or bash tool context.",
402
+ opts.json,
403
+ );
404
+ return;
405
+ }
406
+ body.conversationId = conversationId;
407
+ }
408
+
409
+ const result = await cliIpcCall<SlackDetachCliResult>(
410
+ "conversation_slack_detach_cli",
411
+ { body },
412
+ );
413
+
414
+ if (!result.ok) {
415
+ if (opts.json) {
416
+ process.stdout.write(
417
+ JSON.stringify({
418
+ ok: false,
419
+ error: result.error ?? "Failed to detach Slack thread",
420
+ }) + "\n",
421
+ );
422
+ process.exitCode = exitCodeFromIpcResult(result);
423
+ return;
424
+ }
425
+ return exitFromIpcResult(result);
426
+ }
427
+
428
+ const detach = result.result!;
429
+ if (opts.json) {
430
+ process.stdout.write(
431
+ JSON.stringify({ ok: true, result: detach }) + "\n",
432
+ );
433
+ return;
434
+ }
435
+
436
+ if (detach.detached) {
437
+ log.info(
438
+ `Detached Slack thread ${detach.threadTs} in channel ${detach.channelId} from assistant listening.`,
439
+ );
440
+ } else {
441
+ log.info(
442
+ `Slack thread ${detach.threadTs} in channel ${detach.channelId} was already detached from assistant listening.`,
443
+ );
444
+ }
445
+ },
446
+ );
447
+
321
448
  // -------------------------------------------------------------------
322
449
  // clear
323
450
  // -------------------------------------------------------------------
@@ -16,7 +16,12 @@
16
16
 
17
17
  import type { Command } from "commander";
18
18
 
19
+ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
20
+ import { getConfig } from "../../config/loader.js";
19
21
  import { cliIpcCall } from "../../ipc/cli-client.js";
22
+ import type { OAuth2Config } from "../../security/oauth2.js";
23
+ import { startOAuth2Flow } from "../../security/oauth2.js";
24
+ import { setSecureKeyAsync } from "../../security/secure-keys.js";
20
25
  import { log } from "../logger.js";
21
26
 
22
27
  // ---------------------------------------------------------------------------
@@ -165,7 +170,11 @@ function buildAuthInput(
165
170
  if (credential) return "--credential is not accepted with --auth none";
166
171
  return { type: "none" };
167
172
  }
168
- return `Unknown auth type "${authType}". Use: api_key, platform, none`;
173
+ if (authType === "oauth_subscription") {
174
+ if (!credential) return "--credential is required when --auth oauth_subscription";
175
+ return { type: "oauth_subscription", credential };
176
+ }
177
+ return `Unknown auth type "${authType}". Use: api_key, platform, none, oauth_subscription`;
169
178
  }
170
179
 
171
180
  function writeCliError(msg: string, json?: boolean): void {
@@ -311,6 +320,141 @@ function attachDeleteSubcommand(connections: Command): void {
311
320
  });
312
321
  }
313
322
 
323
+ // ---------------------------------------------------------------------------
324
+ // OpenAI Codex OAuth config (PKCE, no client secret)
325
+ // ---------------------------------------------------------------------------
326
+
327
+ const OPENAI_CODEX_OAUTH_CONFIG: OAuth2Config = {
328
+ authorizeUrl: "https://auth.openai.com/oauth/authorize",
329
+ tokenExchangeUrl: "https://auth.openai.com/oauth/token",
330
+ clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
331
+ scopes: ["openid", "profile", "email", "offline_access"],
332
+ scopeSeparator: " ",
333
+ authorizeParams: { id_token_add_organizations: "true" },
334
+ };
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // Subcommand: login-chatgpt
338
+ // ---------------------------------------------------------------------------
339
+
340
+ function attachLoginChatgptSubcommand(providers: Command): void {
341
+ providers
342
+ .command("login-chatgpt")
343
+ .description("Authenticate with ChatGPT via browser OAuth flow")
344
+ .option("--json", "Output as JSON")
345
+ .action(async (opts: { json?: boolean }) => {
346
+ const config = getConfig();
347
+ if (!isAssistantFeatureFlagEnabled("chatgpt-subscription-auth", config)) {
348
+ writeCliError("This feature is not yet available", opts.json);
349
+ return;
350
+ }
351
+
352
+ try {
353
+ // Step 1: Run browser-based PKCE OAuth flow
354
+ process.stdout.write("Opening browser for ChatGPT authentication...\n");
355
+ const result = await startOAuth2Flow(
356
+ OPENAI_CODEX_OAUTH_CONFIG,
357
+ {
358
+ openUrl: (url) => {
359
+ Bun.spawn(["open", url]);
360
+ },
361
+ },
362
+ {
363
+ callbackTransport: "loopback",
364
+ loopbackPort: 1455,
365
+ loopbackCallbackPath: "/auth/callback",
366
+ },
367
+ );
368
+ const tokens = result.tokens;
369
+
370
+ // Step 2: Store tokens in CES
371
+ const accessStored = await setSecureKeyAsync(
372
+ "credential/chatgpt/access_token",
373
+ tokens.accessToken,
374
+ );
375
+ if (!accessStored) {
376
+ writeCliError("Failed to store access token", opts.json);
377
+ return;
378
+ }
379
+
380
+ if (tokens.refreshToken) {
381
+ const refreshStored = await setSecureKeyAsync(
382
+ "credential/chatgpt/refresh_token",
383
+ tokens.refreshToken,
384
+ );
385
+ if (!refreshStored) {
386
+ writeCliError("Failed to store refresh token", opts.json);
387
+ return;
388
+ }
389
+ }
390
+
391
+ if (tokens.expiresIn) {
392
+ const expiresAt = Math.floor(Date.now() / 1000 + tokens.expiresIn);
393
+ await setSecureKeyAsync(
394
+ "credential/chatgpt/expires_at",
395
+ String(expiresAt),
396
+ );
397
+ }
398
+
399
+ // Step 3: Create (or update) provider connection via IPC
400
+ const connectionName = "chatgpt-subscription";
401
+ const authInput = {
402
+ type: "oauth_subscription",
403
+ credential: "credential/chatgpt/access_token",
404
+ };
405
+
406
+ // Try to update first; if the connection doesn't exist, create it.
407
+ const updateResult = await cliIpcCall<ProviderConnection>(
408
+ "inference_provider_connections_update",
409
+ {
410
+ pathParams: { name: connectionName },
411
+ body: { auth: authInput },
412
+ },
413
+ );
414
+
415
+ if (!updateResult.ok) {
416
+ // Connection doesn't exist yet — create it
417
+ const createResult = await cliIpcCall<ProviderConnection>(
418
+ "inference_provider_connections_create",
419
+ {
420
+ body: {
421
+ name: connectionName,
422
+ provider: "openai",
423
+ auth: authInput,
424
+ },
425
+ },
426
+ );
427
+
428
+ if (!createResult.ok) {
429
+ writeCliError(
430
+ createResult.error ?? "Failed to create provider connection",
431
+ opts.json,
432
+ );
433
+ return;
434
+ }
435
+ }
436
+
437
+ if (opts.json) {
438
+ process.stdout.write(
439
+ JSON.stringify({
440
+ ok: true,
441
+ connection: connectionName,
442
+ message: "ChatGPT subscription auth configured successfully",
443
+ }) + "\n",
444
+ );
445
+ } else {
446
+ process.stdout.write(
447
+ `ChatGPT subscription auth configured successfully.\n` +
448
+ `Connection "${connectionName}" is ready (provider=openai, auth=oauth_subscription).\n`,
449
+ );
450
+ }
451
+ } catch (err) {
452
+ const message = err instanceof Error ? err.message : String(err);
453
+ writeCliError(message, opts.json);
454
+ }
455
+ });
456
+ }
457
+
314
458
  // ---------------------------------------------------------------------------
315
459
  // Registration
316
460
  // ---------------------------------------------------------------------------
@@ -349,4 +493,6 @@ Examples:
349
493
  attachCreateSubcommand(connections);
350
494
  attachUpdateSubcommand(connections);
351
495
  attachDeleteSubcommand(connections);
496
+
497
+ attachLoginChatgptSubcommand(providers);
352
498
  }
@@ -22,7 +22,9 @@ import { cliIpcCall } from "../../ipc/cli-client.js";
22
22
  import type {
23
23
  MemoryV2BackfillOp,
24
24
  MemoryV2BackfillResult,
25
+ MemoryV2EmaScoresResult,
25
26
  MemoryV2ReembedSkillsResult,
27
+ MemoryV2SimulateRouterResult,
26
28
  MemoryV2ValidateResult,
27
29
  } from "../../runtime/routes/memory-v2-routes.js";
28
30
  import { registerCommand } from "../lib/register-command.js";
@@ -252,6 +254,312 @@ Examples:
252
254
  process.exitCode = 1;
253
255
  }
254
256
  });
257
+
258
+ // ── ema ───────────────────────────────────────────────────────────────
259
+
260
+ v2.command("ema")
261
+ .description(
262
+ "List concept pages by injection-frequency EMA score (read-only)",
263
+ )
264
+ .option(
265
+ "-n, --limit <count>",
266
+ "Maximum rows to print (default 25; ignored with --all)",
267
+ "25",
268
+ )
269
+ .option("--all", "Print every page, including zero-score pages")
270
+ .option(
271
+ "--include-zeros",
272
+ "Include pages with score 0 in the default-limited view",
273
+ )
274
+ .option("--json", "Emit raw JSON instead of a formatted table")
275
+ .addHelpText(
276
+ "after",
277
+ `
278
+ EMA score is the time-decayed sum Σ exp(-λ × (now - tᵢ)) with a 3-day
279
+ half-life, computed from memory_v2_injection_events. A score of 1.0 means
280
+ roughly one router selection in the last few minutes; 0.5 means a single
281
+ selection ~3 days ago. Pages that have never been router-selected since
282
+ EMA tracking began report 0.
283
+
284
+ Examples:
285
+ $ assistant memory v2 ema
286
+ $ assistant memory v2 ema -n 100
287
+ $ assistant memory v2 ema --all --json | jq '.entries | length'`,
288
+ )
289
+ .action(
290
+ async (opts: {
291
+ limit: string;
292
+ all?: boolean;
293
+ includeZeros?: boolean;
294
+ json?: boolean;
295
+ }) => {
296
+ const result = await cliIpcCall<MemoryV2EmaScoresResult>(
297
+ "memory_v2_ema_scores",
298
+ { body: {} },
299
+ );
300
+
301
+ if (!result.ok) {
302
+ log.error(result.error ?? "Failed to fetch EMA scores");
303
+ process.exitCode = 1;
304
+ return;
305
+ }
306
+
307
+ const allEntries = result.result!.entries;
308
+ const includeZeros =
309
+ opts.all === true || opts.includeZeros === true;
310
+ const visible = includeZeros
311
+ ? allEntries
312
+ : allEntries.filter((e) => e.score > 0);
313
+
314
+ const limit =
315
+ opts.all === true ? visible.length : Number(opts.limit);
316
+ if (!opts.all && (!Number.isFinite(limit) || limit < 1)) {
317
+ log.error(
318
+ `--limit must be a positive integer (got "${opts.limit}")`,
319
+ );
320
+ process.exitCode = 1;
321
+ return;
322
+ }
323
+ const rows = visible.slice(0, limit);
324
+
325
+ if (opts.json === true) {
326
+ log.info(
327
+ JSON.stringify(
328
+ {
329
+ entries: rows,
330
+ totalScored: allEntries.filter((e) => e.score > 0).length,
331
+ totalPages: allEntries.length,
332
+ },
333
+ null,
334
+ 2,
335
+ ),
336
+ );
337
+ return;
338
+ }
339
+
340
+ if (rows.length === 0) {
341
+ log.info(
342
+ "No concept pages have any EMA signal yet. Send a few turns through the router and try again.",
343
+ );
344
+ return;
345
+ }
346
+
347
+ const slugWidth = Math.min(
348
+ 60,
349
+ Math.max(...rows.map((r) => r.slug.length)),
350
+ );
351
+ const header = `${"slug".padEnd(slugWidth)} ${"score".padStart(8)} modified`;
352
+ log.info(header);
353
+ log.info("-".repeat(header.length));
354
+ for (const row of rows) {
355
+ const slug =
356
+ row.slug.length > slugWidth
357
+ ? row.slug.slice(0, slugWidth - 1) + "…"
358
+ : row.slug.padEnd(slugWidth);
359
+ const score = row.score.toFixed(3).padStart(8);
360
+ const modified =
361
+ row.modifiedAt > 0
362
+ ? new Date(row.modifiedAt).toISOString().slice(0, 10)
363
+ : "—";
364
+ log.info(`${slug} ${score} ${modified}`);
365
+ }
366
+ const totalScored = allEntries.filter((e) => e.score > 0).length;
367
+ log.info(
368
+ `\n${rows.length} of ${visible.length} shown (${totalScored} total with score > 0, ${allEntries.length} pages indexed).`,
369
+ );
370
+ },
371
+ );
372
+
373
+ // ── simulate ──────────────────────────────────────────────────────────
374
+
375
+ v2.command("simulate")
376
+ .description(
377
+ "Dry-run the v4 router against a synthetic query (read-only)",
378
+ )
379
+ .requiredOption(
380
+ "-q, --query <text>",
381
+ "User query to route the simulated turn against",
382
+ )
383
+ .option(
384
+ "--tier1-size <n>",
385
+ "Override memory.v2.router.tier1_size for this run (number or 'null')",
386
+ )
387
+ .option(
388
+ "--tier2-size <n>",
389
+ "Override memory.v2.router.tier2_size for this run (number or 'null')",
390
+ )
391
+ .option(
392
+ "--batch-size <n>",
393
+ "Override memory.v2.router.batch_size for this run (number or 'null')",
394
+ )
395
+ .option("--json", "Emit raw JSON instead of a grouped report")
396
+ .addHelpText(
397
+ "after",
398
+ `
399
+ Runs the v4 router read-only against the live page index + EMA scores, with
400
+ optional tier/batch overrides applied on top of the live config. NO writes:
401
+ no row is appended to memory_v2_injection_events or memory_v2_activation_logs,
402
+ and no activation state is mutated. Use this to preview the effect of a
403
+ config knob change before flipping it in workspace config.json.
404
+
405
+ Limitations:
406
+ - priorEverInjected is empty (single-turn simulation; live router dedups
407
+ against pages already in context).
408
+ - NOW.md is read at simulate-time, not historical-turn time.
409
+ - assistantMessage is empty.
410
+
411
+ Pass 'null' to an override flag to explicitly disable that tier for this run
412
+ (e.g. --tier2-size null reverts to tier1 → tier3). Omitting an override
413
+ inherits the live config value.
414
+
415
+ Examples:
416
+ $ assistant memory v2 simulate -q "what should we ship next"
417
+ $ assistant memory v2 simulate -q "..." --tier1-size 100 --tier2-size 200 --batch-size 50
418
+ $ assistant memory v2 simulate -q "..." --json | jq '.selectedSlugs'`,
419
+ )
420
+ .action(
421
+ async (opts: {
422
+ query: string;
423
+ tier1Size?: string;
424
+ tier2Size?: string;
425
+ batchSize?: string;
426
+ json?: boolean;
427
+ }) => {
428
+ const parseOverride = (
429
+ flag: string,
430
+ raw: string | undefined,
431
+ ): number | null | undefined => {
432
+ if (raw === undefined) return undefined;
433
+ if (raw === "null") return null;
434
+ const parsed = Number(raw);
435
+ if (!Number.isInteger(parsed) || parsed < 1) {
436
+ log.error(
437
+ `${flag} must be a positive integer or 'null' (got "${raw}")`,
438
+ );
439
+ process.exitCode = 1;
440
+ throw new Error("invalid-override");
441
+ }
442
+ return parsed;
443
+ };
444
+
445
+ let configOverrides:
446
+ | {
447
+ tier1_size?: number | null;
448
+ tier2_size?: number | null;
449
+ batch_size?: number | null;
450
+ }
451
+ | undefined;
452
+ try {
453
+ const t1 = parseOverride("--tier1-size", opts.tier1Size);
454
+ const t2 = parseOverride("--tier2-size", opts.tier2Size);
455
+ const bs = parseOverride("--batch-size", opts.batchSize);
456
+ configOverrides = {
457
+ ...(t1 !== undefined ? { tier1_size: t1 } : {}),
458
+ ...(t2 !== undefined ? { tier2_size: t2 } : {}),
459
+ ...(bs !== undefined ? { batch_size: bs } : {}),
460
+ };
461
+ if (Object.keys(configOverrides).length === 0) {
462
+ configOverrides = undefined;
463
+ }
464
+ } catch {
465
+ return;
466
+ }
467
+
468
+ const result = await cliIpcCall<MemoryV2SimulateRouterResult>(
469
+ "memory_v2_simulate_router",
470
+ {
471
+ body: {
472
+ query: opts.query,
473
+ ...(configOverrides ? { configOverrides } : {}),
474
+ },
475
+ },
476
+ );
477
+
478
+ if (!result.ok) {
479
+ log.error(result.error ?? "Failed to simulate router");
480
+ process.exitCode = 1;
481
+ return;
482
+ }
483
+
484
+ const payload = result.result!;
485
+
486
+ if (opts.json === true) {
487
+ log.info(JSON.stringify(payload, null, 2));
488
+ return;
489
+ }
490
+
491
+ log.info("Memory Router Simulation");
492
+ log.info("========================");
493
+ log.info(`Query: ${JSON.stringify(opts.query)}`);
494
+ log.info("");
495
+ log.info("Config (effective):");
496
+ const formatKnob = (
497
+ key: keyof MemoryV2SimulateRouterResult["effectiveConfig"],
498
+ ): string => {
499
+ const eff = payload.effectiveConfig[key];
500
+ const override = (
501
+ payload.overrides as Record<string, number | null | undefined>
502
+ )[key];
503
+ const effStr = eff === null ? "null" : String(eff);
504
+ if (override === undefined) {
505
+ return ` ${key}: ${effStr}`;
506
+ }
507
+ return ` ${key}: ${effStr} (override)`;
508
+ };
509
+ log.info(formatKnob("tier1_size"));
510
+ log.info(formatKnob("tier2_size"));
511
+ log.info(formatKnob("batch_size"));
512
+ log.info(` max_page_ids: ${payload.effectiveConfig.max_page_ids}`);
513
+ log.info("");
514
+ log.info(`Total candidate pages: ${payload.totalCandidatePages}`);
515
+ log.info(
516
+ `Selected: ${payload.selectedSlugs.length} / ${payload.effectiveConfig.max_page_ids} pages`,
517
+ );
518
+ if (payload.failureReason) {
519
+ log.info(`Failure: ${payload.failureReason}`);
520
+ }
521
+ log.info("");
522
+
523
+ const grouped = new Map<string, string[]>();
524
+ for (const slug of payload.selectedSlugs) {
525
+ const source = payload.sourceBySlug[slug] ?? "unknown";
526
+ const bucket = grouped.get(source) ?? [];
527
+ bucket.push(slug);
528
+ grouped.set(source, bucket);
529
+ }
530
+ const sortedKeys = [...grouped.keys()].sort((a, b) => {
531
+ const order = (s: string) => {
532
+ if (s === "tier1") return 0;
533
+ if (s === "tier2") return 1;
534
+ if (s.startsWith("tier3:")) {
535
+ return 2 + Number(s.slice("tier3:".length));
536
+ }
537
+ return Number.MAX_SAFE_INTEGER;
538
+ };
539
+ return order(a) - order(b);
540
+ });
541
+
542
+ for (const key of sortedKeys) {
543
+ const label = key.startsWith("tier3:")
544
+ ? `tier 3 · b${key.slice("tier3:".length)}`
545
+ : key === "tier1"
546
+ ? "tier 1"
547
+ : key === "tier2"
548
+ ? "tier 2"
549
+ : key;
550
+ log.info(label);
551
+ for (const slug of grouped.get(key)!) {
552
+ if (key === "tier2") {
553
+ const score = payload.scores[slug] ?? 0;
554
+ log.info(` - ${slug} (EMA ${score.toFixed(3)})`);
555
+ } else {
556
+ log.info(` - ${slug}`);
557
+ }
558
+ }
559
+ log.info("");
560
+ }
561
+ },
562
+ );
255
563
  },
256
564
  });
257
565
  }
@@ -8,6 +8,7 @@ import {
8
8
  import { registerCommand } from "../lib/register-command.js";
9
9
  import { log } from "../logger.js";
10
10
  import { shouldOutputJson, writeOutput } from "../output.js";
11
+ import { tryResolveConversationId } from "../utils/conversation-id.js";
11
12
 
12
13
  // ---------------------------------------------------------------------------
13
14
  // Command registration
@@ -248,8 +249,6 @@ Examples:
248
249
  }
249
250
  }
250
251
 
251
- const sourceContextId = opts.sessionId ?? `cli-${Date.now()}`;
252
-
253
252
  // Validate --conversation-id if provided
254
253
  const conversationId = opts.conversationId?.trim();
255
254
  if (opts.conversationId != null && !conversationId) {
@@ -261,6 +260,26 @@ Examples:
261
260
  return;
262
261
  }
263
262
 
263
+ // Picks up __CONVERSATION_ID / __SKILL_CONTEXT_JSON env vars
264
+ // so deferred-emit can buffer notifications when called from a
265
+ // background job that hasn't confirmed success yet.
266
+ const originatingConversationId = tryResolveConversationId();
267
+
268
+ // The signal's `sourceContextId` doubles as the home-feed's
269
+ // navigation target — `resolveHomeFeedMirror` looks it up via
270
+ // `getConversation()` and only renders a "Go to Convo" button
271
+ // when it resolves to a real row. Prefer the conversation the
272
+ // CLI was invoked from (env-derived) so notifications emitted
273
+ // by background jobs and skills link back to their producing
274
+ // convo; an explicit --session-id still wins to preserve
275
+ // caller intent, and --conversation-id is the last resort
276
+ // before the unresolvable `cli-<ts>` sentinel.
277
+ const sourceContextId =
278
+ opts.sessionId ??
279
+ originatingConversationId ??
280
+ conversationId ??
281
+ `cli-${Date.now()}`;
282
+
264
283
  const result = await cliIpcCall<{
265
284
  signalId: string;
266
285
  dispatched: boolean;
@@ -289,6 +308,9 @@ Examples:
289
308
  ...(conversationId
290
309
  ? { conversationAffinityHint: { vellum: conversationId } }
291
310
  : {}),
311
+ ...(originatingConversationId
312
+ ? { originatingConversationId }
313
+ : {}),
292
314
  throwOnError: true,
293
315
  },
294
316
  });