@vellumai/assistant 0.8.12 → 0.9.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/AGENTS.md +0 -14
  2. package/ARCHITECTURE.md +45 -45
  3. package/README.md +1 -1
  4. package/bun.lock +200 -154
  5. package/docs/architecture/integrations.md +3 -3
  6. package/docs/architecture/memory.md +2 -2
  7. package/docs/architecture/security.md +10 -10
  8. package/docs/runbook-trusted-contacts.md +12 -12
  9. package/docs/skills.md +6 -6
  10. package/docs/workflows-testing.md +221 -0
  11. package/docs/workflows.md +510 -0
  12. package/examples/plugins/echo/README.md +5 -5
  13. package/knip.json +2 -0
  14. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
  15. package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
  16. package/openapi.yaml +7197 -5708
  17. package/package.json +8 -4
  18. package/scripts/generate-openapi.ts +66 -114
  19. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  20. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  21. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  22. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  24. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  25. package/src/__tests__/app-compiler.test.ts +7 -1
  26. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  27. package/src/__tests__/app-executors.test.ts +43 -0
  28. package/src/__tests__/approval-cascade.test.ts +0 -5
  29. package/src/__tests__/approval-routes-http.test.ts +91 -0
  30. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  31. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  32. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  33. package/src/__tests__/compaction-events.test.ts +63 -7
  34. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  35. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  36. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  37. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  39. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  40. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  41. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  43. package/src/__tests__/conversation-queue.test.ts +0 -5
  44. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  45. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  46. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  47. package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
  48. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  49. package/src/__tests__/conversation-usage.test.ts +2 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  52. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  53. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  54. package/src/__tests__/credential-broker.test.ts +1 -1
  55. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault.test.ts +37 -0
  58. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  59. package/src/__tests__/dynamic-page-surface.test.ts +219 -0
  60. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  61. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  62. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  63. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  64. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  65. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  66. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  67. package/src/__tests__/llm-resolver.test.ts +115 -0
  68. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  69. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  70. package/src/__tests__/media-generate-image.test.ts +20 -9
  71. package/src/__tests__/mock-gateway-ipc.ts +23 -0
  72. package/src/__tests__/model-intents.test.ts +1 -1
  73. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  74. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  75. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  76. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  77. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  78. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  79. package/src/__tests__/resolve-app-id.test.ts +56 -0
  80. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  81. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  82. package/src/__tests__/schedule-routes.test.ts +257 -4
  83. package/src/__tests__/schedule-store.test.ts +60 -0
  84. package/src/__tests__/schedule-tools.test.ts +247 -2
  85. package/src/__tests__/skill-execute-input.test.ts +85 -0
  86. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  87. package/src/__tests__/skills.test.ts +3 -3
  88. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  89. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  90. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  91. package/src/__tests__/system-prompt.test.ts +1 -1
  92. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  93. package/src/__tests__/task-scheduler.test.ts +299 -0
  94. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  95. package/src/__tests__/tool-result-spool.test.ts +3 -1
  96. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  97. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  98. package/src/agent/compaction-circuit.ts +11 -0
  99. package/src/agent/loop.ts +181 -12
  100. package/src/api/constants/call-sites.ts +12 -0
  101. package/src/api/events/assistant-thinking-delta.ts +10 -0
  102. package/src/api/events/usage-update.ts +7 -0
  103. package/src/api/index.ts +4 -1
  104. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  105. package/src/approvals/approval-primitive.ts +2 -2
  106. package/src/background-wake/background-wake-routes.test.ts +5 -2
  107. package/src/bundler/compiler-tools.ts +1 -1
  108. package/src/bundler/package-resolver.ts +0 -1
  109. package/src/calls/call-domain.ts +1 -1
  110. package/src/calls/guardian-action-sweep.ts +16 -93
  111. package/src/cli/AGENTS.md +4 -0
  112. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  113. package/src/cli/commands/credentials.ts +28 -24
  114. package/src/cli/commands/image-generation.ts +23 -9
  115. package/src/cli/commands/notifications.ts +1 -1
  116. package/src/cli/commands/plugins.ts +89 -46
  117. package/src/cli/commands/schedules.ts +384 -11
  118. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  119. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  120. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  121. package/src/cli/lib/inspect-plugin.ts +62 -1
  122. package/src/cli/lib/install-from-github.ts +52 -5
  123. package/src/cli/lib/upgrade-plugin.ts +18 -0
  124. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  125. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  126. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  127. package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
  128. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
  129. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
  130. package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
  131. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  132. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  133. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  134. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  135. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  136. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  137. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  138. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  139. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  140. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  141. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  142. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  143. package/src/config/bundled-tool-registry.ts +14 -2
  144. package/src/config/call-site-defaults.ts +5 -0
  145. package/src/config/feature-flag-registry.json +12 -4
  146. package/src/config/llm-context-resolution.ts +8 -0
  147. package/src/config/llm-resolver.ts +30 -0
  148. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  149. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  150. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  151. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  152. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  153. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  154. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  155. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  156. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  157. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  158. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  159. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  160. package/src/config/schema.ts +2 -0
  161. package/src/config/schemas/call-site-catalog.ts +7 -0
  162. package/src/config/schemas/heartbeat.ts +4 -1
  163. package/src/config/schemas/llm.ts +33 -26
  164. package/src/config/schemas/memory-retrospective.ts +19 -0
  165. package/src/config/schemas/platform.ts +8 -0
  166. package/src/config/schemas/services.ts +5 -2
  167. package/src/config/schemas/workflows.ts +42 -0
  168. package/src/config/skills.ts +3 -3
  169. package/src/context/compactor.ts +273 -39
  170. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  171. package/src/context/tool-result-spool.ts +12 -17
  172. package/src/credential-execution/executable-discovery.ts +1 -1
  173. package/src/credential-execution/process-manager.ts +37 -3
  174. package/src/credential-execution/prompted-credential.ts +205 -0
  175. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  176. package/src/daemon/conversation-process.ts +11 -2
  177. package/src/daemon/conversation-surfaces.ts +167 -3
  178. package/src/daemon/conversation-tool-setup.ts +103 -26
  179. package/src/daemon/conversation-usage.ts +2 -0
  180. package/src/daemon/conversation.ts +115 -11
  181. package/src/daemon/handlers/shared.ts +26 -14
  182. package/src/daemon/host-cu-proxy.ts +15 -12
  183. package/src/daemon/host-file-proxy.ts +15 -12
  184. package/src/daemon/host-transfer-proxy.ts +30 -24
  185. package/src/daemon/lifecycle.ts +40 -3
  186. package/src/daemon/message-protocol.ts +3 -0
  187. package/src/daemon/message-types/messages.ts +2 -10
  188. package/src/daemon/message-types/workflows.ts +49 -0
  189. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  190. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  191. package/src/daemon/process-message.ts +6 -0
  192. package/src/daemon/tool-setup-types.ts +57 -0
  193. package/src/daemon/wake-conversation-ops.ts +18 -0
  194. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  195. package/src/home/feed-types.ts +1 -1
  196. package/src/ipc/gateway-flag-listener.ts +28 -6
  197. package/src/mcp/mcp-auth-state.ts +8 -20
  198. package/src/media/__tests__/image-models.test.ts +57 -0
  199. package/src/media/image-models.ts +66 -0
  200. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  201. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  202. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  203. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  204. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  205. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  206. package/src/memory/auto-analysis-enqueue.ts +14 -1
  207. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  208. package/src/memory/conversation-crud.ts +9 -2
  209. package/src/memory/conversation-disk-view.ts +1 -1
  210. package/src/memory/conversation-queries.ts +22 -7
  211. package/src/memory/db-init.ts +20 -0
  212. package/src/memory/db-maintenance.ts +16 -0
  213. package/src/memory/embedding-runtime-manager.ts +1 -1
  214. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  215. package/src/memory/llm-request-log-source-local.ts +19 -1
  216. package/src/memory/llm-request-log-source.ts +35 -6
  217. package/src/memory/llm-request-log-store.ts +90 -2
  218. package/src/memory/memory-retrospective-constants.ts +9 -0
  219. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  220. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  221. package/src/memory/memory-retrospective-job.ts +500 -208
  222. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  223. package/src/memory/memory-retrospective-state.ts +85 -2
  224. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  225. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  226. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  227. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  228. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  229. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  230. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  231. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  232. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  233. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  234. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  235. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  236. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  237. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  238. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  239. package/src/memory/migrations/index.ts +10 -0
  240. package/src/memory/preloaded-apps.ts +116 -0
  241. package/src/memory/schema/infrastructure.ts +4 -0
  242. package/src/memory/schema/memory-core.ts +4 -0
  243. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  244. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  245. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  246. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  247. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  248. package/src/memory/v2/page-store.ts +4 -3
  249. package/src/memory/v2/qdrant.ts +42 -3
  250. package/src/memory/v2/types.ts +16 -10
  251. package/src/messaging/draft-store.ts +1 -1
  252. package/src/notifications/access-request-copy.ts +200 -113
  253. package/src/notifications/adapters/slack.ts +250 -111
  254. package/src/notifications/adapters/telegram.ts +7 -44
  255. package/src/notifications/approval-card-builder.ts +93 -0
  256. package/src/notifications/broadcaster.ts +74 -0
  257. package/src/notifications/conversation-pairing.ts +8 -6
  258. package/src/notifications/copy-composer.ts +32 -26
  259. package/src/notifications/decision-engine.ts +59 -7
  260. package/src/notifications/guardian-question-mode.ts +145 -155
  261. package/src/notifications/home-feed-side-effect.ts +28 -11
  262. package/src/notifications/notification-utils.ts +66 -0
  263. package/src/notifications/signal.ts +6 -0
  264. package/src/notifications/tool-approval-copy.ts +142 -0
  265. package/src/notifications/types.ts +19 -0
  266. package/src/permissions/threshold.ts +11 -0
  267. package/src/plugin-api/types.ts +16 -4
  268. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  269. package/src/plugins/defaults/index.ts +46 -0
  270. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  271. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  272. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  273. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  274. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  275. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  276. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  277. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  278. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  282. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  283. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  284. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  285. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  286. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  287. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  288. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  289. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  290. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  291. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  292. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  293. package/src/prompts/normalize-onboarding.ts +2 -0
  294. package/src/prompts/persona-resolver.ts +3 -0
  295. package/src/prompts/system-prompt.ts +51 -2
  296. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  297. package/src/prompts/templates/system-sections.ts +8 -3
  298. package/src/providers/call-site-routing.ts +6 -3
  299. package/src/providers/fireworks/client.ts +3 -0
  300. package/src/providers/inference/auth.ts +52 -46
  301. package/src/providers/model-intents.ts +2 -2
  302. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  303. package/src/providers/openai/chat-completions-provider.ts +47 -9
  304. package/src/providers/openai/coerce-object-args.ts +104 -0
  305. package/src/providers/retry.ts +8 -5
  306. package/src/providers/types.ts +10 -0
  307. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  308. package/src/runtime/access-request-helper.ts +16 -9
  309. package/src/runtime/agent-wake.ts +302 -51
  310. package/src/runtime/assistant-stream-state.ts +141 -8
  311. package/src/runtime/background-job-runner.ts +9 -0
  312. package/src/runtime/channel-approval-types.ts +1 -0
  313. package/src/runtime/channel-invite-transports/telegram.ts +6 -5
  314. package/src/runtime/channel-invite-transports/voice.ts +2 -2
  315. package/src/runtime/channel-invite-types.ts +4 -2
  316. package/src/runtime/channel-retry-sweep.ts +19 -41
  317. package/src/runtime/finalize-event-delivery.ts +72 -0
  318. package/src/runtime/guardian-action-message-composer.ts +0 -54
  319. package/src/runtime/http-server.ts +6 -14
  320. package/src/runtime/http-types.ts +0 -1
  321. package/src/runtime/message-composer-types.ts +0 -9
  322. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  323. package/src/runtime/middleware/auth.ts +27 -3
  324. package/src/runtime/middleware/rate-limiter.ts +28 -1
  325. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  326. package/src/runtime/pending-interactions.ts +20 -1
  327. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  328. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  329. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  330. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  331. package/src/runtime/routes/approval-routes.ts +35 -8
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  333. package/src/runtime/routes/btw-routes.ts +37 -1
  334. package/src/runtime/routes/channel-delivery-routes.ts +11 -7
  335. package/src/runtime/routes/channel-route-definitions.ts +3 -0
  336. package/src/runtime/routes/channel-route-shared.ts +3 -1
  337. package/src/runtime/routes/consolidation-routes.ts +17 -13
  338. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  339. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  340. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  341. package/src/runtime/routes/conversation-routes.ts +15 -1
  342. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  343. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  344. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  345. package/src/runtime/routes/image-generation-routes.ts +20 -2
  346. package/src/runtime/routes/inbound-message-handler.ts +13 -12
  347. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
  348. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
  349. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
  350. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
  351. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
  352. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
  353. package/src/runtime/routes/index.ts +6 -0
  354. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  355. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  356. package/src/runtime/routes/migration-routes.ts +5 -9
  357. package/src/runtime/routes/plugins-routes.ts +26 -0
  358. package/src/runtime/routes/ps-routes.ts +10 -8
  359. package/src/runtime/routes/retrospective-routes.ts +235 -0
  360. package/src/runtime/routes/runs-pagination.ts +75 -0
  361. package/src/runtime/routes/schedule-routes.ts +367 -59
  362. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  363. package/src/runtime/routes/surface-action-routes.ts +84 -4
  364. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  365. package/src/runtime/routes/workflow-routes.ts +363 -0
  366. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  367. package/src/runtime/routes/workspace-routes.ts +26 -1
  368. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  369. package/src/runtime/services/analyze-conversation.ts +26 -13
  370. package/src/schedule/inference-profile.ts +28 -0
  371. package/src/schedule/schedule-store.ts +152 -4
  372. package/src/schedule/scheduler-types.ts +6 -0
  373. package/src/schedule/scheduler.ts +96 -0
  374. package/src/security/secret-allowlist.ts +1 -1
  375. package/src/skills/path-classifier.ts +1 -1
  376. package/src/tools/apps/executors.ts +23 -0
  377. package/src/tools/apps/resolve-app-id.ts +42 -0
  378. package/src/tools/browser/browser-execution.ts +9 -11
  379. package/src/tools/credentials/broker.ts +4 -4
  380. package/src/tools/credentials/vault.ts +26 -137
  381. package/src/tools/executor.ts +69 -0
  382. package/src/tools/flag-gated-tools.test.ts +76 -0
  383. package/src/tools/permission-checker.ts +8 -1
  384. package/src/tools/registry.ts +51 -0
  385. package/src/tools/schedule/create.ts +77 -1
  386. package/src/tools/schedule/list.ts +1 -0
  387. package/src/tools/schedule/update.ts +73 -1
  388. package/src/tools/skills/execute.ts +56 -0
  389. package/src/tools/terminal/shell.ts +1 -1
  390. package/src/tools/ui-surface/definitions.ts +91 -2
  391. package/src/tools/workflows/manage-workflows.ts +183 -0
  392. package/src/tools/workflows/run-workflow.test.ts +442 -0
  393. package/src/tools/workflows/run-workflow.ts +88 -0
  394. package/src/types/onboarding-context.ts +2 -0
  395. package/src/usage/attribution.ts +24 -0
  396. package/src/util/canonicalize-identity.ts +12 -3
  397. package/src/util/platform.ts +17 -17
  398. package/src/watcher/__tests__/engine.test.ts +24 -0
  399. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  400. package/src/watcher/engine.ts +7 -0
  401. package/src/watcher/telemetry.ts +74 -0
  402. package/src/workflows/capabilities.test.ts +365 -0
  403. package/src/workflows/capabilities.ts +359 -0
  404. package/src/workflows/deterministic-stringify.ts +27 -0
  405. package/src/workflows/engine-integration.test.ts +656 -0
  406. package/src/workflows/engine.test.ts +1144 -0
  407. package/src/workflows/engine.ts +1078 -0
  408. package/src/workflows/fanout-load.test.ts +168 -0
  409. package/src/workflows/journal-store.test.ts +369 -0
  410. package/src/workflows/journal-store.ts +470 -0
  411. package/src/workflows/leaf-runner.test.ts +704 -0
  412. package/src/workflows/leaf-runner.ts +589 -0
  413. package/src/workflows/library.test.ts +134 -0
  414. package/src/workflows/library.ts +124 -0
  415. package/src/workflows/run-manager.test.ts +711 -0
  416. package/src/workflows/run-manager.ts +593 -0
  417. package/src/workflows/sandbox-escape.test.ts +339 -0
  418. package/src/workflows/sandbox.test.ts +251 -0
  419. package/src/workflows/sandbox.ts +447 -0
  420. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  421. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  422. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  423. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  424. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  425. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  426. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  427. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  428. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  429. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  430. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  431. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  432. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  433. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  434. package/src/workspace/migrations/registry.ts +6 -0
  435. package/src/workspace/migrations/runner.ts +1 -1
  436. package/tsconfig.json +1 -1
  437. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  438. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  439. package/src/__tests__/guardian-action-store.test.ts +0 -106
  440. package/src/daemon/guardian-action-generators.ts +0 -71
  441. package/src/memory/guardian-action-store.ts +0 -484
  442. package/src/runtime/guardian-action-grant-minter.ts +0 -150
@@ -14,6 +14,7 @@ import {
14
14
  inspectPlugin,
15
15
  type PluginInspection,
16
16
  PluginInspectNotFoundError,
17
+ type PluginRemoteInfo,
17
18
  } from "../lib/inspect-plugin.js";
18
19
  import {
19
20
  DEFAULT_PLUGIN_REF,
@@ -380,33 +381,44 @@ function shortSha(sha: string | null): string {
380
381
  return sha ? sha.slice(0, 7) : "—";
381
382
  }
382
383
 
383
- /** Human-readable status line for an inspection result. */
384
- function statusLine(inspection: PluginInspection): string {
385
- const { status, local, remote } = inspection;
384
+ /**
385
+ * Render a commit timestamp as the human-facing version: a UTC `YYYY-MM-DDThh:mm:ss`
386
+ * string (seconds precision, no fractional or zone suffix). Older installs with
387
+ * no recorded commit date — and remote pins whose date could not be fetched —
388
+ * fall back to `unknown`, with the SHA still shown alongside as the precise id.
389
+ */
390
+ function formatTimestamp(iso: string | null): string {
391
+ if (!iso) return "unknown";
392
+ const ms = Date.parse(iso);
393
+ if (!Number.isFinite(ms)) return "unknown";
394
+ return new Date(ms).toISOString().slice(0, 19);
395
+ }
396
+
397
+ /** Human-readable status line for an inspection result. The from/to revisions
398
+ * now live in the installed/remote blocks, so the status itself is just words. */
399
+ function statusLine(status: PluginInspection["status"]): string {
386
400
  switch (status) {
387
401
  case "up-to-date":
388
402
  return "up to date";
389
403
  case "update-available":
390
- return `update available (${shortSha(local?.commit ?? null)} → ${shortSha(
391
- remote?.commit ?? null,
392
- )})`;
404
+ return "update available";
393
405
  case "not-installed":
394
406
  return "not installed";
395
407
  case "not-in-marketplace":
396
408
  return "installed (not in marketplace)";
397
409
  case "unknown-provenance":
398
- return "unknown reinstall to record provenance";
410
+ return "unknown (reinstall to record provenance)";
399
411
  case "remote-unavailable":
400
412
  return "installed (marketplace unavailable)";
401
413
  }
402
414
  }
403
415
 
404
416
  /**
405
- * Summarize local edits relative to the install-time fingerprint. Reports
406
- * "unknown" when no baseline was recorded (an older or manually-copied
407
- * install), "none" when the on-disk tree matches, or a per-category count.
417
+ * Summarize drift relative to the install-time fingerprint. Reports "unknown"
418
+ * when no baseline was recorded (an older or manually-copied install), "none"
419
+ * when the on-disk tree matches, or a per-category count.
408
420
  */
409
- function localEditsLine(changes: FingerprintComparison | null): string {
421
+ function driftLine(changes: FingerprintComparison | null): string {
410
422
  if (!changes)
411
423
  return "unknown (no recorded baseline; reinstall to record one)";
412
424
  if (changes.clean) return "none";
@@ -418,74 +430,105 @@ function localEditsLine(changes: FingerprintComparison | null): string {
418
430
  return parts.join(", ");
419
431
  }
420
432
 
421
- /** Render an inspection as aligned, human-readable summary lines. */
433
+ /** Build the GitHub web URL for a remote pin's location (repo, or repo subtree). */
434
+ function remoteLocation(remote: PluginRemoteInfo): string {
435
+ const base = `https://github.com/${remote.repo}`;
436
+ return remote.path ? `${base}/tree/${remote.commit}/${remote.path}` : base;
437
+ }
438
+
439
+ /**
440
+ * Render an inspection with timestamps as the headline version. The layout is a
441
+ * name heading + rule, a top-level `status`, then `installed`/`remote` blocks
442
+ * that each carry `timestamp` (the human version), `hash` (the precise id), and
443
+ * `location`, with a `drift` line under the installed copy.
444
+ */
422
445
  function formatInspection(inspection: PluginInspection): string[] {
423
- const { name, local, remote, remoteError } = inspection;
424
- const lines: string[] = [`${name} plugin`];
425
- const row = (label: string, value: string) =>
426
- lines.push(` ${label.padEnd(11)} ${value}`);
446
+ const { name, status, local, remote, remoteError } = inspection;
447
+ const lines: string[] = [name, "─".repeat(44)];
448
+ const topRow = (label: string, value: string) =>
449
+ lines.push(`${label.padEnd(11)} ${value}`);
450
+ const blockRow = (label: string, value: string) =>
451
+ lines.push(` ${label.padEnd(9)} ${value}`);
427
452
 
428
- row("status", statusLine(inspection));
453
+ topRow("status", statusLine(status));
429
454
 
430
455
  if (local) {
431
- const installedAt = local.installedAt
432
- ? ` (${local.installedAt.slice(0, 10)})`
433
- : "";
434
- row("installed", `${shortSha(local.commit)}${installedAt}`);
435
- row("local edits", localEditsLine(local.localChanges));
456
+ lines.push("installed");
457
+ blockRow("timestamp", formatTimestamp(local.committedAt));
458
+ blockRow("hash", shortSha(local.commit));
459
+ blockRow("location", local.target);
460
+ // `installedAt` is rewritten on every install/upgrade, so it reads as the
461
+ // last time this copy was materialized rather than a first-install date.
462
+ blockRow("updated", formatTimestamp(local.installedAt));
463
+ topRow("drift", driftLine(local.localChanges));
436
464
  }
437
465
 
438
466
  if (remote) {
439
- const where = remote.path ? `${remote.repo}/${remote.path}` : remote.repo;
440
- row(
441
- "remote pin",
442
- `${shortSha(remote.commit)} (${where}, ${remote.marketplaceRef})`,
443
- );
467
+ lines.push("remote");
468
+ blockRow("timestamp", formatTimestamp(remote.committedAt));
469
+ blockRow("hash", shortSha(remote.commit));
470
+ blockRow("location", remoteLocation(remote));
444
471
  } else if (remoteError) {
445
- row("remote", `unavailable ${remoteError}`);
472
+ topRow("remote", `unavailable: ${remoteError}`);
446
473
  }
447
474
 
448
- const version = local?.version ?? null;
449
- if (version) row("version", version);
475
+ const pkgVersion = local?.version ?? null;
476
+ if (pkgVersion) topRow("pkg version", pkgVersion);
450
477
 
451
478
  const license = remote?.license ?? null;
452
479
  const homepage = remote?.homepage ?? null;
453
- if (license && homepage) row("license", `${license} homepage ${homepage}`);
454
- else if (license) row("license", license);
455
- else if (homepage) row("homepage", homepage);
480
+ if (license) topRow("license", license);
481
+ if (homepage) topRow("homepage", homepage);
456
482
 
457
483
  const description = remote?.description ?? local?.description ?? null;
458
- if (description) row("description", description);
484
+ if (description) topRow("description", description);
459
485
 
460
- for (const issue of local?.issues ?? []) row("issue", issue);
486
+ for (const issue of local?.issues ?? []) topRow("issue", issue);
461
487
 
462
488
  return lines;
463
489
  }
464
490
 
465
- /** Render an upgrade result as human-readable summary lines. */
491
+ /**
492
+ * Render an upgrade result with the `timestamp (hash) → timestamp (hash)` move
493
+ * as the headline, mirroring the inspect layout so the same version identity is
494
+ * used everywhere.
495
+ */
466
496
  function formatUpgrade(result: PluginUpgradeResult): string[] {
467
- const { name, fromCommit, toCommit } = result;
468
- const move = `${shortSha(fromCommit)} → ${shortSha(toCommit)}`;
497
+ const { name, fromCommit, fromTimestamp, toCommit, toTimestamp } = result;
498
+ const move =
499
+ `${formatTimestamp(fromTimestamp)} (${shortSha(fromCommit)})` +
500
+ ` → ${formatTimestamp(toTimestamp)} (${shortSha(toCommit)})`;
469
501
  const provenanceNote = result.provenanceWasUnknown
470
- ? " (previous install had no recorded provenance)"
471
- : "";
502
+ ? "Previous install had no recorded provenance; it has been re-pinned."
503
+ : null;
472
504
 
473
505
  switch (result.outcome) {
474
506
  case "already-up-to-date":
475
- return [`"${name}" is already up to date (${shortSha(toCommit)}).`];
476
- case "would-upgrade":
477
507
  return [
478
- `"${name}" would upgrade ${move}${provenanceNote} (dry run; no changes made).`,
508
+ `"${name}" is already up to date at ${formatTimestamp(toTimestamp)} (${shortSha(toCommit)}).`,
479
509
  ];
510
+ case "would-upgrade": {
511
+ const lines = [
512
+ `"${name}" would upgrade ${move}`,
513
+ "",
514
+ "dry run; no changes made.",
515
+ ];
516
+ if (provenanceNote) lines.push(provenanceNote);
517
+ return lines;
518
+ }
480
519
  case "upgraded": {
481
520
  const count =
482
521
  result.fileCount === null
483
522
  ? ""
484
- : ` (${result.fileCount} file${result.fileCount === 1 ? "" : "s"})`;
485
- return [
486
- `Upgraded "${name}" ${move}${count}${provenanceNote} → ${result.target}`,
523
+ : `(${result.fileCount} file${result.fileCount === 1 ? "" : "s"}) `;
524
+ const lines = [
525
+ `Upgraded "${name}" ${move}`,
526
+ "",
527
+ `${count}→ ${result.target}`,
487
528
  "Restart the assistant to pick up the upgrade.",
488
529
  ];
530
+ if (provenanceNote) lines.push(provenanceNote);
531
+ return lines;
489
532
  }
490
533
  }
491
534
  }
@@ -22,6 +22,9 @@ interface ScheduleRecord {
22
22
  retryCount: number;
23
23
  maxRetries: number;
24
24
  retryBackoffMs: number;
25
+ timeoutMs: number | null;
26
+ inferenceProfile: string | null;
27
+ createdFromConversationId: string | null;
25
28
  description: string | null;
26
29
  cadenceDescription: string | null;
27
30
  mode: string;
@@ -36,6 +39,10 @@ interface ListSchedulesResponse {
36
39
  schedules: ScheduleRecord[];
37
40
  }
38
41
 
42
+ interface GetScheduleResponse {
43
+ schedule: ScheduleRecord;
44
+ }
45
+
39
46
  interface ScheduleRunRecord {
40
47
  id: string;
41
48
  jobId: string;
@@ -64,19 +71,17 @@ export function registerSchedulesCommand(program: Command): void {
64
71
  `
65
72
  Schedules are recurring or one-shot jobs run by the assistant.
66
73
 
67
- This CLI namespace is intentionally landing incrementally. Today it supports
68
- creating schedules, listing schedules, viewing recent run history,
69
- enabling/disabling schedules, manually executing a schedule one time, cancelling
70
- pending one-shot schedules, and deleting schedules.
74
+ This namespace exposes full CRUD against the assistant's schedule store:
75
+ create, list, get, update, enable/disable, cancel, delete, plus run history
76
+ and manual one-time execution. One documented exception: 'create' is limited
77
+ to 'execute' mode — notify/script/wake schedules are created via the
78
+ in-assistant schedule_create tool, but can be inspected and updated here.
71
79
 
72
80
  Examples:
73
81
  $ assistant schedules list
74
- $ assistant schedules list --all
75
- $ assistant schedules runs <schedule-id>
82
+ $ assistant schedules get <schedule-id>
83
+ $ assistant schedules update <schedule-id> --expression '0 9 * * *'
76
84
  $ assistant schedules runs <schedule-id> --limit 25 --json
77
- $ assistant schedules disable <schedule-id>
78
- $ assistant schedules enable <schedule-id>
79
- $ assistant schedules cancel <schedule-id>
80
85
  $ assistant schedules execute <schedule-id>`,
81
86
  );
82
87
 
@@ -199,6 +204,94 @@ Examples:
199
204
  },
200
205
  );
201
206
 
207
+ schedules
208
+ .command("get <id>")
209
+ .alias("inspect")
210
+ .description("Show full details for a single schedule")
211
+ .option("--json", "Machine-readable compact JSON output")
212
+ .addHelpText(
213
+ "after",
214
+ `
215
+ Options:
216
+ --json Output the raw schedule object as compact JSON.
217
+
218
+ Arguments:
219
+ <id> Schedule ID (UUID) — run 'assistant schedules list --all' to find it.
220
+
221
+ Behavior:
222
+ Prints every stored field of the schedule: name, description, mode,
223
+ expression/syntax, timezone, enabled state, status, next/last run times,
224
+ message or script body, inference profile (shown as 'default (mainAgent)'
225
+ when none is pinned), routing intent, and retry policy. Works for
226
+ deferred schedules that 'assistant schedules list' hides by default.
227
+ Aliased as 'inspect'.
228
+
229
+ Examples:
230
+ $ assistant schedules get 9f2c4f3a-3f1a-41e4-88e7-abc123
231
+ $ assistant schedules inspect 9f2c4f3a-3f1a-41e4-88e7-abc123
232
+ $ assistant schedules get 9f2c4f3a-3f1a-41e4-88e7-abc123 --json`,
233
+ )
234
+ .action(async (id: string, opts: { json?: boolean }, cmd: Command) => {
235
+ const scheduleId = id.trim();
236
+ const result = await cliIpcCall<GetScheduleResponse>("getSchedule", {
237
+ pathParams: { id: scheduleId },
238
+ });
239
+
240
+ if (!result.ok) return exitFromIpcResult(result, cmd);
241
+
242
+ const schedule = result.result?.schedule;
243
+ if (!schedule) {
244
+ const error = `Schedule "${scheduleId}" not found. Run 'assistant schedules list --all' to see available schedules.`;
245
+ if (opts.json) {
246
+ writeOutput(cmd, { ok: false, error });
247
+ } else {
248
+ log.error(error);
249
+ }
250
+ process.exitCode = 1;
251
+ return;
252
+ }
253
+
254
+ if (opts.json) {
255
+ writeOutput(cmd, { schedule });
256
+ return;
257
+ }
258
+
259
+ const fields: Array<[string, string]> = [
260
+ ["ID", schedule.id],
261
+ ["Name", schedule.name],
262
+ ["Description", schedule.description ?? "—"],
263
+ ["Enabled", schedule.enabled ? "enabled" : "disabled"],
264
+ ["Status", schedule.status],
265
+ ["Mode", schedule.mode],
266
+ ["Syntax", schedule.syntax],
267
+ ["Expression", schedule.expression ?? "—"],
268
+ ["Cadence", describeSchedule(schedule)],
269
+ ["Timezone", schedule.timezone ?? "—"],
270
+ ["Next run", formatTimestamp(schedule.nextRunAt)],
271
+ ["Last run", formatNullableTimestamp(schedule.lastRunAt)],
272
+ ["Last status", schedule.lastStatus ?? "—"],
273
+ ["One-shot", schedule.isOneShot ? "yes" : "no"],
274
+ ["Message", schedule.message || "—"],
275
+ ["Script", schedule.script ?? "—"],
276
+ [
277
+ "Inference profile",
278
+ schedule.inferenceProfile ?? "default (mainAgent)",
279
+ ],
280
+ ["Routing intent", schedule.routingIntent],
281
+ ["Reuse conversation", schedule.reuseConversation ? "yes" : "no"],
282
+ ["Wake conversation", schedule.wakeConversationId ?? "—"],
283
+ ["Retry count", String(schedule.retryCount)],
284
+ ["Max retries", String(schedule.maxRetries)],
285
+ ["Retry backoff", formatDuration(schedule.retryBackoffMs)],
286
+ ["Timeout", formatDuration(schedule.timeoutMs)],
287
+ ["Source conversation", schedule.createdFromConversationId ?? "—"],
288
+ ];
289
+ const labelWidth = Math.max(...fields.map(([label]) => label.length));
290
+ for (const [label, value] of fields) {
291
+ log.info(`${`${label}:`.padEnd(labelWidth + 2)}${value}`);
292
+ }
293
+ });
294
+
202
295
  schedules
203
296
  .command("runs <id>")
204
297
  .description("List recent runs for a schedule")
@@ -331,6 +424,10 @@ Examples:
331
424
  "-t, --timezone <tz>",
332
425
  "IANA timezone for the expression (e.g. America/New_York)",
333
426
  )
427
+ .option(
428
+ "--profile <name>",
429
+ "Inference profile (llm.profiles key) the schedule's runs use; defaults to the mainAgent model selection when omitted",
430
+ )
334
431
  .option("--no-enabled", "Create the schedule in a disabled state")
335
432
  .option("--json", "Machine-readable compact JSON output")
336
433
  .addHelpText(
@@ -341,6 +438,9 @@ Options:
341
438
  -d, --description <text> Authored description explaining what the schedule is for.
342
439
  -m, --message <text> Message body sent on each fire.
343
440
  -t, --timezone <tz> IANA timezone applied to the expression.
441
+ --profile <name> Inference profile (llm.profiles key) the schedule's
442
+ runs use. When omitted, runs use the default —
443
+ the mainAgent call site's model selection.
344
444
  --no-enabled Create the schedule disabled. Defaults to enabled.
345
445
  --json Output the updated schedule list as compact JSON.
346
446
 
@@ -376,6 +476,7 @@ Examples:
376
476
  description: string;
377
477
  message: string;
378
478
  timezone?: string;
479
+ profile?: string;
379
480
  enabled: boolean;
380
481
  json?: boolean;
381
482
  },
@@ -413,6 +514,7 @@ Examples:
413
514
  enabled: opts.enabled,
414
515
  };
415
516
  if (opts.timezone != null) body.timezone = opts.timezone;
517
+ if (opts.profile != null) body.inferenceProfile = opts.profile;
416
518
 
417
519
  const result = await cliIpcCall<ListSchedulesResponse>(
418
520
  "createSchedule",
@@ -431,6 +533,251 @@ Examples:
431
533
  },
432
534
  );
433
535
 
536
+ schedules
537
+ .command("update <id>")
538
+ .description("Update fields on an existing schedule")
539
+ .option("--name <text>", "New display name")
540
+ .option(
541
+ "-d, --description <text>",
542
+ "New authored description explaining what the schedule is for",
543
+ )
544
+ .option(
545
+ "-e, --expression <expr>",
546
+ "New cron or RRULE expression for the fire times",
547
+ )
548
+ .option(
549
+ "-t, --timezone <tz>",
550
+ "IANA timezone for the expression (e.g. America/New_York)",
551
+ )
552
+ .option(
553
+ "-m, --message <text>",
554
+ "New message body sent to the assistant on each fire",
555
+ )
556
+ .option("--script <command>", "Shell command for script-mode schedules")
557
+ .option(
558
+ "--mode <mode>",
559
+ "One of: notify, execute, script, wake (wake only when the schedule already has a wake conversation target)",
560
+ )
561
+ .option(
562
+ "--routing-intent <intent>",
563
+ "One of: single_channel, multi_channel, all_channels",
564
+ )
565
+ .option("--quiet", "Suppress notifications for this schedule")
566
+ .option("--no-quiet", "Re-enable notifications for this schedule")
567
+ .option(
568
+ "--reuse-conversation",
569
+ "Reuse one conversation across recurring fires",
570
+ )
571
+ .option(
572
+ "--no-reuse-conversation",
573
+ "Start a fresh conversation on each fire",
574
+ )
575
+ .option("--max-retries <count>", "Maximum retry attempts (integer)")
576
+ .option(
577
+ "--retry-backoff-ms <ms>",
578
+ "Retry backoff in milliseconds (integer)",
579
+ )
580
+ .option(
581
+ "--timeout-ms <ms>",
582
+ "Script-mode execution timeout in milliseconds (integer)",
583
+ )
584
+ .option(
585
+ "--clear-timeout",
586
+ "Clear the script timeout override and use the assistant default",
587
+ )
588
+ .option(
589
+ "--profile <name>",
590
+ "Inference profile (llm.profiles key) the schedule's runs use; the default when unset is the mainAgent model selection",
591
+ )
592
+ .option(
593
+ "--clear-profile",
594
+ "Clear the inference profile and revert to the default mainAgent model selection",
595
+ )
596
+ .option("--json", "Machine-readable compact JSON output")
597
+ .addHelpText(
598
+ "after",
599
+ `
600
+ Options:
601
+ --name <text> New display name.
602
+ -d, --description <text> New authored description (must be non-empty).
603
+ -e, --expression <expr> Cron (e.g. '*/30 * * * *') or RRULE expression.
604
+ -t, --timezone <tz> IANA timezone applied to the expression.
605
+ -m, --message <text> New message body sent on each fire.
606
+ --script <command> Shell command for script-mode schedules.
607
+ --mode <mode> notify, execute, script, or wake.
608
+ --routing-intent <intent> single_channel, multi_channel, or all_channels.
609
+ --quiet / --no-quiet Toggle notification suppression.
610
+ --reuse-conversation / --no-reuse-conversation
611
+ Toggle conversation reuse across recurring fires.
612
+ --max-retries <count> Maximum retry attempts.
613
+ --retry-backoff-ms <ms> Retry backoff in milliseconds.
614
+ --timeout-ms <ms> Script-mode execution timeout in milliseconds.
615
+ --clear-timeout Remove the timeout override (mutually exclusive
616
+ with --timeout-ms).
617
+ --profile <name> Inference profile (llm.profiles key) the schedule's
618
+ runs use. When no profile is set, runs use the
619
+ default — the mainAgent call site's model selection.
620
+ --clear-profile Remove the inference profile and revert to the
621
+ default mainAgent model selection (mutually
622
+ exclusive with --profile).
623
+ --json Output the updated schedule list as compact JSON.
624
+
625
+ Arguments:
626
+ <id> Schedule ID (UUID) — run 'assistant schedules list --all' to find it.
627
+
628
+ Behavior:
629
+ Partially updates the schedule: only fields for flags you pass are changed,
630
+ everything else is preserved. At least one update flag is required. To
631
+ enable or disable a schedule, use 'assistant schedules enable <id>' or
632
+ 'assistant schedules disable <id>' instead.
633
+
634
+ Examples:
635
+ $ assistant schedules update 9f2c4f3a-3f1a-41e4-88e7-abc123 \\
636
+ --expression '0 9 * * MON-FRI' --timezone America/New_York
637
+ $ assistant schedules update 9f2c4f3a-3f1a-41e4-88e7-abc123 \\
638
+ --name "Morning summary" --message 'write the morning summary'
639
+ $ assistant schedules update 9f2c4f3a-3f1a-41e4-88e7-abc123 \\
640
+ --max-retries 5 --retry-backoff-ms 30000 --json`,
641
+ )
642
+ .action(
643
+ async (
644
+ id: string,
645
+ opts: {
646
+ name?: string;
647
+ description?: string;
648
+ expression?: string;
649
+ timezone?: string;
650
+ message?: string;
651
+ script?: string;
652
+ mode?: string;
653
+ routingIntent?: string;
654
+ quiet?: boolean;
655
+ reuseConversation?: boolean;
656
+ maxRetries?: string;
657
+ retryBackoffMs?: string;
658
+ timeoutMs?: string;
659
+ clearTimeout?: boolean;
660
+ profile?: string;
661
+ clearProfile?: boolean;
662
+ json?: boolean;
663
+ },
664
+ cmd: Command,
665
+ ) => {
666
+ const scheduleId = id.trim();
667
+ const fail = (error: string) => {
668
+ if (opts.json) {
669
+ writeOutput(cmd, { ok: false, error });
670
+ } else {
671
+ log.error(error);
672
+ }
673
+ process.exitCode = 1;
674
+ };
675
+
676
+ const parseInteger = (
677
+ flag: string,
678
+ value: string,
679
+ ): number | null => {
680
+ const parsed = Number(value);
681
+ if (!Number.isInteger(parsed)) {
682
+ fail(`${flag} must be an integer, got "${value}"`);
683
+ return null;
684
+ }
685
+ return parsed;
686
+ };
687
+
688
+ if (opts.clearTimeout && opts.timeoutMs != null) {
689
+ fail(
690
+ "--timeout-ms and --clear-timeout are mutually exclusive; pass --timeout-ms to set a timeout or --clear-timeout to remove it",
691
+ );
692
+ return;
693
+ }
694
+
695
+ if (opts.clearProfile && opts.profile != null) {
696
+ fail(
697
+ "--profile and --clear-profile are mutually exclusive; pass --profile to set a profile or --clear-profile to revert to the default mainAgent model selection",
698
+ );
699
+ return;
700
+ }
701
+
702
+ // Wake mode requires a wake conversation target, which this CLI
703
+ // cannot set. Allow `--mode wake` only when the schedule already
704
+ // has one — otherwise the scheduler would skip every fire as a
705
+ // no-op.
706
+ if (opts.mode === "wake") {
707
+ const existing = await cliIpcCall<GetScheduleResponse>(
708
+ "getSchedule",
709
+ { pathParams: { id: scheduleId } },
710
+ );
711
+ if (!existing.ok) return exitFromIpcResult(existing, cmd);
712
+ if (!existing.result?.schedule.wakeConversationId) {
713
+ fail(
714
+ "--mode wake requires the schedule to already have a wake conversation target; this CLI cannot set one. Create wake schedules with the schedule tool instead.",
715
+ );
716
+ return;
717
+ }
718
+ }
719
+
720
+ const body: Record<string, unknown> = {};
721
+ if (opts.name != null) body.name = opts.name;
722
+ if (opts.description != null) body.description = opts.description;
723
+ if (opts.expression != null) body.expression = opts.expression;
724
+ if (opts.timezone != null) body.timezone = opts.timezone;
725
+ if (opts.message != null) body.message = opts.message;
726
+ if (opts.script != null) body.script = opts.script;
727
+ if (opts.mode != null) body.mode = opts.mode;
728
+ if (opts.routingIntent != null) {
729
+ body.routingIntent = opts.routingIntent;
730
+ }
731
+ if (opts.quiet != null) body.quiet = opts.quiet;
732
+ if (opts.reuseConversation != null) {
733
+ body.reuseConversation = opts.reuseConversation;
734
+ }
735
+ if (opts.maxRetries != null) {
736
+ const parsed = parseInteger("--max-retries", opts.maxRetries);
737
+ if (parsed == null) return;
738
+ body.maxRetries = parsed;
739
+ }
740
+ if (opts.retryBackoffMs != null) {
741
+ const parsed = parseInteger(
742
+ "--retry-backoff-ms",
743
+ opts.retryBackoffMs,
744
+ );
745
+ if (parsed == null) return;
746
+ body.retryBackoffMs = parsed;
747
+ }
748
+ if (opts.timeoutMs != null) {
749
+ const parsed = parseInteger("--timeout-ms", opts.timeoutMs);
750
+ if (parsed == null) return;
751
+ body.timeoutMs = parsed;
752
+ }
753
+ if (opts.clearTimeout) body.timeoutMs = null;
754
+ if (opts.profile != null) body.inferenceProfile = opts.profile;
755
+ if (opts.clearProfile) body.inferenceProfile = null;
756
+
757
+ if (Object.keys(body).length === 0) {
758
+ fail(
759
+ "At least one update flag is required. Run 'assistant schedules update --help' for the available flags.",
760
+ );
761
+ return;
762
+ }
763
+
764
+ const result = await cliIpcCall<ListSchedulesResponse>(
765
+ "updateSchedule",
766
+ { pathParams: { id: scheduleId }, body },
767
+ );
768
+
769
+ if (!result.ok) return exitFromIpcResult(result, cmd);
770
+
771
+ const response = result.result ?? { schedules: [] };
772
+ if (opts.json) {
773
+ writeOutput(cmd, response);
774
+ return;
775
+ }
776
+
777
+ log.info(`Updated schedule: ${scheduleId}`);
778
+ },
779
+ );
780
+
434
781
  schedules
435
782
  .command("enable <id>")
436
783
  .description("Enable a schedule")
@@ -701,6 +1048,32 @@ function formatNullableTimestamp(value: number | null): string {
701
1048
  }
702
1049
 
703
1050
  function formatDuration(value: number | null): string {
704
- if (value == null || !Number.isFinite(value)) return "—";
705
- return `${value}ms`;
1051
+ if (value == null || !Number.isFinite(value) || value < 0) return "—";
1052
+ if (value < 1000) return `${Math.round(value)}ms`;
1053
+
1054
+ // Keep one decimal of precision only in the sub-minute range, where it's
1055
+ // meaningful; coarser units round to whole seconds. Working in tenths of a
1056
+ // second lets the tier boundary (e.g. 59.97s) round up cleanly to "1m".
1057
+ const tenths = Math.round(value / 100);
1058
+ if (tenths < 600) {
1059
+ const seconds = tenths / 10;
1060
+ return `${Number.isInteger(seconds) ? seconds : seconds.toFixed(1)}s`;
1061
+ }
1062
+
1063
+ const totalSeconds = Math.round(value / 1000);
1064
+ const minutes = Math.floor(totalSeconds / 60);
1065
+ const seconds = totalSeconds % 60;
1066
+ if (minutes < 60) {
1067
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
1068
+ }
1069
+
1070
+ const hours = Math.floor(minutes / 60);
1071
+ const remMinutes = minutes % 60;
1072
+ if (hours < 24) {
1073
+ return remMinutes > 0 ? `${hours}h ${remMinutes}m` : `${hours}h`;
1074
+ }
1075
+
1076
+ const days = Math.floor(hours / 24);
1077
+ const remHours = hours % 24;
1078
+ return remHours > 0 ? `${days}d ${remHours}h` : `${days}d`;
706
1079
  }