@vellumai/assistant 0.8.7-dev.202606052232.2ddc989 → 0.8.8

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 (262) hide show
  1. package/bun.lock +2 -2
  2. package/docs/plugins.md +832 -0
  3. package/examples/plugins/echo/README.md +60 -61
  4. package/examples/plugins/echo/package.json +2 -1
  5. package/examples/plugins/echo/register.ts +143 -0
  6. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -7
  7. package/openapi.yaml +5 -15
  8. package/package.json +2 -2
  9. package/src/__tests__/agent-loop-exit-reason.test.ts +56 -3
  10. package/src/__tests__/anthropic-provider.test.ts +1 -1
  11. package/src/__tests__/app-control-flow.test.ts +1 -1
  12. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +1 -4
  14. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  15. package/src/__tests__/channel-approvals.test.ts +1 -1
  16. package/src/__tests__/circuit-breaker-pipeline.test.ts +405 -0
  17. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  18. package/src/__tests__/compaction-timeout-recovery.test.ts +251 -0
  19. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
  20. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
  21. package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -3
  22. package/src/__tests__/conversation-agent-loop.test.ts +39 -42
  23. package/src/__tests__/conversation-clean-command.test.ts +2 -5
  24. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -4
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +71 -140
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +27 -108
  27. package/src/__tests__/conversation-starter-routes.test.ts +6 -14
  28. package/src/__tests__/conversation-workspace-cache-state.test.ts +16 -17
  29. package/src/__tests__/conversation-workspace-injection.test.ts +1 -61
  30. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -7
  31. package/src/__tests__/db-acp-history.test.ts +0 -101
  32. package/src/__tests__/dynamic-page-surface.test.ts +0 -31
  33. package/src/__tests__/file-write-tool.test.ts +0 -63
  34. package/src/__tests__/gateway-only-guard.test.ts +2 -12
  35. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  36. package/src/__tests__/guardian-routing-invariants.test.ts +4 -2
  37. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  38. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -1
  39. package/src/__tests__/heartbeat-service.test.ts +0 -1
  40. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  41. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  42. package/src/__tests__/injector-background-turn.test.ts +1 -1
  43. package/src/__tests__/injector-chain.test.ts +6 -34
  44. package/src/__tests__/injector-disk-pressure.test.ts +34 -77
  45. package/src/__tests__/injector-document-comments.test.ts +1 -1
  46. package/src/__tests__/list-messages-hidden-metadata.test.ts +0 -38
  47. package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
  48. package/src/__tests__/{overflow-reduction-loop.test.ts → overflow-reduce-pipeline.test.ts} +284 -64
  49. package/src/__tests__/pipeline-runner.test.ts +554 -0
  50. package/src/__tests__/plugin-api-shim.test.ts +6 -3
  51. package/src/__tests__/plugin-bootstrap.test.ts +23 -12
  52. package/src/__tests__/plugin-registry.test.ts +49 -3
  53. package/src/__tests__/plugin-types.test.ts +70 -0
  54. package/src/__tests__/reaction-persistence.test.ts +1 -1
  55. package/src/__tests__/send-endpoint-busy.test.ts +1 -4
  56. package/src/__tests__/skill-feature-flags-integration.test.ts +0 -33
  57. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  58. package/src/__tests__/subagent-fork-notifications.test.ts +3 -1
  59. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  60. package/src/__tests__/subagent-manager-notify.test.ts +3 -1
  61. package/src/__tests__/subagent-notify-parent.test.ts +3 -1
  62. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  63. package/src/__tests__/user-plugin-loader.test.ts +286 -54
  64. package/src/acp/__tests__/client-handler.test.ts +0 -40
  65. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -137
  66. package/src/acp/__tests__/session-manager-persistence.test.ts +28 -95
  67. package/src/acp/agent-process.ts +1 -61
  68. package/src/acp/client-handler.ts +0 -31
  69. package/src/acp/prepare-agent-env.ts +29 -83
  70. package/src/acp/resolve-agent.test.ts +7 -320
  71. package/src/acp/resolve-agent.ts +18 -182
  72. package/src/acp/session-manager.ts +73 -495
  73. package/src/acp/types.ts +0 -8
  74. package/src/agent/compaction-circuit.ts +102 -60
  75. package/src/agent/loop.ts +59 -32
  76. package/src/api/responses/conversation-message.ts +1 -7
  77. package/src/approvals/guardian-request-resolvers.ts +1 -1
  78. package/src/background-wake/next-wake.ts +0 -1
  79. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  80. package/src/config/acp-defaults.test.ts +0 -10
  81. package/src/config/acp-defaults.ts +0 -6
  82. package/src/config/bundled-skills/acp/SKILL.md +31 -83
  83. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  84. package/src/config/bundled-skills/app-builder/SKILL.md +381 -224
  85. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -29
  86. package/src/config/bundled-skills/document-editor/SKILL.md +23 -28
  87. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  88. package/src/config/bundled-tool-registry.ts +0 -2
  89. package/src/config/feature-flag-registry.json +5 -14
  90. package/src/config/schemas/heartbeat.ts +0 -9
  91. package/src/context/strip-injections.ts +2 -8
  92. package/src/context/window-manager.ts +1 -2
  93. package/src/daemon/conversation-agent-loop-handlers.ts +11 -0
  94. package/src/daemon/conversation-agent-loop.ts +279 -62
  95. package/src/daemon/conversation-runtime-assembly.ts +69 -106
  96. package/src/daemon/conversation-store.ts +90 -9
  97. package/src/daemon/conversation-workspace.ts +0 -17
  98. package/src/daemon/conversation.ts +6 -0
  99. package/src/daemon/external-plugins-bootstrap.ts +11 -11
  100. package/src/daemon/handlers/conversations.ts +1 -3
  101. package/src/daemon/handlers/skills.ts +1 -4
  102. package/src/daemon/lifecycle.ts +0 -21
  103. package/src/daemon/server.ts +0 -2
  104. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -3
  105. package/src/heartbeat/heartbeat-run-store.ts +1 -23
  106. package/src/heartbeat/heartbeat-service.ts +0 -26
  107. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  108. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  109. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -15
  110. package/src/ipc/skill-routes/memory.ts +2 -4
  111. package/src/memory/conversation-starter-checkpoints.ts +0 -1
  112. package/src/memory/db-init.ts +0 -2
  113. package/src/memory/job-handlers/conversation-starters.ts +2 -13
  114. package/src/memory/jobs-worker.ts +1 -1
  115. package/src/memory/migrations/index.ts +0 -1
  116. package/src/memory/schema/acp.ts +0 -4
  117. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  118. package/src/memory/v2/consolidation-job.ts +4 -13
  119. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/assign.test.ts +4 -4
  120. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/live-integration.test.ts +4 -4
  121. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/maintain-job.test.ts +5 -5
  122. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/orchestrate.test.ts +3 -3
  123. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/reconcile.test.ts +2 -2
  124. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/render-injection.test.ts +1 -1
  125. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/router.test.ts +3 -3
  126. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selection-log-store.test.ts +8 -8
  127. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selector.test.ts +3 -3
  128. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/shadow-plugin.test.ts +12 -12
  129. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/assign.ts +5 -5
  130. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/capabilities.ts +2 -2
  131. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/maintain-job.ts +8 -8
  132. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/page-content.ts +2 -2
  133. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/provider-blocks.ts +1 -1
  134. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/reconcile.ts +3 -3
  135. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/render-injection.ts +1 -1
  136. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/router.ts +3 -3
  137. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selection-log-store.ts +4 -4
  138. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selector.ts +4 -4
  139. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/shadow-plugin.ts +90 -28
  140. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/tree.ts +1 -1
  141. package/src/plugin-api/index.ts +5 -0
  142. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
  143. package/src/plugins/defaults/{memory-v3-shadow → circuit-breaker}/package.json +2 -2
  144. package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
  145. package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
  146. package/src/plugins/defaults/compaction/package.json +1 -1
  147. package/src/plugins/defaults/compaction/register.ts +19 -8
  148. package/src/plugins/defaults/compaction/terminal.ts +73 -0
  149. package/src/plugins/defaults/index.ts +5 -3
  150. package/src/plugins/defaults/{memory-retrieval/injectors.ts → injectors/register.ts} +7 -45
  151. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +7 -11
  152. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  153. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +126 -0
  154. package/src/plugins/defaults/overflow-reduce/package.json +15 -0
  155. package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
  156. package/src/plugins/external-api.ts +2 -2
  157. package/src/plugins/pipeline.ts +293 -6
  158. package/src/plugins/registry.ts +37 -9
  159. package/src/plugins/types.ts +336 -32
  160. package/src/plugins/user-loader.ts +127 -30
  161. package/src/proactive-artifact/aux-message-injector.ts +1 -1
  162. package/src/proactive-artifact/job.test.ts +1 -1
  163. package/src/prompts/__tests__/system-prompt.test.ts +0 -6
  164. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +2 -4
  165. package/src/runtime/__tests__/agent-wake.test.ts +5 -5
  166. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  167. package/src/runtime/agent-wake.ts +3 -0
  168. package/src/runtime/assistant-event-hub.ts +1 -1
  169. package/src/runtime/channel-approvals.ts +1 -1
  170. package/src/runtime/interactive-ui.ts +1 -1
  171. package/src/runtime/routes/__tests__/acp-routes.test.ts +55 -283
  172. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  173. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +4 -5
  174. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -4
  175. package/src/runtime/routes/acp-routes.test.ts +25 -89
  176. package/src/runtime/routes/acp-routes.ts +29 -81
  177. package/src/runtime/routes/approval-routes.ts +1 -1
  178. package/src/runtime/routes/browser-routes.ts +1 -1
  179. package/src/runtime/routes/browser-tabs-routes.ts +10 -6
  180. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  181. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  182. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  183. package/src/runtime/routes/conversation-routes.ts +2 -15
  184. package/src/runtime/routes/conversation-starter-routes.ts +7 -13
  185. package/src/runtime/routes/conversations-import-routes.ts +7 -24
  186. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  187. package/src/runtime/routes/host-cu-routes.ts +1 -1
  188. package/src/runtime/routes/identity-routes.ts +3 -18
  189. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  190. package/src/runtime/routes/memory-v3-routes.ts +6 -16
  191. package/src/runtime/routes/playground/helpers.ts +1 -1
  192. package/src/runtime/routes/surface-conversation-resolver.ts +3 -4
  193. package/src/runtime/routes/work-items-routes.ts +4 -2
  194. package/src/runtime/services/conversation-serializer.ts +1 -1
  195. package/src/signals/cancel.ts +4 -2
  196. package/src/subagent/manager.ts +5 -17
  197. package/src/tools/acp/list-agents.test.ts +1 -7
  198. package/src/tools/acp/spawn.test.ts +55 -158
  199. package/src/tools/acp/spawn.ts +72 -47
  200. package/src/tools/acp/steer.test.ts +8 -105
  201. package/src/tools/acp/steer.ts +17 -48
  202. package/src/tools/apps/executors.ts +8 -13
  203. package/src/tools/filesystem/write.ts +0 -34
  204. package/src/tools/subagent/spawn.ts +4 -2
  205. package/src/tools/ui-surface/definitions.ts +4 -25
  206. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +5 -4
  207. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +45 -69
  208. package/examples/plugins/echo/hooks/post-tool-use.ts +0 -18
  209. package/examples/plugins/echo/hooks/stop.ts +0 -16
  210. package/examples/plugins/echo/hooks/user-prompt-submit.ts +0 -18
  211. package/examples/plugins/echo/src/emit.ts +0 -19
  212. package/src/__tests__/compaction-circuit.test.ts +0 -258
  213. package/src/__tests__/compaction-direct.test.ts +0 -132
  214. package/src/__tests__/conversations-import-system-filter.test.ts +0 -101
  215. package/src/acp/__tests__/agent-process.test.ts +0 -161
  216. package/src/acp/__tests__/helpers/acp-history-db.ts +0 -82
  217. package/src/acp/__tests__/helpers/exec-file-stub.ts +0 -101
  218. package/src/acp/__tests__/session-manager-resume.test.ts +0 -736
  219. package/src/acp/auto-install.test.ts +0 -196
  220. package/src/acp/auto-install.ts +0 -177
  221. package/src/acp/feature-gate.test.ts +0 -48
  222. package/src/acp/feature-gate.ts +0 -34
  223. package/src/acp/resume-hint.ts +0 -25
  224. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +0 -48
  225. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +0 -57
  226. package/src/config/bundled-skills/app-builder/references/SLIDES.md +0 -38
  227. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -62
  228. package/src/daemon/conversation-registry.ts +0 -159
  229. package/src/daemon/overflow-reduction-loop.ts +0 -230
  230. package/src/memory/migrations/272-acp-session-history-cwd.ts +0 -36
  231. package/src/plugins/defaults/compaction/compact.ts +0 -59
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -75
  235. package/src/plugins/defaults/memory-v3-shadow/register.ts +0 -26
  236. package/src/tools/acp/context.ts +0 -20
  237. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
  238. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/core.test.ts +0 -0
  239. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/eval-turns.json +0 -0
  240. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/live-turns.json +0 -0
  241. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/health.test.ts +0 -0
  242. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/needle.test.ts +0 -0
  243. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/provider-blocks.test.ts +0 -0
  244. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/snapshot.test.ts +0 -0
  245. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/tree.test.ts +0 -0
  246. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
  247. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-eviction.test.ts +0 -0
  248. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-skeleton.test.ts +0 -0
  249. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/core.ts +0 -0
  250. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/README.md +0 -0
  251. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
  252. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/core.json +0 -0
  253. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
  254. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
  255. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
  256. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/health.ts +0 -0
  257. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
  258. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/needle.ts +0 -0
  259. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/orchestrate.ts +0 -0
  260. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/snapshot.ts +0 -0
  261. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/types.ts +0 -0
  262. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/working-set.ts +0 -0
@@ -35,10 +35,6 @@ mock.module("../memory/pkb/pkb-search.js", () => ({
35
35
  },
36
36
  }));
37
37
 
38
- import {
39
- clearConversations,
40
- setConversation,
41
- } from "../daemon/conversation-registry.js";
42
38
  import type {
43
39
  ChannelCapabilities,
44
40
  SlackTranscriptInputRow,
@@ -63,7 +59,6 @@ import {
63
59
  stripInjectionsForCompaction,
64
60
  stripNowScratchpad,
65
61
  } from "../daemon/conversation-runtime-assembly.js";
66
- import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
67
62
  import { buildPkbReminder } from "../daemon/pkb-reminder-builder.js";
68
63
  import type { MessageRow } from "../memory/conversation-crud.js";
69
64
  import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
@@ -108,62 +103,6 @@ function clearNowScratchpad(): void {
108
103
  rmSync(getWorkspacePromptPath("NOW.md"), { force: true });
109
104
  }
110
105
 
111
- // The workspace-context injector sources its block off the live `Conversation`
112
- // looked up by `conversationId`. Register a fake instance carrying both a
113
- // non-dirty workspace context (non-null content, not dirty) so
114
- // `resolveWorkspaceTopLevelContext` returns it verbatim without rescanning the
115
- // filesystem, and an active dynamic-page surface, so `applyRuntimeInjections`
116
- // resolves the `<workspace>` and `<active_workspace>` blocks from it;
117
- // `clearConversations()` between tests keeps suites that assert the blocks are
118
- // absent unaffected.
119
- function seedActiveSurfaceConversation(
120
- conversationId: string,
121
- workspaceText: string,
122
- surfaceId: string,
123
- data: SurfaceData,
124
- channelCapabilities?: ChannelCapabilities,
125
- commandIntent?: { type: string; payload?: string; languageCode?: string },
126
- ): void {
127
- setConversation(conversationId, {
128
- conversationId,
129
- workingDir: "/sandbox",
130
- workspaceTopLevelContext: workspaceText,
131
- workspaceTopLevelDirty: false,
132
- currentActiveSurfaceId: surfaceId,
133
- surfaceState: new Map<
134
- string,
135
- { surfaceType: SurfaceType; data: SurfaceData }
136
- >([[surfaceId, { surfaceType: "dynamic_page", data }]]),
137
- channelCapabilities: channelCapabilities ?? undefined,
138
- commandIntent,
139
- } as never);
140
- }
141
-
142
- function clearWorkspaceContext(): void {
143
- clearConversations();
144
- }
145
-
146
- // The `<channel_capabilities>` branch and the Slack gates source the channel
147
- // capabilities off the live `Conversation` looked up by `conversationId`.
148
- // Register a fake fallback instance carrying the given capabilities (and a
149
- // non-dirty empty workspace + empty surface so the other live-sourced branches
150
- // stay inert) so `applyRuntimeInjections` resolves them; `clearConversations()`
151
- // between tests keeps suites asserting absence unaffected.
152
- function seedChannelCapabilitiesConversation(
153
- caps: ChannelCapabilities | null,
154
- transportHints?: string[],
155
- ): void {
156
- setConversation("runtime-assembly-fallback", {
157
- conversationId: "runtime-assembly-fallback",
158
- workingDir: "/sandbox",
159
- workspaceTopLevelContext: "",
160
- workspaceTopLevelDirty: false,
161
- surfaceState: new Map(),
162
- channelCapabilities: caps ?? undefined,
163
- transportHints,
164
- } as never);
165
- }
166
-
167
106
  // ---------------------------------------------------------------------------
168
107
  // resolveChannelCapabilities
169
108
  // ---------------------------------------------------------------------------
@@ -636,8 +575,6 @@ describe("stripChannelCapabilityContext", () => {
636
575
  // ---------------------------------------------------------------------------
637
576
 
638
577
  describe("applyRuntimeInjections with channelCapabilities", () => {
639
- afterEach(clearConversations);
640
-
641
578
  const baseMessages: Message[] = [
642
579
  {
643
580
  role: "user",
@@ -653,8 +590,9 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
653
590
  supportsVoiceInput: false,
654
591
  };
655
592
 
656
- seedChannelCapabilitiesConversation(caps);
657
- const { messages: result } = await applyRuntimeInjections(baseMessages, {});
593
+ const { messages: result } = await applyRuntimeInjections(baseMessages, {
594
+ channelCapabilities: caps,
595
+ });
658
596
 
659
597
  expect(result.length).toBe(1);
660
598
  expect(result[0].content.length).toBe(2);
@@ -665,8 +603,9 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
665
603
  });
666
604
 
667
605
  test("does not inject when channelCapabilities is null", async () => {
668
- seedChannelCapabilitiesConversation(null);
669
- const { messages: result } = await applyRuntimeInjections(baseMessages, {});
606
+ const { messages: result } = await applyRuntimeInjections(baseMessages, {
607
+ channelCapabilities: null,
608
+ });
670
609
 
671
610
  expect(result.length).toBe(1);
672
611
  expect(result[0].content.length).toBe(1);
@@ -687,8 +626,9 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
687
626
  supportsVoiceInput: false,
688
627
  };
689
628
 
690
- seedChannelCapabilitiesConversation(caps);
691
- const { messages: result } = await applyRuntimeInjections(baseMessages, {});
629
+ const { messages: result } = await applyRuntimeInjections(baseMessages, {
630
+ channelCapabilities: caps,
631
+ });
692
632
 
693
633
  expect(result.length).toBe(1);
694
634
  // channelCapabilities prepends
@@ -815,14 +755,16 @@ describe("applyRuntimeInjections — injection mode", () => {
815
755
  },
816
756
  ];
817
757
 
818
- const channelCapabilities: ChannelCapabilities = {
819
- channel: "telegram",
820
- dashboardCapable: false,
821
- supportsDynamicUi: false,
822
- supportsVoiceInput: false,
823
- };
824
-
825
758
  const fullOptions = {
759
+ workspaceTopLevelContext: "<workspace>\nRoot: /sandbox\n</workspace>",
760
+ channelCommandContext: { type: "start" } as const,
761
+ activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
762
+ channelCapabilities: {
763
+ channel: "telegram",
764
+ dashboardCapable: false,
765
+ supportsDynamicUi: false,
766
+ supportsVoiceInput: false,
767
+ } as ChannelCapabilities,
826
768
  unifiedTurnContext:
827
769
  "<turn_context>\ncurrent_time: 2026-03-04 (Tuesday) 12:00:00 +00:00 (UTC)\ninterface: telegram\n</turn_context>",
828
770
  isNonInteractive: true,
@@ -846,19 +788,10 @@ describe("applyRuntimeInjections — injection mode", () => {
846
788
  beforeEach(() => {
847
789
  seedPkbContent();
848
790
  seedNowScratchpad("Current focus: shipping PR 3");
849
- seedActiveSurfaceConversation(
850
- "injection-mode-conv",
851
- "<workspace>\nRoot: /sandbox\n</workspace>",
852
- "sf_1",
853
- { html: "<div>test</div>" },
854
- channelCapabilities,
855
- { type: "start" },
856
- );
857
791
  });
858
792
  afterEach(() => {
859
793
  clearPkbContent();
860
794
  clearNowScratchpad();
861
- clearWorkspaceContext();
862
795
  });
863
796
 
864
797
  test("full mode (default) includes all injections", async () => {
@@ -1118,7 +1051,7 @@ describe("stripInjectionsForCompaction preserves persistent blocks", () => {
1118
1051
  ).toContain("<turn_context>");
1119
1052
  });
1120
1053
 
1121
- test("<workspace> blocks ARE stripped so the injector re-sources them post-compaction", () => {
1054
+ test("<workspace> blocks are NOT stripped", () => {
1122
1055
  const messages: Message[] = [
1123
1056
  {
1124
1057
  role: "user",
@@ -1134,10 +1067,10 @@ describe("stripInjectionsForCompaction preserves persistent blocks", () => {
1134
1067
 
1135
1068
  const result = stripInjectionsForCompaction(messages);
1136
1069
  expect(result.length).toBe(1);
1137
- expect(result[0].content.length).toBe(1);
1138
- expect((result[0].content[0] as { type: "text"; text: string }).text).toBe(
1139
- "Hello",
1140
- );
1070
+ expect(result[0].content.length).toBe(2);
1071
+ expect(
1072
+ (result[0].content[0] as { type: "text"; text: string }).text,
1073
+ ).toContain("<workspace>");
1141
1074
  });
1142
1075
 
1143
1076
  test("legacy <workspace_top_level> blocks ARE stripped for backward compat", () => {
@@ -2419,8 +2352,6 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2419
2352
  // ---------------------------------------------------------------------------
2420
2353
 
2421
2354
  describe("Slack channel chronological rendering — multi-thread", () => {
2422
- afterEach(clearConversations);
2423
-
2424
2355
  // Slack ts values are seconds-since-epoch with microsecond precision.
2425
2356
  // Pick a few stable anchors so thread aliases (sha-derived) stay
2426
2357
  // predictable across the scenarios.
@@ -2506,8 +2437,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2506
2437
  role: "user",
2507
2438
  content: [{ type: "text", text: "current turn" }],
2508
2439
  };
2509
- seedChannelCapabilitiesConversation(slackChannelCaps);
2510
2440
  const { messages } = await applyRuntimeInjections([lastUserMessage], {
2441
+ channelCapabilities: slackChannelCaps,
2511
2442
  slackChronologicalMessages,
2512
2443
  });
2513
2444
  return messages;
@@ -2795,15 +2726,15 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2795
2726
  role: "user",
2796
2727
  content: [{ type: "text", text: "vellum question" }],
2797
2728
  };
2798
- seedChannelCapabilitiesConversation({
2799
- channel: "vellum",
2800
- dashboardCapable: true,
2801
- supportsDynamicUi: true,
2802
- supportsVoiceInput: true,
2803
- });
2804
2729
  const { messages: result } = await applyRuntimeInjections(
2805
2730
  [lastUserMessage],
2806
2731
  {
2732
+ channelCapabilities: {
2733
+ channel: "vellum",
2734
+ dashboardCapable: true,
2735
+ supportsDynamicUi: true,
2736
+ supportsVoiceInput: true,
2737
+ },
2807
2738
  // Even if we accidentally pass a chronological transcript, the
2808
2739
  // branch must be a no-op for non-slack channels.
2809
2740
  slackChronologicalMessages: [
@@ -2833,16 +2764,16 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2833
2764
  role: "user",
2834
2765
  content: [{ type: "text", text: "DM question" }],
2835
2766
  };
2836
- seedChannelCapabilitiesConversation({
2837
- channel: "slack",
2838
- dashboardCapable: false,
2839
- supportsDynamicUi: false,
2840
- supportsVoiceInput: false,
2841
- chatType: "im",
2842
- });
2843
2767
  const { messages: result } = await applyRuntimeInjections(
2844
2768
  [lastUserMessage],
2845
2769
  {
2770
+ channelCapabilities: {
2771
+ channel: "slack",
2772
+ dashboardCapable: false,
2773
+ supportsDynamicUi: false,
2774
+ supportsVoiceInput: false,
2775
+ chatType: "im",
2776
+ },
2846
2777
  slackChronologicalMessages: [
2847
2778
  {
2848
2779
  role: "user",
@@ -2901,10 +2832,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2901
2832
  ],
2902
2833
  },
2903
2834
  ];
2904
- seedChannelCapabilitiesConversation(slackCaps);
2905
2835
  const { messages: result } = await applyRuntimeInjections(
2906
2836
  runMessagesWithMemory,
2907
2837
  {
2838
+ channelCapabilities: slackCaps,
2908
2839
  slackChronologicalMessages: [
2909
2840
  {
2910
2841
  role: "user",
@@ -2965,10 +2896,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2965
2896
  ],
2966
2897
  },
2967
2898
  ];
2968
- seedChannelCapabilitiesConversation(slackCaps);
2969
2899
  const { messages: result } = await applyRuntimeInjections(
2970
2900
  runMessagesWithMemory,
2971
2901
  {
2902
+ channelCapabilities: slackCaps,
2972
2903
  slackChronologicalMessages: [
2973
2904
  {
2974
2905
  role: "user",
@@ -3004,10 +2935,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3004
2935
  supportsVoiceInput: false,
3005
2936
  chatType: "im",
3006
2937
  };
3007
- seedChannelCapabilitiesConversation(slackCaps);
3008
2938
  const { messages: result } = await applyRuntimeInjections(
3009
2939
  [{ role: "user", content: [{ type: "text", text: "inbound" }] }],
3010
2940
  {
2941
+ channelCapabilities: slackCaps,
3011
2942
  slackChronologicalMessages: [
3012
2943
  {
3013
2944
  role: "user",
@@ -3048,13 +2979,12 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3048
2979
  { loader: () => rows, trustClass: "guardian" },
3049
2980
  );
3050
2981
 
3051
- seedChannelCapabilitiesConversation(slackChannelCaps, [
3052
- "thread context: ...",
3053
- ]);
3054
2982
  const { messages: result } = await applyRuntimeInjections(
3055
2983
  [{ role: "user", content: [{ type: "text", text: "current turn" }] }],
3056
2984
  {
2985
+ channelCapabilities: slackChannelCaps,
3057
2986
  slackChronologicalMessages,
2987
+ transportHints: ["thread context: ..."],
3058
2988
  },
3059
2989
  );
3060
2990
 
@@ -3079,10 +3009,12 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3079
3009
  chatType: "im",
3080
3010
  };
3081
3011
 
3082
- seedChannelCapabilitiesConversation(slackDmCaps, ["dm context: ..."]);
3083
3012
  const { messages: result } = await applyRuntimeInjections(
3084
3013
  [{ role: "user", content: [{ type: "text", text: "hi DM" }] }],
3085
- {},
3014
+ {
3015
+ channelCapabilities: slackDmCaps,
3016
+ transportHints: ["dm context: ..."],
3017
+ },
3086
3018
  );
3087
3019
 
3088
3020
  const allText = result
@@ -3096,19 +3028,18 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3096
3028
 
3097
3029
  // ── transport_hints kept for non-slack channels ───────────────────────
3098
3030
  test("non-slack conversations still receive <transport_hints>", async () => {
3099
- seedChannelCapabilitiesConversation(
3100
- {
3101
- channel: "telegram",
3102
- dashboardCapable: false,
3103
- supportsDynamicUi: false,
3104
- supportsVoiceInput: false,
3105
- chatType: "private",
3106
- },
3107
- ["please answer concisely"],
3108
- );
3109
3031
  const { messages: result } = await applyRuntimeInjections(
3110
3032
  [{ role: "user", content: [{ type: "text", text: "hi" }] }],
3111
- {},
3033
+ {
3034
+ channelCapabilities: {
3035
+ channel: "telegram",
3036
+ dashboardCapable: false,
3037
+ supportsDynamicUi: false,
3038
+ supportsVoiceInput: false,
3039
+ chatType: "private",
3040
+ },
3041
+ transportHints: ["please answer concisely"],
3042
+ },
3112
3043
  );
3113
3044
  const allText = result
3114
3045
  .flatMap((m) => m.content)
@@ -3433,8 +3364,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3433
3364
  role: "user",
3434
3365
  content: [{ type: "text", text: "current turn" }],
3435
3366
  };
3436
- seedChannelCapabilitiesConversation(slackChannelCaps);
3437
3367
  const { messages } = await applyRuntimeInjections([lastUserMessage], {
3368
+ channelCapabilities: slackChannelCaps,
3438
3369
  slackChronologicalMessages,
3439
3370
  slackActiveThreadFocusBlock: focusBlock,
3440
3371
  });
@@ -3703,8 +3634,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3703
3634
  chatType: "channel",
3704
3635
  };
3705
3636
  const newFocus = "<active_thread>\nnewly built\n</active_thread>";
3706
- seedChannelCapabilitiesConversation(slackChannelCaps);
3707
3637
  const { messages: reInjected } = await applyRuntimeInjections(stripped, {
3638
+ channelCapabilities: slackChannelCaps,
3708
3639
  slackActiveThreadFocusBlock: newFocus,
3709
3640
  });
3710
3641
  const reInjectedTexts = reInjected
@@ -3724,15 +3655,15 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3724
3655
  // Defensive: the focus injection is gated on `slackChannel` (i.e.
3725
3656
  // `isSlackChannelConversation`). Even if a caller mistakenly forwards
3726
3657
  // a focus block on a non-Slack channel, it must NOT be appended.
3727
- seedChannelCapabilitiesConversation({
3728
- channel: "vellum",
3729
- dashboardCapable: true,
3730
- supportsDynamicUi: true,
3731
- supportsVoiceInput: true,
3732
- });
3733
3658
  const { messages: result } = await applyRuntimeInjections(
3734
3659
  [{ role: "user", content: [{ type: "text", text: "vellum question" }] }],
3735
3660
  {
3661
+ channelCapabilities: {
3662
+ channel: "vellum",
3663
+ dashboardCapable: true,
3664
+ supportsDynamicUi: true,
3665
+ supportsVoiceInput: true,
3666
+ },
3736
3667
  slackActiveThreadFocusBlock: "<active_thread>\nbogus\n</active_thread>",
3737
3668
  },
3738
3669
  );
@@ -3749,16 +3680,16 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3749
3680
  // Same as above but for Slack DMs (chatType === "im"). The focus
3750
3681
  // injection is keyed on `isSlackChannelConversation` which excludes
3751
3682
  // DMs, so the block must not appear.
3752
- seedChannelCapabilitiesConversation({
3753
- channel: "slack",
3754
- dashboardCapable: false,
3755
- supportsDynamicUi: false,
3756
- supportsVoiceInput: false,
3757
- chatType: "im",
3758
- });
3759
3683
  const { messages: result } = await applyRuntimeInjections(
3760
3684
  [{ role: "user", content: [{ type: "text", text: "DM question" }] }],
3761
3685
  {
3686
+ channelCapabilities: {
3687
+ channel: "slack",
3688
+ dashboardCapable: false,
3689
+ supportsDynamicUi: false,
3690
+ supportsVoiceInput: false,
3691
+ chatType: "im",
3692
+ },
3762
3693
  slackActiveThreadFocusBlock: "<active_thread>\nbogus\n</active_thread>",
3763
3694
  },
3764
3695
  );
@@ -1,12 +1,6 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
1
+ import { describe, expect, test } from "bun:test";
2
2
 
3
- import {
4
- clearConversations,
5
- setConversation,
6
- } from "../daemon/conversation-registry.js";
7
3
  import { applyRuntimeInjections } from "../daemon/conversation-runtime-assembly.js";
8
- import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
9
- import { createApp } from "../memory/app-store.js";
10
4
  import type { Message } from "../providers/types.js";
11
5
 
12
6
  // ---------------------------------------------------------------------------
@@ -17,46 +11,6 @@ function userMsg(text: string): Message {
17
11
  return { role: "user", content: [{ type: "text", text }] };
18
12
  }
19
13
 
20
- // `applyRuntimeInjections` synthesizes this conversation id when no
21
- // `turnContext` is supplied, so the `workspace-context` injector resolves the
22
- // live workspace block from the registry under this key.
23
- const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
24
-
25
- // Register the fallback conversation in the live registry so the runtime
26
- // injectors resolve their blocks from it (the orchestrator no longer threads
27
- // workspace or active-surface content as options).
28
- function registerFallbackConversation(fields: Record<string, unknown>): void {
29
- setConversation(FALLBACK_CONVERSATION_ID, {
30
- conversationId: FALLBACK_CONVERSATION_ID,
31
- workingDir: "/sandbox",
32
- // Non-dirty empty workspace by default so the workspace-context injector
33
- // skips both the filesystem rescan and the DB refresh unless a test
34
- // explicitly seeds a block via `workspaceTopLevelContext`.
35
- workspaceTopLevelContext: "",
36
- workspaceTopLevelDirty: false,
37
- ...fields,
38
- } as never);
39
- }
40
-
41
- // Seed the live conversation registry with a pre-rendered top-level block. The
42
- // cache is non-dirty with non-null content, so `resolveWorkspaceTopLevelContext`
43
- // returns it verbatim without rescanning the filesystem.
44
- function seedWorkspaceContext(text: string): void {
45
- registerFallbackConversation({
46
- workspaceTopLevelContext: text,
47
- workspaceTopLevelDirty: false,
48
- });
49
- }
50
-
51
- // Build the conversation surface-state map that `buildActiveSurfaceContext`
52
- // reads to render the `<active_workspace>` block.
53
- function makeSurfaceState(
54
- surfaceId: string,
55
- data: SurfaceData,
56
- ): Map<string, { surfaceType: SurfaceType; data: SurfaceData }> {
57
- return new Map([[surfaceId, { surfaceType: "dynamic_page", data }]]);
58
- }
59
-
60
14
  // ---------------------------------------------------------------------------
61
15
  // Tests
62
16
  // ---------------------------------------------------------------------------
@@ -65,20 +19,15 @@ const sampleContext =
65
19
  "<workspace>\nRoot: /sandbox\nDirectories: src, lib, tests\n</workspace>";
66
20
 
67
21
  // The workspace-context default injector emits the workspace block as a
68
- // `prepend-user-tail` placement during `applyRuntimeInjections`. It sources
69
- // the rendered block from the per-conversation workspace registry and
70
- // (re)injects only when the block is absent from the working messages, so the
71
- // suite seeds the registry and exercises that end-to-end path.
22
+ // `prepend-user-tail` placement during `applyRuntimeInjections`. The suite
23
+ // below exercises that end-to-end path.
72
24
 
73
25
  describe("applyRuntimeInjections — workspace top-level context", () => {
74
- afterEach(() => {
75
- clearConversations();
76
- });
77
-
78
- test("injects workspace context when registered", async () => {
79
- seedWorkspaceContext(sampleContext);
26
+ test("injects workspace context when provided", async () => {
80
27
  const messages: Message[] = [userMsg("Hello")];
81
- const { messages: result } = await applyRuntimeInjections(messages, {});
28
+ const { messages: result } = await applyRuntimeInjections(messages, {
29
+ workspaceTopLevelContext: sampleContext,
30
+ });
82
31
 
83
32
  expect(result).toHaveLength(1);
84
33
  expect(result[0].content).toHaveLength(2);
@@ -86,38 +35,22 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
86
35
  expect((result[0].content[1] as { text: string }).text).toBe("Hello");
87
36
  });
88
37
 
89
- test("does not inject when no workspace context is registered", async () => {
38
+ test("does not inject when workspace context is null", async () => {
90
39
  const messages: Message[] = [userMsg("Hello")];
91
- const { messages: result } = await applyRuntimeInjections(messages, {});
40
+ const { messages: result } = await applyRuntimeInjections(messages, {
41
+ workspaceTopLevelContext: null,
42
+ });
92
43
 
93
44
  expect(result).toHaveLength(1);
94
45
  expect(result[0].content).toHaveLength(1);
95
46
  });
96
47
 
97
- test("does not re-inject when the workspace block is already present", async () => {
98
- // GIVEN the registry holds a workspace block AND the working messages
99
- // already carry that block (a normal cached turn, post-injection).
100
- seedWorkspaceContext(sampleContext);
101
- const messages: Message[] = [userMsg(sampleContext), userMsg("Hello")];
102
-
103
- // WHEN injections are applied
104
- const { messages: result } = await applyRuntimeInjections(messages, {});
105
-
106
- // THEN presence detection skips the block to keep the prefix stable.
107
- expect(result).toHaveLength(2);
108
- expect(result[1].content).toHaveLength(1);
109
- expect((result[1].content[0] as { text: string }).text).toBe("Hello");
110
- });
111
-
112
48
  test("workspace context appears before active surface context in content", async () => {
113
- registerFallbackConversation({
49
+ const messages: Message[] = [userMsg("Hello")];
50
+ const { messages: result } = await applyRuntimeInjections(messages, {
51
+ activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
114
52
  workspaceTopLevelContext: sampleContext,
115
- workspaceTopLevelDirty: false,
116
- currentActiveSurfaceId: "sf_1",
117
- surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
118
53
  });
119
- const messages: Message[] = [userMsg("Hello")];
120
- const { messages: result } = await applyRuntimeInjections(messages, {});
121
54
 
122
55
  // Workspace is injected last (in applyRuntimeInjections order) so it
123
56
  // prepends to whatever was already prepended by activeSurface.
@@ -131,20 +64,16 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
131
64
  });
132
65
 
133
66
  test("app-backed active surface tells the model to load app-builder with the right argument", async () => {
134
- const app = createApp({
135
- name: "Example App",
136
- schemaJson: "{}",
137
- htmlDefinition: "<div>test</div>",
138
- });
139
- registerFallbackConversation({
140
- currentActiveSurfaceId: "sf_1",
141
- surfaceState: makeSurfaceState("sf_1", {
67
+ const messages: Message[] = [userMsg("Edit this app")];
68
+ const { messages: result } = await applyRuntimeInjections(messages, {
69
+ activeSurface: {
70
+ surfaceId: "sf_1",
142
71
  html: "<div>test</div>",
143
- appId: app.id,
144
- }),
72
+ appId: "app-1",
73
+ appName: "Example App",
74
+ },
75
+ workspaceTopLevelContext: null,
145
76
  });
146
- const messages: Message[] = [userMsg("Edit this app")];
147
- const { messages: result } = await applyRuntimeInjections(messages, {});
148
77
 
149
78
  const activeWorkspaceText = (result[0].content[0] as { text: string }).text;
150
79
  expect(activeWorkspaceText).toContain('skill: "app-builder"');
@@ -153,14 +82,10 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
153
82
  });
154
83
 
155
84
  describe("applyRuntimeInjections — minimal mode skips workspace blocks", () => {
156
- afterEach(() => {
157
- clearConversations();
158
- });
159
-
160
85
  test("minimal mode skips workspace top-level context", async () => {
161
- seedWorkspaceContext(sampleContext);
162
86
  const messages: Message[] = [userMsg("Hello")];
163
87
  const { messages: result } = await applyRuntimeInjections(messages, {
88
+ workspaceTopLevelContext: sampleContext,
164
89
  mode: "minimal",
165
90
  });
166
91
 
@@ -170,12 +95,9 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
170
95
  });
171
96
 
172
97
  test("minimal mode skips active surface context", async () => {
173
- registerFallbackConversation({
174
- currentActiveSurfaceId: "sf_1",
175
- surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
176
- });
177
98
  const messages: Message[] = [userMsg("Hello")];
178
99
  const { messages: result } = await applyRuntimeInjections(messages, {
100
+ activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
179
101
  mode: "minimal",
180
102
  });
181
103
 
@@ -185,14 +107,11 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
185
107
  });
186
108
 
187
109
  test("full mode (default) still includes workspace blocks", async () => {
188
- registerFallbackConversation({
110
+ const messages: Message[] = [userMsg("Hello")];
111
+ const { messages: result } = await applyRuntimeInjections(messages, {
189
112
  workspaceTopLevelContext: sampleContext,
190
- workspaceTopLevelDirty: false,
191
- currentActiveSurfaceId: "sf_1",
192
- surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
113
+ activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
193
114
  });
194
- const messages: Message[] = [userMsg("Hello")];
195
- const { messages: result } = await applyRuntimeInjections(messages, {});
196
115
 
197
116
  expect(result[0].content).toHaveLength(3);
198
117
  expect((result[0].content[0] as { text: string }).text).toBe(sampleContext);
@@ -339,8 +339,8 @@ describe("GET /v1/conversation-starters", () => {
339
339
 
340
340
  test("does not re-enqueue for invalid items when within cooldown period", async () => {
341
341
  const now = Date.now();
342
- // Last attempt 30 seconds ago (within the 5-minute cooldown)
343
- const recentAttemptAt = now - 30_000;
342
+ // Generation happened 30 seconds ago (within the 60s cooldown)
343
+ const recentGenAt = now - 30_000;
344
344
  insertStarter({
345
345
  label: "Let me check calendar",
346
346
  prompt: "Let me check what Alice has today.",
@@ -353,13 +353,9 @@ describe("GET /v1/conversation-starters", () => {
353
353
  category: "productivity",
354
354
  createdAt: now - 1,
355
355
  });
356
- setCheckpoint(
357
- "conversation_starters:last_attempt_at:default",
358
- String(recentAttemptAt),
359
- );
360
356
  setCheckpoint(
361
357
  "conversation_starters:last_gen_at:default",
362
- String(recentAttemptAt),
358
+ String(recentGenAt),
363
359
  );
364
360
  setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
365
361
  insertMemoryItem();
@@ -379,8 +375,8 @@ describe("GET /v1/conversation-starters", () => {
379
375
 
380
376
  test("re-enqueues for invalid items after cooldown expires", async () => {
381
377
  const now = Date.now();
382
- // Last attempt 6 minutes ago (past the 5-minute cooldown)
383
- const oldAttemptAt = now - 6 * 60_000;
378
+ // Generation happened 90 seconds ago (past the 60s cooldown)
379
+ const oldGenAt = now - 90_000;
384
380
  insertStarter({
385
381
  label: "Let me check calendar",
386
382
  prompt: "Let me check what Alice has today.",
@@ -393,13 +389,9 @@ describe("GET /v1/conversation-starters", () => {
393
389
  category: "productivity",
394
390
  createdAt: now - 1,
395
391
  });
396
- setCheckpoint(
397
- "conversation_starters:last_attempt_at:default",
398
- String(oldAttemptAt),
399
- );
400
392
  setCheckpoint(
401
393
  "conversation_starters:last_gen_at:default",
402
- String(oldAttemptAt),
394
+ String(oldGenAt),
403
395
  );
404
396
  setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
405
397
  insertMemoryItem();