@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
@@ -47,10 +47,7 @@ import {
47
47
  estimatePromptTokensWithTools,
48
48
  getCalibrationProviderKey,
49
49
  } from "../context/token-estimator.js";
50
- import type {
51
- ContextWindowCompactOptions,
52
- ContextWindowManager,
53
- } from "../context/window-manager.js";
50
+ import type { ContextWindowManager } from "../context/window-manager.js";
54
51
  import type { ToolProfiler } from "../events/tool-profiling-listener.js";
55
52
  import { writeRelationshipState } from "../home/relationship-state-writer.js";
56
53
  import {
@@ -86,14 +83,22 @@ import { enqueueMemoryRetrospectiveOnCompaction } from "../memory/memory-retrosp
86
83
  import type { PermissionPrompter } from "../permissions/prompter.js";
87
84
  import { HOOKS } from "../plugin-api/constants.js";
88
85
  import type { UserPromptSubmitContext } from "../plugin-api/types.js";
89
- import { defaultCompact } from "../plugins/defaults/compaction/compact.js";
86
+ import { defaultCompactionTerminal } from "../plugins/defaults/compaction/terminal.js";
90
87
  import { deepRepairHistory } from "../plugins/defaults/history-repair/terminal.js";
91
88
  import postCompactReinject from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
92
89
  import userPromptSubmitMemoryRetrieval, {
93
90
  type MemoryRetrievalHookContext,
94
91
  } from "../plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.js";
95
- import { runHook } from "../plugins/pipeline.js";
96
- import type { TurnContext as PluginTurnContext } from "../plugins/types.js";
92
+ import { DEFAULT_TIMEOUTS, runHook, runPipeline } from "../plugins/pipeline.js";
93
+ import { getMiddlewaresFor } from "../plugins/registry.js";
94
+ import type {
95
+ CompactionArgs,
96
+ CompactionResult,
97
+ OverflowReduceArgs,
98
+ OverflowReduceResult,
99
+ TurnContext as PluginTurnContext,
100
+ } from "../plugins/types.js";
101
+ import { PluginExecutionError, PluginTimeoutError } from "../plugins/types.js";
97
102
  import type { ContentBlock, Message } from "../providers/types.js";
98
103
  import type { Provider } from "../providers/types.js";
99
104
  import { resolveActorTrust } from "../runtime/actor-trust-resolver.js";
@@ -144,8 +149,11 @@ import type {
144
149
  } from "./conversation-runtime-assembly.js";
145
150
  import {
146
151
  applyRuntimeInjections,
152
+ buildActiveDocuments,
153
+ buildActiveSurfaceContext,
147
154
  buildSubagentStatusBlock,
148
155
  buildUnifiedTurnContextBlock,
156
+ buildWorkspaceTopLevelContext,
149
157
  getSlackCompactionWatermarkForPrefix,
150
158
  inboundActorContextFromTrust,
151
159
  inboundActorContextFromTrustContext,
@@ -170,10 +178,6 @@ import type {
170
178
  UsageStats,
171
179
  } from "./message-protocol.js";
172
180
  import type { ConfirmationStateChanged } from "./message-types/messages.js";
173
- import {
174
- type OverflowReduceArgs,
175
- runOverflowReductionLoop,
176
- } from "./overflow-reduction-loop.js";
177
181
  import { parseActualTokensFromError } from "./parse-actual-tokens-from-error.js";
178
182
  import {
179
183
  persistUnsendableImageDowngrades,
@@ -213,12 +217,12 @@ function formatDiskPressureBlockedMessage(): string {
213
217
  // ── Plugin pipeline helpers ──────────────────────────────────────────
214
218
  //
215
219
  // Canonical {@link PluginTurnContext} builder threaded into every
216
- // `runHook` call inside `runAgentLoopImpl`. The orchestrator composes
220
+ // `runPipeline` call inside `runAgentLoopImpl`. The orchestrator composes
217
221
  // the context on demand at each call site from ambient state rather than
218
222
  // carrying a persistent `TurnContext` instance across the turn.
219
223
 
220
224
  /**
221
- * Synthetic fallback trust context used when the orchestrator fires a hook
225
+ * Synthetic fallback trust context used when the orchestrator fires a pipeline
222
226
  * before the per-turn trust snapshot has been captured (e.g. invocations that
223
227
  * bypass `processMessage` / `drainQueue`). We bias to `unknown` rather than
224
228
  * `guardian` so a missing snapshot cannot accidentally grant elevated trust
@@ -230,14 +234,14 @@ const FALLBACK_TURN_TRUST: TrustContext = {
230
234
  };
231
235
 
232
236
  /**
233
- * Build the {@link TurnContext} passed to {@link runHook}.
237
+ * Build the {@link TurnContext} passed to {@link runPipeline}.
234
238
  *
235
- * Canonical source of truth for every hook call site inside the agent
236
- * loop. Every `runHook` invocation in `runAgentLoopImpl` (and in the
239
+ * Canonical source of truth for every pipeline call site inside the agent
240
+ * loop. Every `runPipeline` invocation in `runAgentLoopImpl` (and in the
237
241
  * handlers that share its ambient state) must route through this helper
238
242
  * rather than constructing a `TurnContext` literal inline — this keeps
239
243
  * `turnIndex`, trust resolution, and the `contextWindowManager` attachment
240
- * consistent across hooks, which in turn keeps structured logs
244
+ * consistent across pipeline slots, which in turn keeps structured logs
241
245
  * filtered by `conversationId`/`turnIndex` coherent across slots.
242
246
  *
243
247
  * Behavior:
@@ -249,9 +253,9 @@ const FALLBACK_TURN_TRUST: TrustContext = {
249
253
  * level context, then {@link FALLBACK_TURN_TRUST}. The cascade matches
250
254
  * the one inside the orchestrator's inline injection assembly so
251
255
  * middleware reads the same trust class the runtime sees.
252
- * - `contextWindowManager` is attached unconditionally. Hooks that
253
- * don't need it can ignore it; it remains available via the typed
254
- * optional field on `TurnContext`.
256
+ * - `contextWindowManager` is attached unconditionally. Pipelines that
257
+ * don't need it can ignore it; the default compaction plugin reads it
258
+ * via the typed optional field on `TurnContext`.
255
259
  */
256
260
  function buildPluginTurnContext(
257
261
  ctx: AgentLoopConversationContext,
@@ -363,6 +367,8 @@ export interface AgentLoopConversationContext {
363
367
  currentTurnSurfaces: AssistantSurface[];
364
368
 
365
369
  workingDir: string;
370
+ workspaceTopLevelContext: string | null;
371
+ workspaceTopLevelDirty: boolean;
366
372
  channelCapabilities?: ChannelCapabilities;
367
373
  /** Per-turn snapshot of trustContext, frozen at message-processing start. */
368
374
  currentTurnTrustContext?: TrustContext;
@@ -388,6 +394,8 @@ export interface AgentLoopConversationContext {
388
394
  /** Task-run scope for the current turn. Cleared at turn end so queued/drained turns don't inherit it. */
389
395
  taskRunId?: string;
390
396
  assistantId?: string;
397
+ voiceCallControlPrompt?: string;
398
+ transportHints?: string[];
391
399
  clientTimezone?: string;
392
400
 
393
401
  readonly coreToolNames: Set<string>;
@@ -462,6 +470,7 @@ export interface AgentLoopConversationContext {
462
470
  getWorkspaceGitService?: (workspaceDir: string) => GitServiceInitializer;
463
471
  commitTurnChanges?: typeof commitTurnChanges;
464
472
 
473
+ refreshWorkspaceTopLevelContextIfNeeded(): void;
465
474
  markWorkspaceTopLevelDirty(): void;
466
475
  getQueueDepth(): number;
467
476
  hasQueuedMessages(): boolean;
@@ -763,6 +772,10 @@ export async function runAgentLoopImpl(
763
772
  : null,
764
773
  },
765
774
  );
775
+ const diskPressureContext =
776
+ diskPressureDecision.action === "allow-cleanup-mode"
777
+ ? { cleanupModeActive: true }
778
+ : null;
766
779
  ctx.diskPressureCleanupModeActive =
767
780
  diskPressureDecision.action === "allow-cleanup-mode";
768
781
 
@@ -872,6 +885,7 @@ export async function runAgentLoopImpl(
872
885
  // so this fires exactly once per `/compact` event.
873
886
  const consumedPostCompactReinject = ctx.pendingPostCompactReinject;
874
887
  ctx.pendingPostCompactReinject = false;
888
+ state.shouldInjectWorkspace = isFirstMessage || consumedPostCompactReinject;
875
889
  let compactedThisTurn = consumedPostCompactReinject;
876
890
  let slackCompactedThisTurn = false;
877
891
  const isSlackConversation = ctx.channelCapabilities?.channel === "slack";
@@ -1009,24 +1023,58 @@ export async function runAgentLoopImpl(
1009
1023
  // Skip auto-compaction while the circuit breaker is open. Force paths
1010
1024
  // and user-initiated /compact bypass this check.
1011
1025
  const autoCompactAllowed =
1012
- !(await ctx.agentLoop.compactionCircuit.isOpen());
1026
+ !(await ctx.agentLoop.compactionCircuit.isOpen(ctx));
1013
1027
  if (compactCheck.needed && autoCompactAllowed) {
1014
1028
  ctx.emitActivityState("thinking", "context_compacting", {
1015
1029
  requestId: reqId,
1016
1030
  });
1017
1031
  }
1032
+ const compactionOptions = {
1033
+ precomputedEstimate: compactCheck.estimatedTokens,
1034
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
1035
+ actorTrustClass: resolveTurnActorTrustClass(ctx),
1036
+ };
1018
1037
  let compacted: Awaited<
1019
1038
  ReturnType<typeof ctx.contextWindowManager.maybeCompact>
1020
1039
  > | null = null;
1021
1040
  if (autoCompactAllowed) {
1022
- compacted = await defaultCompact({
1023
- manager: ctx.contextWindowManager,
1024
- messages: messagesForStartOfTurnCompaction,
1025
- signal: abortController.signal,
1026
- precomputedEstimate: compactCheck.estimatedTokens,
1027
- overrideProfile: resolveCurrentOverrideProfile() ?? null,
1028
- actorTrustClass: resolveTurnActorTrustClass(ctx),
1029
- });
1041
+ try {
1042
+ compacted = (await runPipeline<CompactionArgs, CompactionResult>(
1043
+ "compaction",
1044
+ getMiddlewaresFor("compaction"),
1045
+ (args) =>
1046
+ defaultCompactionTerminal(args, buildPluginTurnContext(ctx, reqId)),
1047
+ {
1048
+ messages: messagesForStartOfTurnCompaction,
1049
+ signal: abortController.signal,
1050
+ options: compactionOptions,
1051
+ },
1052
+ buildPluginTurnContext(ctx, reqId),
1053
+ DEFAULT_TIMEOUTS.compaction,
1054
+ )) as Awaited<ReturnType<typeof ctx.contextWindowManager.maybeCompact>>;
1055
+ } catch (err) {
1056
+ if (err instanceof PluginTimeoutError) {
1057
+ // Pipeline exceeded its budget. Record the failure so the circuit
1058
+ // breaker tracks consecutive timeouts (it trips after three),
1059
+ // then degrade gracefully by skipping compaction this turn —
1060
+ // the turn proceeds with the un-compacted history rather than
1061
+ // hard-failing. The inner summary call has been aborted by the
1062
+ // runner's signal-linking, so updateSummary's local fallback
1063
+ // also ran before this catch block is reached.
1064
+ rlog.warn(
1065
+ { err, phase: "start-of-turn-compaction" },
1066
+ "Compaction pipeline timed out — skipping compaction this turn",
1067
+ );
1068
+ await ctx.agentLoop.compactionCircuit.recordOutcome(
1069
+ ctx,
1070
+ true,
1071
+ onEvent,
1072
+ );
1073
+ compacted = null;
1074
+ } else {
1075
+ throw err;
1076
+ }
1077
+ }
1030
1078
  }
1031
1079
  // Only track circuit-breaker state when a summary LLM call actually ran.
1032
1080
  // `summaryFailed` is `undefined` on early returns (compaction disabled,
@@ -1035,6 +1083,7 @@ export async function runAgentLoopImpl(
1035
1083
  // the 3-strike counter and break the invariant.
1036
1084
  if (compacted && compacted.summaryFailed !== undefined) {
1037
1085
  await ctx.agentLoop.compactionCircuit.recordOutcome(
1086
+ ctx,
1038
1087
  compacted.summaryFailed,
1039
1088
  onEvent,
1040
1089
  );
@@ -1044,6 +1093,7 @@ export async function runAgentLoopImpl(
1044
1093
  compacted,
1045
1094
  messagesForStartOfTurnCompaction,
1046
1095
  );
1096
+ state.shouldInjectWorkspace = true;
1047
1097
  if (compacted.compactedPersistedMessages > 0) {
1048
1098
  compactedThisTurn = true;
1049
1099
  }
@@ -1301,7 +1351,26 @@ export async function runAgentLoopImpl(
1301
1351
 
1302
1352
  // Shared injection options — reused whenever we need to re-inject after reduction.
1303
1353
  const injectionOpts = {
1354
+ diskPressureContext,
1355
+ // Resolved from the conversation's surface state here, where the
1356
+ // runtime injector is the only consumer of the active-surface block.
1357
+ activeSurface: buildActiveSurfaceContext({
1358
+ currentActiveSurfaceId: ctx.currentActiveSurfaceId,
1359
+ currentPage: ctx.currentPage,
1360
+ surfaceState: ctx.surfaceState,
1361
+ }),
1362
+ // Resolved here, where the runtime injector is the only consumer of the
1363
+ // active-documents block.
1364
+ activeDocuments: buildActiveDocuments(ctx.conversationId),
1365
+ workspaceTopLevelContext: buildWorkspaceTopLevelContext(
1366
+ ctx,
1367
+ state.shouldInjectWorkspace,
1368
+ ),
1369
+ channelCapabilities: ctx.channelCapabilities ?? null,
1370
+ channelCommandContext: ctx.commandIntent ?? null,
1304
1371
  unifiedTurnContext: unifiedTurnContextStr,
1372
+ voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
1373
+ transportHints: ctx.transportHints ?? null,
1305
1374
  isNonInteractive: !isInteractiveResolved,
1306
1375
  isBackgroundConversation: isBackgroundConversationType(
1307
1376
  turnStartConversation?.conversationType,
@@ -1406,12 +1475,16 @@ export async function runAgentLoopImpl(
1406
1475
  "Preflight budget exceeded — running overflow reducer before provider call",
1407
1476
  );
1408
1477
 
1409
- // `runOverflowReductionLoop` drives the tier loop forced compaction
1410
- // tool-result truncation media stubbing → injection downgrade — plus
1411
- // the re-inject/re-estimate convergence check. The callbacks below are
1412
- // the orchestrator-specific side effects it coordinates per iteration
1413
- // (activity emission, compaction application, runtime injection
1414
- // reassembly, token re-estimation).
1478
+ // Overflow reduction runs through the plugin pipeline. The default
1479
+ // middleware (`default-overflow-reduce`, registered at bootstrap)
1480
+ // contains the historical tier loop forced compaction → tool-result
1481
+ // truncation media stubbing injection downgrade — plus the
1482
+ // re-inject/re-estimate convergence check. The callbacks below are
1483
+ // the orchestrator-specific side effects that the plugin coordinates
1484
+ // per iteration (activity emission, compaction application, runtime
1485
+ // injection reassembly, token re-estimation). Registered plugins that
1486
+ // wrap the `overflowReduce` slot see each iteration through their own
1487
+ // middleware `next` callback.
1415
1488
  const messagesForPreflightOverflowReduction =
1416
1489
  slackChronologicalContext?.messages ?? ctx.messages;
1417
1490
  const overflowArgs: OverflowReduceArgs = {
@@ -1425,18 +1498,72 @@ export async function runAgentLoopImpl(
1425
1498
  maxAttempts: resolveCurrentContextBudget().overflowRecovery.maxAttempts,
1426
1499
  abortSignal: abortController.signal,
1427
1500
  compactFn: async (msgs, signal, opts) => {
1428
- // Delegate the reducer's forced-compaction tier to the default
1429
- // compaction plugin, overlaying the turn's resolved inference
1430
- // profile and actor trust class onto the reducer-supplied options.
1431
- const reducerOptions = (opts ?? {}) as ContextWindowCompactOptions;
1432
- return defaultCompact({
1433
- manager: ctx.contextWindowManager,
1434
- messages: msgs,
1435
- signal,
1436
- ...reducerOptions,
1437
- overrideProfile: resolveCurrentOverrideProfile() ?? null,
1438
- actorTrustClass: resolveTurnActorTrustClass(ctx),
1439
- });
1501
+ // Route the reducer's forced-compaction tier through the
1502
+ // `compaction` pipeline so registered plugins observe these
1503
+ // invocations. Without this, custom compaction middleware only
1504
+ // sees the three orchestrator-owned call sites and misses the
1505
+ // reducer-initiated forced compactions entirely.
1506
+ //
1507
+ // Pipeline timeouts must be caught locally — a `PluginTimeoutError`
1508
+ // bubbling out of here would abort the overflow-reducer tier loop
1509
+ // entirely, skipping fallback tiers (tool-result truncation, media
1510
+ // stubbing, injection downgrade) and bypassing circuit-breaker
1511
+ // bookkeeping. On timeout, record the failure and return a
1512
+ // `compacted: false` result so the reducer falls through to the
1513
+ // next tier.
1514
+ try {
1515
+ return (await runPipeline<CompactionArgs, CompactionResult>(
1516
+ "compaction",
1517
+ getMiddlewaresFor("compaction"),
1518
+ (args) =>
1519
+ defaultCompactionTerminal(
1520
+ args,
1521
+ buildPluginTurnContext(ctx, reqId),
1522
+ ),
1523
+ {
1524
+ messages: msgs,
1525
+ signal,
1526
+ options: {
1527
+ ...(opts ?? {}),
1528
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
1529
+ actorTrustClass: resolveTurnActorTrustClass(ctx),
1530
+ },
1531
+ },
1532
+ buildPluginTurnContext(ctx, reqId),
1533
+ DEFAULT_TIMEOUTS.compaction,
1534
+ )) as Awaited<
1535
+ ReturnType<typeof ctx.contextWindowManager.maybeCompact>
1536
+ >;
1537
+ } catch (err) {
1538
+ if (err instanceof PluginTimeoutError) {
1539
+ rlog.warn(
1540
+ { err, phase: "overflow-reducer-forced-compaction" },
1541
+ "Compaction pipeline timed out — falling through to next reducer tier",
1542
+ );
1543
+ await ctx.agentLoop.compactionCircuit.recordOutcome(
1544
+ ctx,
1545
+ true,
1546
+ onEvent,
1547
+ );
1548
+ return {
1549
+ messages: msgs,
1550
+ compacted: false,
1551
+ previousEstimatedInputTokens: 0,
1552
+ estimatedInputTokens: 0,
1553
+ maxInputTokens: 0,
1554
+ thresholdTokens: 0,
1555
+ compactedMessages: 0,
1556
+ compactedPersistedMessages: 0,
1557
+ summaryCalls: 0,
1558
+ summaryInputTokens: 0,
1559
+ summaryOutputTokens: 0,
1560
+ summaryModel: "",
1561
+ summaryText: "",
1562
+ reason: "compaction pipeline timed out",
1563
+ };
1564
+ }
1565
+ throw err;
1566
+ }
1440
1567
  },
1441
1568
  emitActivityState: () => {
1442
1569
  ctx.emitActivityState("thinking", "context_compacting", {
@@ -1455,12 +1582,14 @@ export async function runAgentLoopImpl(
1455
1582
  // breaker.
1456
1583
  if (result.summaryFailed !== undefined) {
1457
1584
  await ctx.agentLoop.compactionCircuit.recordOutcome(
1585
+ ctx,
1458
1586
  result.summaryFailed,
1459
1587
  onEvent,
1460
1588
  );
1461
1589
  }
1462
1590
  if (result.compacted) {
1463
1591
  await applySuccessfulCompaction(result, compactedBasis);
1592
+ state.shouldInjectWorkspace = true;
1464
1593
  }
1465
1594
  },
1466
1595
  reinjectForMode: async (
@@ -1471,10 +1600,9 @@ export async function runAgentLoopImpl(
1471
1600
  ) => {
1472
1601
  // Mirror the pre-PR-23 behavior: `ctx.messages` must track the
1473
1602
  // reducer's latest output before re-injection runs, because other
1474
- // sites consulted through `injectionOpts` (slack history, etc.) and
1475
- // the injectors' own message-presence scans depend on it, and
1476
- // `applyCompactionResult` only updates `ctx.messages` on a
1477
- // compaction tier. Assigning here
1603
+ // sites consulted through `injectionOpts` (`workspaceTopLevelContext`,
1604
+ // slack history, etc.) depend on it and `applyCompactionResult`
1605
+ // only updates `ctx.messages` on a compaction tier. Assigning here
1478
1606
  // keeps non-compaction tiers (tool-result truncation, media
1479
1607
  // stubbing, injection downgrade) observable to downstream
1480
1608
  // injection assembly on the same turn.
@@ -1490,6 +1618,10 @@ export async function runAgentLoopImpl(
1490
1618
  // present in `reducedMessages`.)
1491
1619
  const injection = await applyRuntimeInjections(reducedMessages, {
1492
1620
  ...injectionOpts,
1621
+ workspaceTopLevelContext: buildWorkspaceTopLevelContext(
1622
+ ctx,
1623
+ state.shouldInjectWorkspace,
1624
+ ),
1493
1625
  // Once ANY iteration has compacted `ctx.messages`, the captured
1494
1626
  // `slackChronologicalMessages` snapshot (built from the full
1495
1627
  // persisted transcript) would overwrite the compacted history
@@ -1515,7 +1647,29 @@ export async function runAgentLoopImpl(
1515
1647
  }),
1516
1648
  };
1517
1649
 
1518
- const overflowResult = await runOverflowReductionLoop(overflowArgs);
1650
+ const overflowResult = await runPipeline<
1651
+ OverflowReduceArgs,
1652
+ OverflowReduceResult
1653
+ >(
1654
+ "overflowReduce",
1655
+ getMiddlewaresFor("overflowReduce"),
1656
+ // Terminal — only reached when every registered middleware calls
1657
+ // `next` and delegates past the innermost layer. The default plugin
1658
+ // is a terminal itself (it doesn't call `next`), so in practice
1659
+ // this fallback fires only when the default has been explicitly
1660
+ // deregistered (tests) and no user plugin replaces it. Strict-fail
1661
+ // semantics: throw so the missing terminal surfaces as a visible
1662
+ // error instead of silently returning the history untouched.
1663
+ async () => {
1664
+ throw new PluginExecutionError(
1665
+ "overflowReduce pipeline has no terminal handler — every reducer middleware called next() without providing a replacement",
1666
+ "overflowReduce",
1667
+ );
1668
+ },
1669
+ overflowArgs,
1670
+ buildPluginTurnContext(ctx, reqId),
1671
+ DEFAULT_TIMEOUTS.overflowReduce,
1672
+ );
1519
1673
 
1520
1674
  ctx.messages = overflowResult.messages;
1521
1675
  runMessages = overflowResult.runMessages;
@@ -1618,6 +1772,10 @@ export async function runAgentLoopImpl(
1618
1772
  // presence.
1619
1773
  const injection = await postCompactReinject({
1620
1774
  ...injectionOpts,
1775
+ workspaceTopLevelContext: buildWorkspaceTopLevelContext(
1776
+ ctx,
1777
+ state.shouldInjectWorkspace,
1778
+ ),
1621
1779
  // Suppress the chronological-transcript snapshot once the reducer
1622
1780
  // has collapsed `ctx.messages`; the captured snapshot reflects the
1623
1781
  // full persisted transcript and would overwrite compaction.
@@ -1627,6 +1785,7 @@ export async function runAgentLoopImpl(
1627
1785
  mode: currentInjectionMode,
1628
1786
  turnContext,
1629
1787
  history,
1788
+ isTrustedActor,
1630
1789
  logger: rlog,
1631
1790
  });
1632
1791
  return injection.messages;
@@ -1905,12 +2064,14 @@ export async function runAgentLoopImpl(
1905
2064
  );
1906
2065
  if (emergencyResult.summaryFailed !== undefined) {
1907
2066
  await ctx.agentLoop.compactionCircuit.recordOutcome(
2067
+ ctx,
1908
2068
  emergencyResult.summaryFailed,
1909
2069
  onEvent,
1910
2070
  );
1911
2071
  }
1912
2072
  if (emergencyResult.compacted) {
1913
2073
  await applySuccessfulCompaction(emergencyResult, ctx.messages);
2074
+ state.shouldInjectWorkspace = true;
1914
2075
  }
1915
2076
  // Clear the overflow flag and re-run the agent loop with
1916
2077
  // the compacted context.
@@ -1979,6 +2140,7 @@ export async function runAgentLoopImpl(
1979
2140
  step.compactionResult.summaryFailed !== undefined
1980
2141
  ) {
1981
2142
  await ctx.agentLoop.compactionCircuit.recordOutcome(
2143
+ ctx,
1982
2144
  step.compactionResult.summaryFailed,
1983
2145
  onEvent,
1984
2146
  );
@@ -1989,6 +2151,7 @@ export async function runAgentLoopImpl(
1989
2151
  step.compactionResult,
1990
2152
  convergenceCompactionBasis,
1991
2153
  );
2154
+ state.shouldInjectWorkspace = true;
1992
2155
  state.reducerCompacted = true;
1993
2156
  }
1994
2157
 
@@ -1999,6 +2162,10 @@ export async function runAgentLoopImpl(
1999
2162
  // present in `ctx.messages`.)
2000
2163
  const injection = await applyRuntimeInjections(ctx.messages, {
2001
2164
  ...injectionOpts,
2165
+ workspaceTopLevelContext: buildWorkspaceTopLevelContext(
2166
+ ctx,
2167
+ state.shouldInjectWorkspace,
2168
+ ),
2002
2169
  slackChronologicalMessages: state.reducerCompacted
2003
2170
  ? null
2004
2171
  : injectionOpts.slackChronologicalMessages,
@@ -2061,25 +2228,71 @@ export async function runAgentLoopImpl(
2061
2228
  ctx.emitActivityState("thinking", "context_compacting", {
2062
2229
  requestId: reqId,
2063
2230
  });
2064
- const emergencyCompact = await defaultCompact({
2065
- manager: ctx.contextWindowManager,
2066
- messages: ctx.messages,
2067
- signal: abortController.signal,
2068
- force: true,
2069
- minKeepRecentUserTurns: 0,
2070
- overrideProfile: resolveCurrentOverrideProfile() ?? null,
2071
- });
2231
+ let emergencyCompact: Awaited<
2232
+ ReturnType<typeof ctx.contextWindowManager.maybeCompact>
2233
+ > | null = null;
2234
+ try {
2235
+ emergencyCompact = (await runPipeline<
2236
+ CompactionArgs,
2237
+ CompactionResult
2238
+ >(
2239
+ "compaction",
2240
+ getMiddlewaresFor("compaction"),
2241
+ (args) =>
2242
+ defaultCompactionTerminal(
2243
+ args,
2244
+ buildPluginTurnContext(ctx, reqId),
2245
+ ),
2246
+ {
2247
+ messages: ctx.messages,
2248
+ signal: abortController.signal,
2249
+ options: {
2250
+ force: true,
2251
+ minKeepRecentUserTurns: 0,
2252
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
2253
+ },
2254
+ },
2255
+ buildPluginTurnContext(ctx, reqId),
2256
+ DEFAULT_TIMEOUTS.compaction,
2257
+ )) as Awaited<
2258
+ ReturnType<typeof ctx.contextWindowManager.maybeCompact>
2259
+ >;
2260
+ } catch (err) {
2261
+ if (err instanceof PluginTimeoutError) {
2262
+ // Emergency compaction timed out. Record the circuit-breaker
2263
+ // failure and fall through to the graceful-error path below
2264
+ // (the unsuccessful-compaction fallback) rather than hard-
2265
+ // failing the turn.
2266
+ rlog.warn(
2267
+ { err, phase: "emergency-compaction" },
2268
+ "Emergency compaction pipeline timed out — continuing with overflow fallback",
2269
+ );
2270
+ await ctx.agentLoop.compactionCircuit.recordOutcome(
2271
+ ctx,
2272
+ true,
2273
+ onEvent,
2274
+ );
2275
+ emergencyCompact = null;
2276
+ } else {
2277
+ throw err;
2278
+ }
2279
+ }
2072
2280
  // Only track when the summary LLM actually ran; `force: true`
2073
2281
  // bypasses the auto-threshold gate but not the early-return paths.
2074
- if (emergencyCompact.summaryFailed !== undefined) {
2282
+ if (
2283
+ emergencyCompact &&
2284
+ emergencyCompact.summaryFailed !== undefined
2285
+ ) {
2075
2286
  await ctx.agentLoop.compactionCircuit.recordOutcome(
2287
+ ctx,
2076
2288
  emergencyCompact.summaryFailed,
2077
2289
  onEvent,
2078
2290
  );
2079
2291
  }
2080
- if (emergencyCompact.compacted) {
2292
+ if (emergencyCompact?.compacted) {
2081
2293
  await applySuccessfulCompaction(emergencyCompact, ctx.messages);
2082
2294
  state.reducerCompacted = true;
2295
+ state.shouldInjectWorkspace = true;
2083
2296
  }
2084
2297
 
2085
2298
  // Only re-inject the memory-static block when ctx.messages was
@@ -2089,6 +2302,10 @@ export async function runAgentLoopImpl(
2089
2302
  // present in `ctx.messages`.)
2090
2303
  const injection = await applyRuntimeInjections(ctx.messages, {
2091
2304
  ...injectionOpts,
2305
+ workspaceTopLevelContext: buildWorkspaceTopLevelContext(
2306
+ ctx,
2307
+ state.shouldInjectWorkspace,
2308
+ ),
2092
2309
  slackChronologicalMessages: state.reducerCompacted
2093
2310
  ? null
2094
2311
  : injectionOpts.slackChronologicalMessages,