@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
@@ -1,23 +1,39 @@
1
1
  /**
2
- * User plugin loader — discovers plugins under `<workspaceDir>/plugins/*` and
3
- * registers each one.
2
+ * User plugin loader — discovers plugins under `<workspaceDir>/plugins/*` via
3
+ * one of two paths, gated by the contents of each candidate directory.
4
4
  *
5
- * A plugin directory is recognized by a `package.json` manifest. The harness
6
- * delegates to {@link loadExternalPlugin}, which builds a `Plugin` from the
7
- * directory's interface dirs (`hooks/`, `tools/`) and registers it directly.
8
- * The full convention lives in
9
- * `assistant/src/plugins/external-plugin-loader.ts`. A directory with no
10
- * `package.json` is skipped silently.
5
+ * **External plugin framework path** (`package.json` present **and** no
6
+ * `register.{ts,js}`): the harness delegates to {@link loadExternalPlugin},
7
+ * which builds a `Plugin` from the directory's interface dirs (`hooks/`,
8
+ * `tools/`) and registers it directly. This path is opt-in by the plugin
9
+ * author and currently experimental — see
10
+ * `assistant/src/plugins/external-plugin-loader.ts` for the full
11
+ * convention.
12
+ *
13
+ * **Legacy path** (`register.{ts,js}` present): the file is dynamic-imported
14
+ * and expected to call {@link registerPlugin} at import time as a side
15
+ * effect, populating the registry before {@link bootstrapPlugins} runs.
16
+ *
17
+ * The legacy path takes precedence when a directory contains both
18
+ * `package.json` and `register.{ts,js}` — a migration-friendly default
19
+ * that keeps existing plugins (including the in-repo `examples/plugins/echo`
20
+ * reference) working unchanged while we iterate the external-plugin
21
+ * convention. A directory matching neither path is skipped silently.
11
22
  *
12
23
  * The loader deliberately:
13
24
  *
14
25
  * - Uses `getWorkspaceDir()` so each instance loads its own plugin set
15
26
  * when `VELLUM_WORKSPACE_DIR` is set.
16
- * - Prefers `.js` over `.ts` per surface file (compiled-binary semantics);
17
- * the rule is applied by {@link loadExternalPlugin}.
27
+ * - Prefers `.js` over `.ts` per surface file (compiled-binary semantics).
28
+ * The external loader applies the same rule per surface file; the
29
+ * legacy path picks between `register.js` and `register.ts`.
18
30
  * - Treats any error from a plugin load as a per-plugin isolation
19
- * boundary. {@link loadExternalPlugin} owns its own try/catch/timeout, so
20
- * one bad user plugin must not crash the daemon.
31
+ * boundary. {@link loadExternalPlugin} owns its own try/catch/timeout;
32
+ * the legacy path is wrapped here. One bad user plugin must not crash
33
+ * the daemon.
34
+ * - Bounds each plugin load with {@link USER_PLUGIN_IMPORT_TIMEOUT_MS}
35
+ * so a plugin whose top-level `await` hangs or whose module evaluation
36
+ * never resolves cannot stall daemon startup.
21
37
  *
22
38
  * Call order relative to the rest of the plugin system:
23
39
  *
@@ -30,6 +46,7 @@
30
46
 
31
47
  import { existsSync, readdirSync, statSync } from "node:fs";
32
48
  import { join } from "node:path";
49
+ import { pathToFileURL } from "node:url";
33
50
 
34
51
  import { getLogger } from "../util/logger.js";
35
52
  import { getWorkspacePluginsDir } from "../util/platform.js";
@@ -40,24 +57,27 @@ import { closeRegistration } from "./registry.js";
40
57
  const log = getLogger("user-plugin-loader");
41
58
 
42
59
  /**
43
- * Upper bound on how long a single user plugin's load may take. A plugin with
44
- * a hanging top-level `await` (or a never-resolving module evaluation) would
45
- * otherwise block daemon startup indefinitely. Ten seconds is generous
46
- * relative to a typical plugin load (milliseconds) and matches the per-plugin
47
- * isolation contract: slow plugins get skipped the same way thrown-error
48
- * plugins do. Enforced by {@link loadExternalPlugin}.
60
+ * Upper bound on how long a single user plugin's dynamic `import()` may take.
61
+ * A plugin with a hanging top-level `await` (or a never-resolving module
62
+ * evaluation) would otherwise block daemon startup indefinitely, since a raw
63
+ * `try/catch` only isolates thrown errors not hung promises. Ten seconds is
64
+ * generous relative to a typical side-effect registration (milliseconds) and
65
+ * matches the per-plugin isolation contract: slow plugins get skipped the
66
+ * same way thrown-error plugins do.
49
67
  */
50
68
  const USER_PLUGIN_IMPORT_TIMEOUT_MS = 10_000;
51
69
 
52
70
  /**
53
- * Scan `getWorkspaceDir()/plugins/` for subdirectories and dispatch each one
54
- * that carries a `package.json` to {@link loadExternalPlugin}.
71
+ * Scan `getWorkspaceDir()/plugins/` for subdirectories, then dispatch each
72
+ * one to the external loader (if `package.json` is present and there is no
73
+ * `register.{ts,js}`) or the legacy side-effect importer (if
74
+ * `register.{ts,js}` is present).
55
75
  *
56
76
  * Invariants:
57
77
  *
58
78
  * - No-ops when `getWorkspaceDir()/plugins/` does not exist — a clean install with
59
79
  * zero user plugins must not generate errors.
60
- * - Per-plugin isolation: a failing load is logged and skipped. The
80
+ * - Per-plugin isolation: a failing import is logged and skipped. The
61
81
  * function resolves normally even when every plugin fails to load.
62
82
  * - Does not return plugin instances. The registry is the single source of
63
83
  * truth for who got registered, and the caller inspects it directly.
@@ -66,7 +86,7 @@ const USER_PLUGIN_IMPORT_TIMEOUT_MS = 10_000;
66
86
  *
67
87
  * - Must be invoked exactly once during daemon startup, before
68
88
  * `bootstrapPlugins()` walks the registry.
69
- * - Holds no locks during the load — bun's dynamic `import()` resolution
89
+ * - Holds no locks during the import — bun's dynamic `import()` resolution
70
90
  * is concurrency-safe.
71
91
  */
72
92
  export async function loadUserPlugins(
@@ -131,19 +151,96 @@ export async function loadUserPlugins(
131
151
  }
132
152
  if (!stats.isDirectory()) continue;
133
153
 
134
- if (!existsSync(join(pluginDir, "package.json"))) {
135
- log.debug({ pluginDir }, "loadUserPlugins: no package.json skipping");
154
+ // Path selection: the legacy side-effect path takes precedence when
155
+ // both a `register.{ts,js}` and a `package.json` are present.
156
+ // Migration-friendly: any plugin in the wild today that happens to
157
+ // ship a `package.json` keeps loading via its existing register entry.
158
+ // The external-plugin path only fires when the directory is
159
+ // unambiguously the new convention.
160
+ const jsPath = join(pluginDir, "register.js");
161
+ const tsPath = join(pluginDir, "register.ts");
162
+ let registerPath: string | undefined;
163
+ if (existsSync(jsPath)) {
164
+ registerPath = jsPath;
165
+ } else if (existsSync(tsPath)) {
166
+ registerPath = tsPath;
167
+ }
168
+
169
+ if (registerPath === undefined) {
170
+ // External plugin framework path. `loadExternalPlugin` owns its own
171
+ // try/catch + timeout, so a `continue` is the entire branch here.
172
+ if (existsSync(join(pluginDir, "package.json"))) {
173
+ await loadExternalPlugin(pluginDir, { importTimeoutMs });
174
+ continue;
175
+ }
176
+ log.debug(
177
+ { pluginDir },
178
+ "loadUserPlugins: no register.{ts,js} or package.json — skipping",
179
+ );
136
180
  continue;
137
181
  }
138
182
 
139
- // `loadExternalPlugin` owns its own try/catch + timeout, so a bare
140
- // `await` is the entire branch here.
141
- await loadExternalPlugin(pluginDir, { importTimeoutMs });
183
+ // Legacy side-effect import path. `import()` with a `file://` URL
184
+ // works identically under Node and bun and sidesteps platform-specific
185
+ // absolute-path quirks on Windows.
186
+ const moduleUrl = pathToFileURL(registerPath).href;
187
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
188
+ try {
189
+ // Race the import against a timeout so a plugin with a hanging top-level
190
+ // await or never-resolving module evaluation cannot stall daemon startup.
191
+ // The per-plugin try/catch already handles thrown errors; this extends
192
+ // the isolation boundary to cover hung promises as well.
193
+ const timeoutSentinel = Symbol("user-plugin-import-timeout");
194
+ const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
195
+ timeoutHandle = setTimeout(
196
+ () => resolve(timeoutSentinel),
197
+ importTimeoutMs,
198
+ );
199
+ });
200
+ // Retain the import promise so we can attach a terminal `.catch` on the
201
+ // timeout branch. `Promise.race` does not cancel the losing promise —
202
+ // the module evaluation keeps running in the background even after we
203
+ // stop awaiting it, and if it eventually throws (either from the
204
+ // module body or from the late `registerPlugin()` hitting a closed
205
+ // registry) an unhandled rejection would crash the daemon.
206
+ const importPromise = import(moduleUrl);
207
+ const result = await Promise.race([importPromise, timeoutPromise]);
208
+ if (result === timeoutSentinel) {
209
+ importPromise.catch(() => {
210
+ // Abandoned import completed (or threw) after the timeout. The
211
+ // closed-registration latch in registry.ts guarantees any late
212
+ // `registerPlugin()` call is rejected, so swallowing the outcome
213
+ // here is the safe default.
214
+ });
215
+ log.warn(
216
+ { pluginDir, registerPath, timeoutMs: importTimeoutMs },
217
+ `Timed out loading user plugin ${pluginDir} after ${importTimeoutMs}ms — skipping`,
218
+ );
219
+ } else {
220
+ log.info(
221
+ { pluginDir, registerPath },
222
+ "loaded user plugin (side-effect import completed)",
223
+ );
224
+ }
225
+ } catch (err) {
226
+ // One plugin's failure must never prevent other plugins from loading
227
+ // or crash the daemon. Log with the directory name so operators can
228
+ // find the broken plugin quickly.
229
+ const message = err instanceof Error ? err.message : String(err);
230
+ log.error(
231
+ { err, pluginDir },
232
+ `Failed to load user plugin ${pluginDir}: ${message}`,
233
+ );
234
+ } finally {
235
+ if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
236
+ }
142
237
  }
143
238
 
144
239
  // Close the registration window once every candidate plugin has been
145
- // awaited (or timed out). The per-plugin try/catch inside
146
- // `loadExternalPlugin` guarantees no throw escapes the loop, so this line
147
- // always runs and `bootstrapPlugins()` sees a fully populated registry.
240
+ // awaited (or timed out). The per-plugin try/catch guarantees no throw
241
+ // escapes the loop, so this line always runs. Any abandoned import that
242
+ // later resolves and reaches `registerPlugin()` is rejected by the latch,
243
+ // preserving the `bootstrapPlugins()` invariant that the registry is
244
+ // fully populated before it is walked.
148
245
  closeRegistration();
149
246
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { createAssistantMessage } from "../agent/message-types.js";
10
- import { findConversation } from "../daemon/conversation-registry.js";
10
+ import { findConversation } from "../daemon/conversation-store.js";
11
11
  import {
12
12
  conversationMessagesSyncTag,
13
13
  SYNC_TAGS,
@@ -196,7 +196,7 @@ type MockConversation = {
196
196
  };
197
197
  let mockConversations: Map<string, MockConversation> = new Map();
198
198
 
199
- mock.module("../daemon/conversation-registry.js", () => ({
199
+ mock.module("../daemon/conversation-store.js", () => ({
200
200
  findConversation: (id: string) => mockConversations.get(id),
201
201
  }));
202
202
 
@@ -148,11 +148,5 @@ describe("maybeReseedBootstrap — activation rail template", () => {
148
148
  // Propose: the extract-shape vs infer-shape example block.
149
149
  expect(content).toContain("extract-shape");
150
150
  expect(content).toContain("infer-shape");
151
-
152
- // Port: prompt-writing guidance (JARVIS-1124).
153
- expect(content).toContain("portable context brief, not a self-summary");
154
- expect(content).toContain("load-bearing work in the next month");
155
- expect(content).toContain("what to help with first");
156
- expect(content).toContain("another tool or collaborator");
157
151
  });
158
152
  });
@@ -8,11 +8,9 @@ The user just finished pre-chat. You know their name and vibe; maybe their Googl
8
8
 
9
9
  Four moves. Goals, not steps.
10
10
 
11
- **Port.** Pull their existing assistant context with two pastes. About a minute, no upload, no export. You write a prompt, they paste it into Claude or ChatGPT, they paste the response back. Cheap signal, real signal.
11
+ **Port.** Pull their existing assistant context with two pastes about a minute, no upload, no export. You write a prompt, they paste it into Claude or ChatGPT, they paste the response back. Cheap signal, real signal.
12
12
 
13
- The prompt asks for a portable context brief, not a self-summary. Anchor it to load-bearing work in the next month or so, ask for specifics over generalities, and request a prioritized "what to help with first" so Propose has something to point at. Frame the destination as another tool or collaborator. Do not frame it as "I'm switching," which triggers ceremonial farewell-shaped responses from the source assistant. Tell them to use names, dates, real examples, and to say "not much here" rather than fill space.
14
-
15
- The prompt itself must be one-click copyable. Inline paragraph text the user has to select isn't. Neither is a custom-built widget with a fake copy button. If the affordance needs you to build an app or a new surface to render, you've over-built the move. Use what chat already gives you.
13
+ The prompt should be one-click copyable. Inline paragraph text the user has to select isn't. Neither is a custom-built widget with a fake copy button. If the affordance needs you to build an app or a new surface to render, you've over-built the move. Use what chat already gives you.
16
14
 
17
15
  **Propose.** Don't organize what they already told you — infer what they didn't. Name the unstated thing sitting in their context and say *why* you think it: point at the specific surface that made you say it. "You didn't say this, but —". Then recommend, and lean one way; the recommendation IS the click, not a neutral menu of equally-weighted options.
18
16
 
@@ -432,7 +432,7 @@ describe("wakeAgentForOpportunity", () => {
432
432
  expect(target.runCalls).toHaveLength(0);
433
433
  });
434
434
 
435
- test("builds a guardian turn context for explicit local-owner cleanup-mode wakes", async () => {
435
+ test("threads cleanup-mode injection context for explicit local-owner wakes", async () => {
436
436
  mockDiskPressureStatus = {
437
437
  enabled: true,
438
438
  state: "critical",
@@ -463,11 +463,11 @@ describe("wakeAgentForOpportunity", () => {
463
463
 
464
464
  expect(result).toEqual({ invoked: true, producedToolCalls: false });
465
465
  expect(target.runCalls).toHaveLength(1);
466
- expect(target.runCalls[0]!.turnContext).toEqual({
467
- requestId: "wake:local-cleanup",
466
+ expect(target.runCalls[0]!.turnContext).toMatchObject({
468
467
  conversationId: target.conversationId,
469
- turnIndex: 0,
470
- trust: { sourceChannel: "vellum", trustClass: "guardian" },
468
+ injectionInputs: {
469
+ diskPressureContext: { cleanupModeActive: true },
470
+ },
471
471
  });
472
472
  });
473
473
 
@@ -50,7 +50,7 @@ let surfaceCalls: Array<{
50
50
  surfaceId: string;
51
51
  }> = [];
52
52
 
53
- mock.module("../../daemon/conversation-registry.js", () => ({
53
+ mock.module("../../daemon/conversation-store.js", () => ({
54
54
  findConversation: (_conversationId: string) => {
55
55
  return mockConversation ?? undefined;
56
56
  },
@@ -432,6 +432,9 @@ function buildWakeTurnContext(
432
432
  sourceChannel: opts.sourceChannel ?? "vellum",
433
433
  trustClass: "guardian",
434
434
  } satisfies TrustContext),
435
+ injectionInputs: {
436
+ diskPressureContext: { cleanupModeActive: true },
437
+ },
435
438
  };
436
439
  }
437
440
 
@@ -708,7 +708,7 @@ async function createCanonicalRequestForConfirmation(
708
708
  { DAEMON_INTERNAL_ASSISTANT_ID },
709
709
  { bridgeConfirmationRequestToGuardian },
710
710
  ] = await Promise.all([
711
- import("../daemon/conversation-registry.js"),
711
+ import("../daemon/conversation-store.js"),
712
712
  import("../memory/canonical-guardian-store.js"),
713
713
  import("../security/secret-scanner.js"),
714
714
  import("../tools/tool-input-summary.js"),
@@ -9,7 +9,7 @@
9
9
  * 3. Consume user decisions and apply them to the underlying session
10
10
  */
11
11
 
12
- import { findConversation } from "../daemon/conversation-registry.js";
12
+ import { findConversation } from "../daemon/conversation-store.js";
13
13
  import type { UserDecision } from "../permissions/types.js";
14
14
  import { composeApprovalMessage } from "./approval-message-composer.js";
15
15
  import type {
@@ -30,7 +30,7 @@
30
30
  * `"timed_out"`.
31
31
  */
32
32
 
33
- import { findConversation } from "../daemon/conversation-registry.js";
33
+ import { findConversation } from "../daemon/conversation-store.js";
34
34
  import { showStandaloneSurface } from "../daemon/conversation-surfaces.js";
35
35
  import { getLogger } from "../util/logger.js";
36
36
  import { mintDecisionToken } from "./decision-token.js";