@vellumai/assistant 0.11.4-staging.1 → 0.11.4-staging.3

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 (205) hide show
  1. package/AGENTS.md +8 -2
  2. package/ARCHITECTURE.md +2 -0
  3. package/docs/architecture/memory.md +15 -0
  4. package/docs/browser-use-architecture-phase2.md +128 -56
  5. package/docs/flux-turn-detection-spike.md +11 -6
  6. package/knip.json +3 -0
  7. package/openapi.yaml +135 -75
  8. package/package.json +1 -1
  9. package/scripts/write-plugin-api-shim.ts +10 -0
  10. package/src/__tests__/app-control-flow.test.ts +1 -0
  11. package/src/__tests__/approval-routes-http.test.ts +2 -2
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  13. package/src/__tests__/byok-default-profile-ensure.test.ts +15 -0
  14. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  15. package/src/__tests__/compaction-events.test.ts +8 -10
  16. package/src/__tests__/conversation-agent-loop.test.ts +4 -1
  17. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  18. package/src/__tests__/conversation-notifiers-provenance.test.ts +1 -1
  19. package/src/__tests__/conversation-queue.test.ts +99 -0
  20. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  21. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  22. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  23. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  24. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  25. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  26. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  27. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  29. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  30. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  31. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  32. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  35. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  36. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  37. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  38. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  39. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  40. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  41. package/src/__tests__/document-sync-tags.test.ts +0 -75
  42. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  43. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  44. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  45. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  46. package/src/__tests__/media-generate-image.test.ts +131 -21
  47. package/src/__tests__/memory-retrieval-hook.test.ts +94 -2
  48. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  49. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -3
  50. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  51. package/src/__tests__/qdrant-manager.test.ts +14 -1
  52. package/src/__tests__/run-due-schedules.test.ts +21 -0
  53. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  54. package/src/__tests__/schedule-routes.test.ts +23 -0
  55. package/src/__tests__/schedule-store.test.ts +17 -0
  56. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  57. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  58. package/src/__tests__/starter-task-flow.test.ts +5 -4
  59. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  60. package/src/__tests__/subagent-spawn-and-await.test.ts +4 -7
  61. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  62. package/src/__tests__/subagent-tools.test.ts +81 -101
  63. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  64. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  65. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  66. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  67. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  68. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  69. package/src/__tests__/voice-config-update.test.ts +40 -0
  70. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  71. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  72. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  73. package/src/acp/session-manager.ts +8 -1
  74. package/src/api/surfaces.ts +12 -3
  75. package/src/calls/__tests__/voice-session-bridge.test.ts +21 -10
  76. package/src/calls/__tests__/voice-triage-escalate.test.ts +8 -0
  77. package/src/calls/call-controller.ts +16 -7
  78. package/src/calls/voice-session-bridge.ts +44 -25
  79. package/src/calls/voice-triage-escalate.ts +1 -0
  80. package/src/cli/bundled-modules.ts +29 -0
  81. package/src/cli/commands/db/repair.ts +4 -8
  82. package/src/cli/commands/domain.ts +6 -3
  83. package/src/cli/commands/email.ts +6 -3
  84. package/src/cli/commands/keys.ts +8 -3
  85. package/src/cli/commands/plugins.ts +85 -36
  86. package/src/cli/commands/schedules.ts +35 -1
  87. package/src/cli/lib/bundled-marketplace.json +1 -1
  88. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  89. package/src/config/assistant-feature-flags.ts +36 -15
  90. package/src/config/balanced-model-experiment.ts +35 -0
  91. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  92. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  93. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  94. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  95. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  96. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  97. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  98. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  99. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  100. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  101. package/src/config/call-site-defaults.ts +7 -0
  102. package/src/config/default-profile-catalog.ts +97 -5
  103. package/src/config/feature-flag-registry.json +11 -11
  104. package/src/config/skills.ts +9 -2
  105. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  106. package/src/daemon/conversation-agent-loop.ts +14 -13
  107. package/src/daemon/conversation-notifiers.ts +11 -9
  108. package/src/daemon/conversation-store.ts +4 -4
  109. package/src/daemon/conversation-surfaces.ts +54 -18
  110. package/src/daemon/conversation-tool-setup.ts +2 -4
  111. package/src/daemon/conversation.ts +81 -44
  112. package/src/daemon/doordash-steps.ts +2 -2
  113. package/src/daemon/lifecycle.ts +14 -1
  114. package/src/daemon/process-message.ts +0 -23
  115. package/src/daemon/windows-compiled-entry.ts +4 -0
  116. package/src/documents/document-store.ts +5 -235
  117. package/src/hooks/types.ts +5 -0
  118. package/src/ipc/gateway-flag-listener.ts +17 -3
  119. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +118 -0
  120. package/src/live-voice/live-voice-manager.ts +16 -3
  121. package/src/live-voice/live-voice-session.ts +63 -9
  122. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  123. package/src/monitoring/control.ts +1 -0
  124. package/src/monitoring/db-integrity-sample.ts +4 -5
  125. package/src/permissions/prompter.ts +1 -5
  126. package/src/persistence/conversation-queries.ts +66 -16
  127. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  128. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  129. package/src/persistence/schema/documents.ts +4 -4
  130. package/src/plugin-api/constants.ts +8 -0
  131. package/src/plugin-api/index.ts +5 -1
  132. package/src/plugin-api/vision-support.test.ts +1 -1
  133. package/src/plugin-api/webhook-url.ts +13 -11
  134. package/src/plugins/defaults/main.ts +6 -7
  135. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  136. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  137. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  138. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  139. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  140. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  141. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  142. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  143. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  144. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  145. package/src/plugins/mtime-cache.ts +17 -0
  146. package/src/prompts/templates/system-sections.ts +0 -7
  147. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  148. package/src/providers/__tests__/retry-callsite.test.ts +20 -0
  149. package/src/providers/model-catalog.ts +3 -3
  150. package/src/providers/model-intents.ts +2 -2
  151. package/src/providers/openai/chat-completions-provider.ts +16 -7
  152. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +11 -6
  153. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +9 -49
  154. package/src/routes/control.ts +1 -0
  155. package/src/routes/route-host-client.ts +1 -0
  156. package/src/runtime/AGENTS.md +16 -17
  157. package/src/runtime/agent-wake.ts +15 -12
  158. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +170 -1
  159. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  160. package/src/runtime/routes/conversation-list-routes.ts +54 -22
  161. package/src/runtime/routes/conversation-management-routes.ts +2 -3
  162. package/src/runtime/routes/conversation-routes.ts +11 -13
  163. package/src/runtime/routes/documents-routes.ts +3 -222
  164. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  165. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  166. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  167. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  168. package/src/runtime/routes/schedule-routes.ts +94 -7
  169. package/src/runtime/routes/workspace-routes.ts +0 -9
  170. package/src/runtime/routes/workspace-utils.ts +3 -13
  171. package/src/runtime/services/conversation-serializer.ts +7 -2
  172. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  173. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  174. package/src/schedule/plugin-schedule-availability.ts +58 -0
  175. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  176. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  177. package/src/schedule/schedule-store.ts +5 -1
  178. package/src/schedule/scheduler.ts +9 -4
  179. package/src/schedule/worker-control.ts +1 -0
  180. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  181. package/src/subagent/consult-context.ts +11 -11
  182. package/src/subagent/consult-prompt.ts +26 -35
  183. package/src/subagent/manager.ts +20 -37
  184. package/src/subagent/notify.ts +7 -1
  185. package/src/subagent/types.ts +8 -6
  186. package/src/tools/acp/spawn.ts +6 -4
  187. package/src/tools/skills/scaffold-managed.ts +25 -7
  188. package/src/tools/subagent/spawn.ts +28 -88
  189. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  190. package/src/tts/__tests__/reasoning-tag-filter.test.ts +15 -0
  191. package/src/tts/reasoning-tag-filter.ts +11 -32
  192. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  193. package/src/util/logger.ts +16 -0
  194. package/src/util/think-tag-stream.ts +95 -0
  195. package/src/util/worker-process.ts +37 -4
  196. package/src/windows-compiled-cli.ts +32 -0
  197. package/src/windows-compiled-entry.ts +4 -0
  198. package/src/windows-compiled-logger.ts +6 -0
  199. package/src/windows-compiled-worker-entry.ts +29 -0
  200. package/src/workspace/byok-default-profile-ensure.ts +15 -6
  201. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  202. package/src/workspace/migrations/registry.ts +2 -0
  203. package/src/__tests__/document-workspace-file.test.ts +0 -467
  204. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  205. package/src/subagent/consult-transcript.ts +0 -90
package/openapi.yaml CHANGED
@@ -6596,6 +6596,18 @@ paths:
6596
6596
  "system:all" for conversations in no group, "system:pinned" for the Pinned section, or a custom group id. A
6597
6597
  group-scoped request is recency-ordered like every list read (COALESCE(last_message_at, updated_at)
6598
6598
  descending) and never has pinned rows appended to it. Omit to span every group.
6599
+ - name: needsAttention
6600
+ in: query
6601
+ required: false
6602
+ schema:
6603
+ type: string
6604
+ enum:
6605
+ - "true"
6606
+ description:
6607
+ 'Pass "true" to return only conversations whose latest assistant message the user has not seen: the same
6608
+ predicate behind the unread count and the section index, so a client that keeps no complete conversation
6609
+ list can ask for exactly the rows its attention surfaces need. Composes with every other filter. Any value
6610
+ other than "true" is rejected. Omit to span every conversation.'
6599
6611
  responses:
6600
6612
  "200":
6601
6613
  description: Successful response
@@ -6953,7 +6965,9 @@ paths:
6953
6965
  get:
6954
6966
  operationId: conversations_by_id_get
6955
6967
  summary: Get conversation detail
6956
- description: Retrieve a single conversation with full metadata.
6968
+ description:
6969
+ Retrieve a single conversation with full metadata. Rows the listing hides by type (legacy private rows) are
6970
+ not found here either.
6957
6971
  tags:
6958
6972
  - conversations
6959
6973
  parameters:
@@ -10906,11 +10920,6 @@ paths:
10906
10920
  type: number
10907
10921
  updatedAt:
10908
10922
  type: number
10909
- workspacePath:
10910
- anyOf:
10911
- - type: string
10912
- - type: "null"
10913
- description: Workspace-relative file this document is bound to, or null
10914
10923
  required:
10915
10924
  - success
10916
10925
  - surfaceId
@@ -10920,7 +10929,6 @@ paths:
10920
10929
  - wordCount
10921
10930
  - createdAt
10922
10931
  - updatedAt
10923
- - workspacePath
10924
10932
  additionalProperties: false
10925
10933
  /v1/documents/{id}/comments:
10926
10934
  get:
@@ -11338,74 +11346,6 @@ paths:
11338
11346
  schema:
11339
11347
  type: string
11340
11348
  format: binary
11341
- /v1/documents/for-workspace-file:
11342
- post:
11343
- operationId: documents_forworkspacefile_post
11344
- summary: Get or create the document for a workspace markdown file
11345
- description:
11346
- "Return the document bound to a workspace markdown file, creating it on first open. The file is the source
11347
- of truth at open time: stored content that has drifted from the file is refreshed from disk before the document
11348
- is returned."
11349
- tags:
11350
- - documents
11351
- requestBody:
11352
- required: true
11353
- content:
11354
- application/json:
11355
- schema:
11356
- type: object
11357
- properties:
11358
- path:
11359
- type: string
11360
- description: Workspace-relative path to a .md/.markdown file
11361
- conversationId:
11362
- type: string
11363
- description: Conversation the document is opened from
11364
- required:
11365
- - path
11366
- - conversationId
11367
- responses:
11368
- "200":
11369
- description: Successful response
11370
- content:
11371
- application/json:
11372
- schema:
11373
- type: object
11374
- properties:
11375
- success:
11376
- type: boolean
11377
- surfaceId:
11378
- type: string
11379
- conversationId:
11380
- type: string
11381
- title:
11382
- type: string
11383
- content:
11384
- type: string
11385
- wordCount:
11386
- type: number
11387
- createdAt:
11388
- type: number
11389
- updatedAt:
11390
- type: number
11391
- workspacePath:
11392
- anyOf:
11393
- - type: string
11394
- - type: "null"
11395
- description: Workspace-relative file this document is bound to, or null
11396
- required:
11397
- - success
11398
- - surfaceId
11399
- - conversationId
11400
- - title
11401
- - content
11402
- - wordCount
11403
- - createdAt
11404
- - updatedAt
11405
- - workspacePath
11406
- additionalProperties: false
11407
- "422":
11408
- description: Path is not a markdown file
11409
11349
  /v1/domain/register:
11410
11350
  post:
11411
11351
  operationId: domain_register_post
@@ -25778,6 +25718,20 @@ paths:
25778
25718
  description:
25779
25719
  User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
25780
25720
  applies. Always null for user-created schedules.
25721
+ disarmReason:
25722
+ anyOf:
25723
+ - type: string
25724
+ enum:
25725
+ - user_disabled
25726
+ - plugin_removed
25727
+ - plugin_disabled
25728
+ - declaration_removed
25729
+ - declaration_disabled
25730
+ - type: "null"
25731
+ description:
25732
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled,
25733
+ the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule
25734
+ is on, and always null for user-created schedules."
25781
25735
  isOneShot:
25782
25736
  type: boolean
25783
25737
  isDeferred:
@@ -25814,6 +25768,7 @@ paths:
25814
25768
  - workflowName
25815
25769
  - sourceKey
25816
25770
  - userEnabled
25771
+ - disarmReason
25817
25772
  - isOneShot
25818
25773
  - isDeferred
25819
25774
  additionalProperties: false
@@ -26011,6 +25966,20 @@ paths:
26011
25966
  description:
26012
25967
  User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
26013
25968
  applies. Always null for user-created schedules.
25969
+ disarmReason:
25970
+ anyOf:
25971
+ - type: string
25972
+ enum:
25973
+ - user_disabled
25974
+ - plugin_removed
25975
+ - plugin_disabled
25976
+ - declaration_removed
25977
+ - declaration_disabled
25978
+ - type: "null"
25979
+ description:
25980
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled,
25981
+ the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule
25982
+ is on, and always null for user-created schedules."
26014
25983
  isOneShot:
26015
25984
  type: boolean
26016
25985
  isDeferred:
@@ -26047,6 +26016,7 @@ paths:
26047
26016
  - workflowName
26048
26017
  - sourceKey
26049
26018
  - userEnabled
26019
+ - disarmReason
26050
26020
  - isOneShot
26051
26021
  - isDeferred
26052
26022
  additionalProperties: false
@@ -26196,6 +26166,20 @@ paths:
26196
26166
  description:
26197
26167
  User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
26198
26168
  applies. Always null for user-created schedules.
26169
+ disarmReason:
26170
+ anyOf:
26171
+ - type: string
26172
+ enum:
26173
+ - user_disabled
26174
+ - plugin_removed
26175
+ - plugin_disabled
26176
+ - declaration_removed
26177
+ - declaration_disabled
26178
+ - type: "null"
26179
+ description:
26180
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled,
26181
+ the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule
26182
+ is on, and always null for user-created schedules."
26199
26183
  isOneShot:
26200
26184
  type: boolean
26201
26185
  isDeferred:
@@ -26232,6 +26216,7 @@ paths:
26232
26216
  - workflowName
26233
26217
  - sourceKey
26234
26218
  - userEnabled
26219
+ - disarmReason
26235
26220
  - isOneShot
26236
26221
  - isDeferred
26237
26222
  additionalProperties: false
@@ -26378,6 +26363,20 @@ paths:
26378
26363
  description:
26379
26364
  User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
26380
26365
  applies. Always null for user-created schedules.
26366
+ disarmReason:
26367
+ anyOf:
26368
+ - type: string
26369
+ enum:
26370
+ - user_disabled
26371
+ - plugin_removed
26372
+ - plugin_disabled
26373
+ - declaration_removed
26374
+ - declaration_disabled
26375
+ - type: "null"
26376
+ description:
26377
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled,
26378
+ the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule
26379
+ is on, and always null for user-created schedules."
26381
26380
  isOneShot:
26382
26381
  type: boolean
26383
26382
  isDeferred:
@@ -26414,6 +26413,7 @@ paths:
26414
26413
  - workflowName
26415
26414
  - sourceKey
26416
26415
  - userEnabled
26416
+ - disarmReason
26417
26417
  - isOneShot
26418
26418
  - isDeferred
26419
26419
  additionalProperties: false
@@ -26620,6 +26620,20 @@ paths:
26620
26620
  description:
26621
26621
  User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
26622
26622
  applies. Always null for user-created schedules.
26623
+ disarmReason:
26624
+ anyOf:
26625
+ - type: string
26626
+ enum:
26627
+ - user_disabled
26628
+ - plugin_removed
26629
+ - plugin_disabled
26630
+ - declaration_removed
26631
+ - declaration_disabled
26632
+ - type: "null"
26633
+ description:
26634
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled,
26635
+ the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule
26636
+ is on, and always null for user-created schedules."
26623
26637
  isOneShot:
26624
26638
  type: boolean
26625
26639
  isDeferred:
@@ -26656,6 +26670,7 @@ paths:
26656
26670
  - workflowName
26657
26671
  - sourceKey
26658
26672
  - userEnabled
26673
+ - disarmReason
26659
26674
  - isOneShot
26660
26675
  - isDeferred
26661
26676
  additionalProperties: false
@@ -26805,6 +26820,20 @@ paths:
26805
26820
  description:
26806
26821
  User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
26807
26822
  applies. Always null for user-created schedules.
26823
+ disarmReason:
26824
+ anyOf:
26825
+ - type: string
26826
+ enum:
26827
+ - user_disabled
26828
+ - plugin_removed
26829
+ - plugin_disabled
26830
+ - declaration_removed
26831
+ - declaration_disabled
26832
+ - type: "null"
26833
+ description:
26834
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled,
26835
+ the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule
26836
+ is on, and always null for user-created schedules."
26808
26837
  isOneShot:
26809
26838
  type: boolean
26810
26839
  isDeferred:
@@ -26841,6 +26870,7 @@ paths:
26841
26870
  - workflowName
26842
26871
  - sourceKey
26843
26872
  - userEnabled
26873
+ - disarmReason
26844
26874
  - isOneShot
26845
26875
  - isDeferred
26846
26876
  additionalProperties: false
@@ -26992,6 +27022,20 @@ paths:
26992
27022
  description:
26993
27023
  User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
26994
27024
  applies. Always null for user-created schedules.
27025
+ disarmReason:
27026
+ anyOf:
27027
+ - type: string
27028
+ enum:
27029
+ - user_disabled
27030
+ - plugin_removed
27031
+ - plugin_disabled
27032
+ - declaration_removed
27033
+ - declaration_disabled
27034
+ - type: "null"
27035
+ description:
27036
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled,
27037
+ the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule
27038
+ is on, and always null for user-created schedules."
26995
27039
  isOneShot:
26996
27040
  type: boolean
26997
27041
  isDeferred:
@@ -27028,6 +27072,7 @@ paths:
27028
27072
  - workflowName
27029
27073
  - sourceKey
27030
27074
  - userEnabled
27075
+ - disarmReason
27031
27076
  - isOneShot
27032
27077
  - isDeferred
27033
27078
  additionalProperties: false
@@ -27314,6 +27359,20 @@ paths:
27314
27359
  description:
27315
27360
  User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
27316
27361
  applies. Always null for user-created schedules.
27362
+ disarmReason:
27363
+ anyOf:
27364
+ - type: string
27365
+ enum:
27366
+ - user_disabled
27367
+ - plugin_removed
27368
+ - plugin_disabled
27369
+ - declaration_removed
27370
+ - declaration_disabled
27371
+ - type: "null"
27372
+ description:
27373
+ "Why a plugin-sourced schedule is off: the user turned it off, the plugin is gone, the plugin is disabled,
27374
+ the declaration is gone, or the plugin's own files turned it off. Null whenever the schedule
27375
+ is on, and always null for user-created schedules."
27317
27376
  isOneShot:
27318
27377
  type: boolean
27319
27378
  isDeferred:
@@ -27350,6 +27409,7 @@ paths:
27350
27409
  - workflowName
27351
27410
  - sourceKey
27352
27411
  - userEnabled
27412
+ - disarmReason
27353
27413
  - isOneShot
27354
27414
  - isDeferred
27355
27415
  additionalProperties: false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.11.4-staging.1",
3
+ "version": "0.11.4-staging.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,10 @@
1
+ import path from "node:path";
2
+
3
+ import { ensurePluginApiShim } from "../src/plugins/ensure-plugin-api-shim.js";
4
+
5
+ const outputDir = Bun.argv[2];
6
+ if (!outputDir || !path.isAbsolute(outputDir)) {
7
+ throw new Error("An absolute plugin API shim output directory is required.");
8
+ }
9
+
10
+ await ensurePluginApiShim({ workspaceDir: outputDir });
@@ -140,6 +140,7 @@ function buildContext(
140
140
  guardianPrincipalId: "user-1",
141
141
  },
142
142
  sendToClient: () => {},
143
+ emit: () => {},
143
144
  pendingSurfaceActions: new Map(),
144
145
  lastSurfaceAction: new Map(),
145
146
  surfaceState: new Map(),
@@ -113,7 +113,7 @@ function makeIdleSession(opts?: {
113
113
  ensureActorScopedHistory: async () => {},
114
114
  getMessages: () => [],
115
115
  usageStats: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
116
- updateClient: () => {},
116
+ replayActivityState: () => {},
117
117
  setHostBrowserProxy: () => {},
118
118
  setHostCuProxy: () => {},
119
119
  setHostAppControlProxy: () => {},
@@ -176,7 +176,7 @@ function makeConfirmationEmittingSession(opts?: {
176
176
  ensureActorScopedHistory: async () => {},
177
177
  getMessages: () => [],
178
178
  usageStats: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
179
- updateClient: () => {},
179
+ replayActivityState: () => {},
180
180
  setHostBrowserProxy: () => {},
181
181
  setHostCuProxy: () => {},
182
182
  setHostAppControlProxy: () => {},
@@ -40,7 +40,10 @@ interface RegistryFlag {
40
40
  key: string;
41
41
  label: string;
42
42
  description: string;
43
- defaultEnabled: boolean;
43
+ /** Boolean for a gate; the winning variation's name for a multivariate flag. */
44
+ defaultEnabled: boolean | string;
45
+ /** Declared variations. Present only on multivariate flags. */
46
+ values?: string[];
44
47
  }
45
48
 
46
49
  interface Registry {
@@ -179,8 +182,27 @@ describe("assistant feature flag guard", () => {
179
182
  const violations: string[] = [];
180
183
 
181
184
  for (const flag of assistantFlags) {
182
- if (typeof flag.defaultEnabled !== "boolean") {
183
- violations.push(`${flag.key}: missing or non-boolean 'defaultEnabled'`);
185
+ // A gate declares a boolean default. A multivariate flag declares its
186
+ // variations in `values` and names the winning one in `defaultEnabled`,
187
+ // which the resolver serves verbatim to string consumers.
188
+ if (flag.values === undefined) {
189
+ if (typeof flag.defaultEnabled !== "boolean") {
190
+ violations.push(
191
+ `${flag.key}: missing or non-boolean 'defaultEnabled'`,
192
+ );
193
+ }
194
+ } else if (
195
+ !Array.isArray(flag.values) ||
196
+ flag.values.length < 2 ||
197
+ flag.values.some((value) => typeof value !== "string")
198
+ ) {
199
+ violations.push(
200
+ `${flag.key}: 'values' must list at least two string variations`,
201
+ );
202
+ } else if (!flag.values.includes(flag.defaultEnabled as string)) {
203
+ violations.push(
204
+ `${flag.key}: 'defaultEnabled' must name one of the declared 'values'`,
205
+ );
184
206
  }
185
207
  if (
186
208
  typeof flag.description !== "string" ||
@@ -282,11 +282,26 @@ describe("ensureByokDefaultProfiles", () => {
282
282
  ["openai", "quality-optimized", "gpt-5.4"],
283
283
  ["fireworks", "balanced", "accounts/fireworks/models/kimi-k2p5"],
284
284
  ["fireworks", "balanced", "accounts/fireworks/models/kimi-k2p6"],
285
+ [
286
+ "fireworks",
287
+ "balanced",
288
+ "accounts/fireworks/models/deepseek-v4-flash-0731",
289
+ ],
285
290
  [
286
291
  "fireworks",
287
292
  "quality-optimized",
288
293
  "accounts/fireworks/models/deepseek-v4-flash",
289
294
  ],
295
+ [
296
+ "fireworks",
297
+ "quality-optimized",
298
+ "accounts/fireworks/models/deepseek-v4-flash-0731",
299
+ ],
300
+ [
301
+ "fireworks",
302
+ "cost-optimized",
303
+ "accounts/fireworks/models/deepseek-v4-flash",
304
+ ],
290
305
  ] as const)(
291
306
  "a historical intent-era copy converts (%s %s pinned to %s)",
292
307
  (provider, key, model) => {
@@ -28,7 +28,7 @@ function makeContext(
28
28
  ): Conversation {
29
29
  return asConversation({
30
30
  conversationId: "session-1",
31
- sendToClient: (msg) => sent.push(msg),
31
+ emit: (msg) => sent.push(msg),
32
32
  pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
33
33
  lastSurfaceAction: new Map<
34
34
  string,
@@ -436,12 +436,11 @@ describe("forceCompact context-window usage push", () => {
436
436
  expect(usageEvent.maxTokens).toBe(200_000);
437
437
  });
438
438
 
439
- test("pushes to the caller's sink when the conversation's own sender is dead", async () => {
440
- // A `/compact` draining behind an interactive turn: `process-message.ts`
441
- // resets `sendToClient` to a no-op in its `finally`, while the queued item
442
- // still carries the live sink its result card streams on. The push has to
443
- // follow the card, not the dead sender.
444
- const stranded: AssistantEvent[] = [];
439
+ test("pushes to the caller's sink instead of the conversation sink when one is given", async () => {
440
+ // A `/compact` draining from the queue carries the queued item's own
441
+ // sink, the one its result card streams on. The push has to follow the
442
+ // card so the two can never be delivered to different places.
443
+ const conversationSink: AssistantEvent[] = [];
445
444
  const cardSink: AssistantEvent[] = [];
446
445
  mockCompactResult = {
447
446
  messages: [],
@@ -460,11 +459,10 @@ describe("forceCompact context-window usage push", () => {
460
459
  };
461
460
 
462
461
  const conversation = makeConversation(
463
- stranded,
464
- "conv-compact-dead-sender",
462
+ conversationSink,
463
+ "conv-compact-caller-sink",
465
464
  [56_000, 18_000],
466
465
  );
467
- conversation.updateClient(() => {}, true);
468
466
 
469
467
  const result = await conversation.forceCompact((msg) => cardSink.push(msg));
470
468
 
@@ -475,7 +473,7 @@ describe("forceCompact context-window usage push", () => {
475
473
  .tokens,
476
474
  ).toBe(result.estimatedInputTokens);
477
475
  expect(
478
- stranded.filter((m) => m.type === "context_window_usage").length,
476
+ conversationSink.filter((m) => m.type === "context_window_usage").length,
479
477
  ).toBe(0);
480
478
  });
481
479
 
@@ -1345,7 +1345,10 @@ describe("session-agent-loop", () => {
1345
1345
  });
1346
1346
  const cleanupFlagDuringInjection = captureCleanupFlagDuringInjection(ctx);
1347
1347
 
1348
- await runAgentLoopImpl(ctx, "free up space", "msg-1", () => {});
1348
+ // Presence is declared per turn; the policy input must carry it.
1349
+ await runAgentLoopImpl(ctx, "free up space", "msg-1", () => {}, {
1350
+ isInteractive: true,
1351
+ });
1349
1352
 
1350
1353
  expect(classifyDiskPressureTurnPolicyMock).toHaveBeenCalledWith(
1351
1354
  mockDiskPressureStatus,
@@ -431,3 +431,115 @@ describe("sendToClient receives state signals", () => {
431
431
  });
432
432
  });
433
433
  });
434
+
435
+ describe("the sink is fixed for the conversation's life", () => {
436
+ test("a cold instance delivers activity state with no turn ever having run", () => {
437
+ // The summarize-up-to route resolves its conversation outside the send
438
+ // path and emits a thinking indicator before any turn: it must reach the
439
+ // sink the conversation was built with, not depend on a send having wired
440
+ // one (LUM-2987).
441
+ const clientMsgs: AssistantEvent[] = [];
442
+ const conversation = makeConversation((msg) => clientMsgs.push(msg));
443
+
444
+ conversation.emitActivityState("thinking", "context_compacting", {
445
+ statusText: "Summarizing conversation",
446
+ });
447
+
448
+ const activity = clientMsgs.filter(
449
+ (m) => m.type === "assistant_activity_state",
450
+ );
451
+ expect(activity).toHaveLength(1);
452
+ expect((activity[0] as { statusText?: string }).statusText).toBe(
453
+ "Summarizing conversation",
454
+ );
455
+ });
456
+
457
+ test("presence is a property of the turn, not of delivery", () => {
458
+ // Nothing is in flight, so no human is asserted present, even though the
459
+ // sink is live and delivering.
460
+ const clientMsgs: AssistantEvent[] = [];
461
+ const conversation = makeConversation((msg) => clientMsgs.push(msg));
462
+ expect(conversation.hasNoClient).toBe(true);
463
+ conversation.emitActivityState("thinking", "message_dequeued");
464
+ expect(clientMsgs).toHaveLength(1);
465
+ expect(conversation.hasNoClient).toBe(true);
466
+ });
467
+ });
468
+
469
+ describe("event observers", () => {
470
+ test("an observer sees each event after the sink delivered it, and detaches", () => {
471
+ const order: string[] = [];
472
+ const conversation = makeConversation((msg) => {
473
+ order.push(`sink:${msg.type}`);
474
+ });
475
+ const detach = conversation.addEventObserver((msg) => {
476
+ order.push(`observer:${msg.type}`);
477
+ });
478
+
479
+ conversation.emitActivityState("thinking", "message_dequeued");
480
+ expect(order).toEqual([
481
+ "sink:assistant_activity_state",
482
+ "observer:assistant_activity_state",
483
+ ]);
484
+
485
+ detach();
486
+ conversation.emitActivityState("idle", "message_complete");
487
+ expect(order).toEqual([
488
+ "sink:assistant_activity_state",
489
+ "observer:assistant_activity_state",
490
+ "sink:assistant_activity_state",
491
+ ]);
492
+ });
493
+
494
+ test("a throwing observer neither blocks the sink nor its siblings", () => {
495
+ const seen: string[] = [];
496
+ const conversation = makeConversation((msg) => {
497
+ seen.push(`sink:${msg.type}`);
498
+ });
499
+ conversation.addEventObserver(() => {
500
+ throw new Error("observer boom");
501
+ });
502
+ conversation.addEventObserver((msg) => {
503
+ seen.push(`second:${msg.type}`);
504
+ });
505
+
506
+ conversation.emitActivityState("thinking", "message_dequeued");
507
+ expect(seen).toEqual([
508
+ "sink:assistant_activity_state",
509
+ "second:assistant_activity_state",
510
+ ]);
511
+ });
512
+
513
+ test("the prompter's confirmation_request reaches observers after the sink", () => {
514
+ // Voice auto-resolves approval prompts it has no UI for; it must observe
515
+ // the request only once the sink has put it on the wire, so its resolution
516
+ // lands after the request it answers.
517
+ const order: string[] = [];
518
+ const conversation = makeConversation((msg) => {
519
+ order.push(`sink:${msg.type}`);
520
+ });
521
+ conversation.addEventObserver((msg) => {
522
+ if (msg.type === "confirmation_request") {
523
+ order.push("observer:confirmation_request");
524
+ conversation.handleConfirmationResponse(msg.requestId, "deny", {
525
+ emissionContext: { source: "system" },
526
+ });
527
+ }
528
+ });
529
+
530
+ void conversation.prompter.prompt(
531
+ "bash",
532
+ { command: "rm -rf /tmp/x" },
533
+ "high",
534
+ [],
535
+ [],
536
+ undefined,
537
+ conversation.conversationId,
538
+ "sandbox",
539
+ false,
540
+ );
541
+
542
+ expect(order[0]).toBe("sink:confirmation_request");
543
+ expect(order[1]).toBe("observer:confirmation_request");
544
+ });
545
+ });
@@ -35,7 +35,7 @@ describe("live-call question provenance", () => {
35
35
 
36
36
  test("a row persisted after the turn settled carries the owner, not the stale turn actor", async () => {
37
37
  registerConversationNotifiers("conv-n1", {
38
- sendToClient: () => {},
38
+ emit: () => {},
39
39
  messages: [],
40
40
  // Voice cleanup restored the owner; the per-turn field still holds the
41
41
  // finished voice turn's contact.