@vellumai/assistant 0.10.3 → 0.10.4-staging.1

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 (239) hide show
  1. package/openapi.yaml +73 -56
  2. package/package.json +1 -1
  3. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  4. package/src/__tests__/assistant-stream-state.test.ts +3 -76
  5. package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
  6. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  7. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  8. package/src/__tests__/channel-guardian.test.ts +82 -32
  9. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  10. package/src/__tests__/channel-reply-delivery.test.ts +6 -2
  11. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  12. package/src/__tests__/config-loader-backfill.test.ts +148 -0
  13. package/src/__tests__/consult-deadline.test.ts +60 -0
  14. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  15. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  16. package/src/__tests__/contacts-relay-reads.test.ts +6 -9
  17. package/src/__tests__/contacts-write.test.ts +0 -2
  18. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
  19. package/src/__tests__/conversation-agent-loop.test.ts +98 -7
  20. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  21. package/src/__tests__/conversation-error.test.ts +18 -0
  22. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  23. package/src/__tests__/conversation-title-service.test.ts +222 -201
  24. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  25. package/src/__tests__/delete-propagation.test.ts +5 -3
  26. package/src/__tests__/dm-backfill.test.ts +6 -4
  27. package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
  28. package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
  29. package/src/__tests__/guardian-dispatch.test.ts +50 -5
  30. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  31. package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
  32. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  33. package/src/__tests__/helpers/mock-logger.ts +1 -0
  34. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  35. package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
  36. package/src/__tests__/invite-redemption-service.test.ts +273 -53
  37. package/src/__tests__/invite-routes-http.test.ts +34 -0
  38. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  39. package/src/__tests__/list-messages-page-latest.test.ts +173 -4
  40. package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
  41. package/src/__tests__/non-member-access-request.test.ts +15 -13
  42. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  43. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  44. package/src/__tests__/persona-resolver.test.ts +75 -45
  45. package/src/__tests__/plugin-bootstrap.test.ts +13 -5
  46. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  47. package/src/__tests__/provider-usage-tracking.test.ts +1 -1
  48. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  49. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  50. package/src/__tests__/reaction-persistence.test.ts +51 -4
  51. package/src/__tests__/relay-server.test.ts +88 -31
  52. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
  53. package/src/__tests__/settings-routes.test.ts +32 -0
  54. package/src/__tests__/slack-block-formatting.test.ts +1 -38
  55. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
  56. package/src/__tests__/stt-hints.test.ts +6 -3
  57. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  58. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  59. package/src/__tests__/subagent-role-registry.test.ts +17 -4
  60. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  61. package/src/__tests__/subagent-tools.test.ts +398 -3
  62. package/src/__tests__/thread-backfill.test.ts +3 -3
  63. package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
  64. package/src/__tests__/tool-start-timestamp.test.ts +4 -3
  65. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  66. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
  67. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  68. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  69. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  70. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  71. package/src/__tests__/voice-invite-redemption.test.ts +183 -20
  72. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  73. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
  74. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  75. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  76. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  77. package/src/agent/loop-exclusive-tool.test.ts +19 -15
  78. package/src/agent/loop-native-web-search.test.ts +200 -0
  79. package/src/agent/loop.ts +108 -1
  80. package/src/api/responses/conversation-message.ts +9 -0
  81. package/src/approvals/guardian-request-resolvers.ts +16 -4
  82. package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
  83. package/src/calls/guardian-dispatch.ts +14 -11
  84. package/src/calls/inbound-trust-reader.ts +7 -1
  85. package/src/calls/relay-access-wait.ts +6 -6
  86. package/src/calls/relay-server.ts +22 -2
  87. package/src/calls/relay-setup-router.ts +10 -10
  88. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  89. package/src/cli/commands/contacts.ts +10 -7
  90. package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
  91. package/src/cli/commands/memory/worker.ts +97 -30
  92. package/src/cli/commands/plugins.ts +3 -146
  93. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
  94. package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
  95. package/src/cli/lib/publish-plugin.ts +231 -1
  96. package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
  97. package/src/config/bundled-skills/subagent/SKILL.md +16 -1
  98. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  99. package/src/config/call-site-defaults.ts +0 -6
  100. package/src/config/llm-resolver.ts +0 -3
  101. package/src/config/schemas/call-site-catalog.ts +0 -7
  102. package/src/config/schemas/heartbeat.ts +2 -5
  103. package/src/config/schemas/llm.ts +3 -12
  104. package/src/config/schemas/memory-lifecycle.ts +1 -1
  105. package/src/config/seed-inference-profiles.ts +76 -35
  106. package/src/config/sync-gated-profiles.ts +0 -3
  107. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
  108. package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
  109. package/src/contacts/contact-store.ts +27 -237
  110. package/src/contacts/contacts-write.ts +18 -58
  111. package/src/contacts/gateway-channel-read.ts +51 -0
  112. package/src/contacts/member-write-relay.ts +25 -31
  113. package/src/contacts/types.ts +3 -15
  114. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  115. package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
  116. package/src/daemon/conversation-agent-loop.ts +68 -61
  117. package/src/daemon/conversation-error.ts +7 -10
  118. package/src/daemon/conversation-tool-setup.ts +0 -10
  119. package/src/daemon/conversation.ts +10 -0
  120. package/src/daemon/external-plugins-bootstrap.ts +8 -2
  121. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  122. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  123. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
  124. package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
  125. package/src/daemon/handlers/config-channels.ts +14 -29
  126. package/src/daemon/lifecycle.ts +16 -4
  127. package/src/daemon/message-types/surfaces.ts +2 -0
  128. package/src/heartbeat/heartbeat-service.ts +5 -0
  129. package/src/home/relationship-state-writer.ts +5 -0
  130. package/src/memory/__tests__/embedding-cache.test.ts +136 -0
  131. package/src/memory/compaction-ledger-store.ts +107 -0
  132. package/src/memory/conversation-crud.ts +136 -61
  133. package/src/memory/conversation-title-service.ts +173 -24
  134. package/src/memory/embedding-backend.ts +8 -1
  135. package/src/memory/embedding-cache.ts +139 -0
  136. package/src/memory/jobs-worker.ts +75 -29
  137. package/src/memory/memory-retrospective-job.ts +5 -0
  138. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
  139. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  140. package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
  141. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
  142. package/src/memory/schema/contacts.ts +6 -2
  143. package/src/memory/schema/conversations.ts +39 -0
  144. package/src/memory/steps.ts +1090 -367
  145. package/src/memory/worker-control.ts +104 -18
  146. package/src/memory/worker-process.ts +17 -0
  147. package/src/messaging/channel-binding-metadata.ts +31 -0
  148. package/src/messaging/channel-binding-schema.ts +51 -0
  149. package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
  150. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
  151. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
  152. package/src/messaging/providers/a2a/deliver.ts +5 -1
  153. package/src/messaging/providers/a2a/transport.ts +10 -0
  154. package/src/messaging/providers/callback-routing.ts +48 -0
  155. package/src/messaging/providers/channel-transport.ts +55 -0
  156. package/src/messaging/providers/index.ts +65 -241
  157. package/src/messaging/providers/slack/binding-metadata.ts +62 -0
  158. package/src/messaging/providers/slack/transport.ts +92 -0
  159. package/src/messaging/providers/telegram-bot/transport.ts +51 -0
  160. package/src/messaging/providers/whatsapp/transport.ts +38 -0
  161. package/src/notifications/__tests__/broadcaster.test.ts +0 -8
  162. package/src/notifications/__tests__/connected-channels.test.ts +8 -36
  163. package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
  164. package/src/notifications/destination-resolver.ts +7 -23
  165. package/src/notifications/emit-signal.ts +5 -11
  166. package/src/plugins/defaults/index.ts +0 -35
  167. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
  168. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
  169. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
  170. package/src/plugins/disabled-state.ts +31 -0
  171. package/src/plugins/registry.ts +55 -12
  172. package/src/prompts/persona-resolver.ts +43 -11
  173. package/src/providers/call-site-routing.ts +41 -0
  174. package/src/providers/provider-send-message.ts +6 -0
  175. package/src/providers/ratelimit.ts +6 -0
  176. package/src/providers/registry.ts +1 -1
  177. package/src/providers/retry.ts +6 -0
  178. package/src/providers/types.ts +13 -0
  179. package/src/providers/usage-tracking.ts +6 -0
  180. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
  181. package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
  182. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  183. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
  184. package/src/runtime/access-request-helper.ts +1 -2
  185. package/src/runtime/actor-trust-resolver.ts +44 -17
  186. package/src/runtime/anchored-guardian.test.ts +7 -54
  187. package/src/runtime/anchored-guardian.ts +4 -53
  188. package/src/runtime/assistant-stream-state.ts +12 -74
  189. package/src/runtime/channel-reply-delivery.ts +3 -8
  190. package/src/runtime/guardian-vellum-migration.ts +18 -16
  191. package/src/runtime/invite-redemption-service.ts +25 -10
  192. package/src/runtime/local-actor-identity.test.ts +108 -0
  193. package/src/runtime/local-actor-identity.ts +27 -20
  194. package/src/runtime/member-verdict-cache.ts +0 -0
  195. package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
  196. package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
  197. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
  198. package/src/runtime/routes/contact-routes.ts +40 -25
  199. package/src/runtime/routes/conversation-list-routes.ts +1 -29
  200. package/src/runtime/routes/conversation-routes.ts +27 -7
  201. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
  202. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
  203. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  204. package/src/runtime/routes/settings-routes.ts +8 -3
  205. package/src/runtime/services/conversation-serializer.ts +6 -49
  206. package/src/runtime/slack-block-formatting.ts +0 -15
  207. package/src/runtime/trust-verdict-consumer.ts +36 -41
  208. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  209. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  210. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
  211. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  212. package/src/subagent/index.ts +1 -1
  213. package/src/subagent/manager.ts +245 -33
  214. package/src/subagent/types.ts +8 -1
  215. package/src/tools/registry.ts +10 -3
  216. package/src/tools/subagent/consult-deadline.ts +49 -0
  217. package/src/tools/subagent/spawn.ts +234 -5
  218. package/src/util/logger.ts +9 -0
  219. package/src/util/platform.ts +14 -0
  220. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  221. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  222. package/src/workspace/migrations/registry.ts +2 -0
  223. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  224. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  225. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  226. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
  227. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
  228. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
  229. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  230. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  231. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  232. package/src/plugins/defaults/advisor/config.ts +0 -21
  233. package/src/plugins/defaults/advisor/consult.ts +0 -197
  234. package/src/plugins/defaults/advisor/context-pack.ts +0 -288
  235. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  236. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  237. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  238. package/src/plugins/defaults/advisor/package.json +0 -14
  239. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
@@ -7,18 +7,31 @@
7
7
  *
8
8
  * Subcommands:
9
9
  *
10
- * - `start` — spawn the worker process (detached, background).
11
- * - `stop` — send SIGTERM to the running worker process.
12
- * - `status` report whether the worker process is running.
10
+ * - `start` — spawn the worker process and enable `memory.worker.enabled`,
11
+ * standing the daemon's synchronous in-process runner down.
12
+ * - `stop` SIGTERM the worker process and disable `memory.worker.enabled`,
13
+ * handing the queue back to the synchronous in-process runner.
14
+ * - `status` — report the worker process state, the `memory.worker.enabled`
15
+ * config value, and whether the synchronous in-process runner is going.
13
16
  *
14
17
  * All three run directly in the CLI process (transport: "local") — no IPC
15
- * round-trip to the daemon.
18
+ * round-trip to the daemon. The daemon's worker supervisor re-reads
19
+ * `memory.worker.enabled` each poll and stands its synchronous runner down (or
20
+ * resumes it) accordingly, so flipping the flag here switches the running
21
+ * daemon's mode without a restart.
16
22
  */
17
23
 
18
24
  import type { Command } from "commander";
19
25
 
26
+ import {
27
+ getConfigReadOnly,
28
+ loadRawConfig,
29
+ saveRawConfig,
30
+ setNestedValue,
31
+ } from "../../../config/loader.js";
20
32
  import {
21
33
  probeMemoryWorker,
34
+ probeSyncRunner,
22
35
  spawnMemoryWorkerProcess,
23
36
  } from "../../../memory/worker-control.js";
24
37
  import { getMemoryWorkerPidPath } from "../../../util/platform.js";
@@ -26,6 +39,17 @@ import { registerCommand } from "../../lib/register-command.js";
26
39
  import { log } from "../../logger.js";
27
40
  import { shouldOutputJson, writeOutput } from "../../output.js";
28
41
 
42
+ /**
43
+ * Persist `memory.worker.enabled` to the on-disk config via the shared
44
+ * raw-config helpers, so only this leaf changes (schema defaults are not baked
45
+ * into the file). The assistant picks the change up on its next config read.
46
+ */
47
+ function setWorkerEnabled(enabled: boolean): void {
48
+ const raw = loadRawConfig();
49
+ setNestedValue(raw, "memory.worker.enabled", enabled);
50
+ saveRawConfig(raw);
51
+ }
52
+
29
53
  // ---------------------------------------------------------------------------
30
54
  // `start`
31
55
  // ---------------------------------------------------------------------------
@@ -38,6 +62,9 @@ async function startWorker(
38
62
  try {
39
63
  result = await spawnMemoryWorkerProcess();
40
64
  } catch (err) {
65
+ // Spawn failed — leave `memory.worker.enabled` untouched so the daemon's
66
+ // synchronous runner keeps draining the queue rather than standing down
67
+ // for a worker that never came up.
41
68
  const msg = err instanceof Error ? err.message : String(err);
42
69
  if (shouldOutputJson(cmd)) {
43
70
  writeOutput(cmd, { ok: false, error: msg });
@@ -48,25 +75,28 @@ async function startWorker(
48
75
  return;
49
76
  }
50
77
 
51
- if (result.alreadyRunning) {
52
- const msg = `Memory worker is already running (PID ${result.pid})`;
53
- if (shouldOutputJson(cmd)) {
54
- writeOutput(cmd, { ok: false, error: msg, pid: result.pid });
55
- } else {
56
- log.error(msg);
57
- }
58
- process.exitCode = 1;
59
- return;
60
- }
78
+ // The worker process is up (freshly spawned or already running). Enable the
79
+ // flag so the daemon's supervisor stands its synchronous runner down (and so
80
+ // the daemon spawns the worker again on the next restart).
81
+ setWorkerEnabled(true);
61
82
 
62
83
  if (shouldOutputJson(cmd)) {
63
84
  writeOutput(cmd, {
64
85
  ok: true,
65
86
  pid: result.pid,
87
+ alreadyRunning: result.alreadyRunning,
66
88
  pidPath: getMemoryWorkerPidPath(),
89
+ workerEnabled: true,
67
90
  });
68
91
  } else {
69
- log.info(`Memory worker started (PID ${result.pid})`);
92
+ log.info(
93
+ result.alreadyRunning
94
+ ? `Memory worker is already running (PID ${result.pid})`
95
+ : `Memory worker started (PID ${result.pid})`,
96
+ );
97
+ log.info(
98
+ "Enabled memory.worker.enabled; the synchronous in-process runner will stand down",
99
+ );
70
100
  }
71
101
  }
72
102
 
@@ -75,15 +105,28 @@ async function startWorker(
75
105
  // ---------------------------------------------------------------------------
76
106
 
77
107
  function stopWorker(opts: { json?: boolean }, cmd: Command): void {
108
+ // Persist the preference first: `stop` means "hand the queue back to the
109
+ // synchronous in-process runner." Disabling the flag makes the daemon's
110
+ // supervisor resume processing in-process on its next poll and lines the next
111
+ // daemon restart up with synchronous mode; the SIGTERM below then stops the
112
+ // now-redundant worker process.
113
+ setWorkerEnabled(false);
114
+
78
115
  const current = probeMemoryWorker();
79
116
  if (current.status !== "running" || current.pid == null) {
80
- const msg = "Memory worker is not running";
117
+ // No worker process to signal flipping the flag alone restores
118
+ // synchronous mode, so this is success, not an error.
81
119
  if (shouldOutputJson(cmd)) {
82
- writeOutput(cmd, { ok: false, error: msg });
120
+ writeOutput(cmd, {
121
+ ok: true,
122
+ workerWasRunning: false,
123
+ workerEnabled: false,
124
+ });
83
125
  } else {
84
- log.error(msg);
126
+ log.info(
127
+ "Memory worker process was not running; disabled memory.worker.enabled (synchronous runner active)",
128
+ );
85
129
  }
86
- process.exitCode = 1;
87
130
  return;
88
131
  }
89
132
 
@@ -93,7 +136,7 @@ function stopWorker(opts: { json?: boolean }, cmd: Command): void {
93
136
  } catch (err) {
94
137
  const msg = err instanceof Error ? err.message : String(err);
95
138
  if (shouldOutputJson(cmd)) {
96
- writeOutput(cmd, { ok: false, error: msg, pid });
139
+ writeOutput(cmd, { ok: false, error: msg, pid, workerEnabled: false });
97
140
  } else {
98
141
  log.error(`Failed to stop memory worker (PID ${pid}): ${msg}`);
99
142
  }
@@ -102,9 +145,12 @@ function stopWorker(opts: { json?: boolean }, cmd: Command): void {
102
145
  }
103
146
 
104
147
  if (shouldOutputJson(cmd)) {
105
- writeOutput(cmd, { ok: true, pid });
148
+ writeOutput(cmd, { ok: true, pid, workerEnabled: false });
106
149
  } else {
107
150
  log.info(`Memory worker stop signal sent (PID ${pid})`);
151
+ log.info(
152
+ "Disabled memory.worker.enabled; the synchronous in-process runner will take over",
153
+ );
108
154
  }
109
155
  }
110
156
 
@@ -113,14 +159,25 @@ function stopWorker(opts: { json?: boolean }, cmd: Command): void {
113
159
  // ---------------------------------------------------------------------------
114
160
 
115
161
  function statusWorker(opts: { json?: boolean }, cmd: Command): void {
116
- const result = probeMemoryWorker();
162
+ const worker = probeMemoryWorker();
163
+ const syncRunner = probeSyncRunner();
164
+ const workerEnabled = getConfigReadOnly().memory.worker.enabled;
165
+
117
166
  if (shouldOutputJson(cmd)) {
118
- writeOutput(cmd, result);
167
+ writeOutput(cmd, { ...worker, workerEnabled, syncRunner });
119
168
  } else {
120
- if (result.status === "running") {
121
- log.info(`Memory worker is running (PID ${result.pid})`);
169
+ if (worker.status === "running") {
170
+ log.info(`Memory worker process is running (PID ${worker.pid})`);
122
171
  } else {
123
- log.info("Memory worker is not running");
172
+ log.info("Memory worker process is not running");
173
+ }
174
+ log.info(`memory.worker.enabled: ${workerEnabled}`);
175
+ if (syncRunner.status === "running") {
176
+ log.info(
177
+ `Synchronous in-process runner is running (PID ${syncRunner.pid})`,
178
+ );
179
+ } else {
180
+ log.info("Synchronous in-process runner is not running");
124
181
  }
125
182
  }
126
183
  }
@@ -139,7 +196,11 @@ export function registerMemoryWorkerCommand(memory: Command): void {
139
196
  "after",
140
197
  `
141
198
  The memory worker processes embedding, consolidation, and cleanup jobs in a
142
- separate OS process so they do not block the daemon's main event loop.
199
+ separate OS process so they do not block the assistant's main event loop.
200
+
201
+ \`start\` enables memory.worker.enabled and \`stop\` disables it, so the
202
+ assistant's synchronous in-process runner stands down (start) or takes back over
203
+ (stop) without a restart.
143
204
 
144
205
  Examples:
145
206
  $ assistant memory worker start
@@ -149,7 +210,9 @@ Examples:
149
210
 
150
211
  worker
151
212
  .command("start")
152
- .description("Start the memory worker as a background process")
213
+ .description(
214
+ "Start the memory worker process and enable memory.worker.enabled",
215
+ )
153
216
  .option("--json", "Emit raw JSON instead of a formatted summary")
154
217
  .action(async (opts: { json?: boolean }, cmd: Command) => {
155
218
  await startWorker(opts, cmd);
@@ -157,7 +220,9 @@ Examples:
157
220
 
158
221
  worker
159
222
  .command("stop")
160
- .description("Stop the running memory worker process")
223
+ .description(
224
+ "Stop the memory worker process and disable memory.worker.enabled",
225
+ )
161
226
  .option("--json", "Emit raw JSON instead of a formatted summary")
162
227
  .action((opts: { json?: boolean }, cmd: Command) => {
163
228
  stopWorker(opts, cmd);
@@ -165,7 +230,9 @@ Examples:
165
230
 
166
231
  worker
167
232
  .command("status")
168
- .description("Check whether the memory worker process is running")
233
+ .description(
234
+ "Report worker process state, memory.worker.enabled, and the synchronous runner",
235
+ )
169
236
  .option("--json", "Emit raw JSON instead of a formatted summary")
170
237
  .action((opts: { json?: boolean }, cmd: Command) => {
171
238
  statusWorker(opts, cmd);
@@ -42,17 +42,7 @@ import {
42
42
  PluginPinHistoryError,
43
43
  resolvePinToMarketplaceCommit,
44
44
  } from "../lib/plugin-pin-history.js";
45
- import {
46
- buildPublishPayload,
47
- findPluginRoot,
48
- formatPayloadForPrint,
49
- formatPublishResult,
50
- formatValidationResult,
51
- postPublishRequest,
52
- resolveGitContext,
53
- resolvePlatformDeps,
54
- validatePluginForPublish,
55
- } from "../lib/publish-plugin.js";
45
+ import { runPublish } from "../lib/publish-plugin.js";
56
46
  import { registerCommand } from "../lib/register-command.js";
57
47
  import {
58
48
  InvalidSearchPatternError,
@@ -561,141 +551,8 @@ $ assistant plugins publish --json`,
561
551
  json?: boolean;
562
552
  category?: string;
563
553
  }) => {
564
- // 1. Find the plugin root
565
- const searchDir = opts.path ?? process.cwd();
566
- const pluginDir = findPluginRoot(searchDir);
567
- if (!pluginDir) {
568
- console.error(
569
- `No package.json found in ${searchDir} or any parent directory.`,
570
- );
571
- process.exitCode = 1;
572
- return;
573
- }
574
-
575
- // 2. Validate the plugin
576
- const validation = validatePluginForPublish(pluginDir);
577
- if (!validation.valid) {
578
- if (opts.json) {
579
- process.stdout.write(
580
- JSON.stringify({ ok: false, errors: validation.issues }) +
581
- "\n",
582
- );
583
- } else {
584
- console.error(formatValidationResult(validation));
585
- }
586
- process.exitCode = 1;
587
- return;
588
- }
589
-
590
- if (!opts.json && validation.warnings.length > 0) {
591
- console.warn(formatValidationResult(validation));
592
- }
593
-
594
- // 3. Resolve git context
595
- let git;
596
- try {
597
- git = await resolveGitContext(pluginDir);
598
- } catch (err) {
599
- const message = err instanceof Error ? err.message : String(err);
600
- console.error(`Git context resolution failed: ${message}`);
601
- process.exitCode = 1;
602
- return;
603
- }
604
-
605
- if (git.dirty) {
606
- console.warn(
607
- "Warning: working tree is dirty. The pinned commit SHA should match a clean, pushed commit.",
608
- );
609
- }
610
-
611
- if (!git.pushed) {
612
- console.error(
613
- `Commit ${git.sha.slice(0, 7)} has not been pushed to the remote. Push your changes first.`,
614
- );
615
- process.exitCode = 1;
616
- return;
617
- }
618
-
619
- // 4. Determine category
620
- const category = opts.category ?? "other";
621
-
622
- // 5. Build the payload
623
- const payload = buildPublishPayload(validation, git, category);
624
-
625
- // 6. Handle --print mode
626
- if (opts.print) {
627
- if (opts.json) {
628
- process.stdout.write(
629
- JSON.stringify({ ok: true, payload }) + "\n",
630
- );
631
- } else {
632
- console.log(formatPayloadForPrint(payload));
633
- }
634
- return;
635
- }
636
-
637
- // 7. Confirm before submitting (unless --force or --json)
638
- if (!opts.force && !opts.json) {
639
- console.log("\nPlugin entry to submit:");
640
- console.log(formatPayloadForPrint(payload));
641
- console.log("");
642
- const result = await confirmPrompt({
643
- question: "Submit this entry to the Vellum marketplace? [y/N] ",
644
- isTTY: Boolean(process.stdin.isTTY),
645
- refuseNonInteractiveMessage:
646
- "Refusing to publish non-interactively. Pass --force to confirm.",
647
- });
648
- if (result === "non-interactive") {
649
- process.exitCode = 1;
650
- return;
651
- }
652
- }
653
-
654
- // 8. Submit to the platform API
655
- const deps = await resolvePlatformDeps();
656
- if (!deps) {
657
- const msg =
658
- "Not connected to Vellum platform. Run `assistant platform connect` to connect, or use --print to generate the entry without submitting.";
659
- if (opts.json) {
660
- process.stdout.write(
661
- JSON.stringify({
662
- ok: false,
663
- error: "not_connected",
664
- message: msg,
665
- }) + "\n",
666
- );
667
- } else {
668
- console.error(msg);
669
- }
670
- process.exitCode = 1;
671
- return;
672
- }
673
-
674
- try {
675
- const result = await postPublishRequest(payload, deps);
676
-
677
- if (opts.json) {
678
- process.stdout.write(JSON.stringify(result) + "\n");
679
- } else {
680
- console.log(formatPublishResult(result));
681
- }
682
-
683
- if (!result.ok) process.exitCode = 1;
684
- } catch (err) {
685
- const message = err instanceof Error ? err.message : String(err);
686
- if (opts.json) {
687
- process.stdout.write(
688
- JSON.stringify({
689
- ok: false,
690
- error: "request_failed",
691
- message,
692
- }) + "\n",
693
- );
694
- } else {
695
- console.error(`Publish request failed: ${message}`);
696
- }
697
- process.exitCode = 1;
698
- }
554
+ const ok = await runPublish(opts, { confirmPrompt });
555
+ if (!ok) process.exitCode = 1;
699
556
  },
700
557
  );
701
558
 
@@ -157,8 +157,8 @@ describe("listAllPlugins", () => {
157
157
  test("includes default plugins with source=default", () => {
158
158
  const result = listAllPlugins({ workspacePluginsDir: pluginsDir });
159
159
  const defaults = result.filter((p) => p.source === "default");
160
- // All 15 default plugins should be present.
161
- expect(defaults.length).toBe(15);
160
+ // All 14 default plugins should be present.
161
+ expect(defaults.length).toBe(14);
162
162
  // Names should all start with "default-".
163
163
  expect(defaults.every((p) => p.name.startsWith("default-"))).toBe(true);
164
164
  // None should be disabled by default in a fresh temp dir.
@@ -212,24 +212,24 @@ describe("listAllPlugins", () => {
212
212
  });
213
213
 
214
214
  test("detects disabled state for default plugins via stub directory", () => {
215
- mkdirSync(join(pluginsDir, "default-advisor"), { recursive: true });
216
- writeFileSync(join(pluginsDir, "default-advisor", ".disabled"), "");
215
+ mkdirSync(join(pluginsDir, "default-compaction"), { recursive: true });
216
+ writeFileSync(join(pluginsDir, "default-compaction", ".disabled"), "");
217
217
 
218
218
  const result = listAllPlugins({ workspacePluginsDir: pluginsDir });
219
- const advisor = result.find((p) => p.name === "default-advisor");
220
- expect(advisor).toBeDefined();
221
- expect(advisor!.disabled).toBe(true);
219
+ const compaction = result.find((p) => p.name === "default-compaction");
220
+ expect(compaction).toBeDefined();
221
+ expect(compaction!.disabled).toBe(true);
222
222
  });
223
223
 
224
224
  test("default stub directory is excluded from user listing", () => {
225
- mkdirSync(join(pluginsDir, "default-advisor"), { recursive: true });
226
- writeFileSync(join(pluginsDir, "default-advisor", ".disabled"), "");
225
+ mkdirSync(join(pluginsDir, "default-compaction"), { recursive: true });
226
+ writeFileSync(join(pluginsDir, "default-compaction", ".disabled"), "");
227
227
 
228
228
  const result = listAllPlugins({ workspacePluginsDir: pluginsDir });
229
229
  // Should appear exactly once, as a default entry (not a user entry).
230
- const advisorEntries = result.filter((p) => p.name === "default-advisor");
231
- expect(advisorEntries).toHaveLength(1);
232
- expect(advisorEntries[0]!.source).toBe("default");
230
+ const compactionEntries = result.filter((p) => p.name === "default-compaction");
231
+ expect(compactionEntries).toHaveLength(1);
232
+ expect(compactionEntries[0]!.source).toBe("default");
233
233
  });
234
234
 
235
235
  test("sort order: enabled user, disabled user, enabled default, disabled default", () => {
@@ -247,8 +247,8 @@ describe("listAllPlugins", () => {
247
247
  writeFileSync(join(pluginsDir, "bbb-disabled", ".disabled"), "");
248
248
 
249
249
  // Disable one default plugin
250
- mkdirSync(join(pluginsDir, "default-advisor"), { recursive: true });
251
- writeFileSync(join(pluginsDir, "default-advisor", ".disabled"), "");
250
+ mkdirSync(join(pluginsDir, "default-compaction"), { recursive: true });
251
+ writeFileSync(join(pluginsDir, "default-compaction", ".disabled"), "");
252
252
 
253
253
  const result = listAllPlugins({ workspacePluginsDir: pluginsDir });
254
254
 
@@ -306,8 +306,8 @@ describe("listAllPlugins", () => {
306
306
 
307
307
  test("default plugins have version from their manifest", () => {
308
308
  const result = listAllPlugins({ workspacePluginsDir: pluginsDir });
309
- const advisor = result.find((p) => p.name === "default-advisor");
310
- expect(advisor).toBeDefined();
311
- expect(advisor!.packageJson?.version).toBeTruthy();
309
+ const compaction = result.find((p) => p.name === "default-compaction");
310
+ expect(compaction).toBeDefined();
311
+ expect(compaction!.packageJson?.version).toBeTruthy();
312
312
  });
313
313
  });
@@ -11,6 +11,7 @@ import {
11
11
  formatValidationResult,
12
12
  type ParsedPackageJson,
13
13
  type PublishValidation,
14
+ runPublish,
14
15
  validatePluginForPublish,
15
16
  } from "../publish-plugin.js";
16
17
 
@@ -200,6 +201,7 @@ describe("buildPublishPayload", () => {
200
201
  repo: "me/my-plugin",
201
202
  dirty: false,
202
203
  pushed: true,
204
+ pluginPath: "",
203
205
  };
204
206
 
205
207
  const payload = buildPublishPayload(validation, git, "productivity");
@@ -208,12 +210,37 @@ describe("buildPublishPayload", () => {
208
210
  expect(payload.source.source).toBe("github");
209
211
  expect(payload.source.repo).toBe("me/my-plugin");
210
212
  expect(payload.source.ref).toBe("e83c5163316f89bfbde7d9ab23ca2e25604af290");
213
+ expect(payload.source.path).toBeUndefined();
211
214
  expect(payload.category).toBe("productivity");
212
215
  expect(payload.description).toBe("Does cool stuff");
213
216
  expect(payload.license).toBe("MIT");
214
217
  expect(payload.homepage).toBe("https://github.com/me/my-plugin");
215
218
  });
216
219
 
220
+ it("sets source.path for nested plugin roots", () => {
221
+ const validation: PublishValidation = {
222
+ valid: true,
223
+ issues: [],
224
+ warnings: [],
225
+ packageJson: {
226
+ name: "my-plugin",
227
+ version: "1.0.0",
228
+ peerDependencies: { "@vellumai/plugin-api": "^1.0.0" },
229
+ },
230
+ pluginDir: "/tmp/monorepo/packages/plugin",
231
+ };
232
+ const git = {
233
+ sha: "e83c5163316f89bfbde7d9ab23ca2e25604af290",
234
+ repo: "me/monorepo",
235
+ dirty: false,
236
+ pushed: true,
237
+ pluginPath: "packages/plugin",
238
+ };
239
+
240
+ const payload = buildPublishPayload(validation, git, "other");
241
+ expect(payload.source.path).toBe("packages/plugin");
242
+ });
243
+
217
244
  it("extracts homepage from repository.url when homepage is absent", () => {
218
245
  const validation: PublishValidation = {
219
246
  valid: true,
@@ -232,6 +259,7 @@ describe("buildPublishPayload", () => {
232
259
  repo: "me/my-plugin",
233
260
  dirty: false,
234
261
  pushed: true,
262
+ pluginPath: "",
235
263
  };
236
264
 
237
265
  const payload = buildPublishPayload(validation, git, "other");
@@ -304,3 +332,73 @@ describe("formatValidationResult", () => {
304
332
  expect(result).toContain("passed");
305
333
  });
306
334
  });
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // runPublish (CLI entrypoint)
338
+ // ---------------------------------------------------------------------------
339
+
340
+ describe("runPublish", () => {
341
+ it("returns false on denied confirmation without submitting", async () => {
342
+ const dir = makePluginDir(tmpdir(), validPkg, { hooks: true });
343
+ // Initialize a git repo so resolveGitContext doesn't fail
344
+ const { execSync } = await import("node:child_process");
345
+ const gitEnv = {
346
+ ...process.env,
347
+ GIT_AUTHOR_NAME: "Test",
348
+ GIT_AUTHOR_EMAIL: "test@test.com",
349
+ GIT_COMMITTER_NAME: "Test",
350
+ GIT_COMMITTER_EMAIL: "test@test.com",
351
+ };
352
+ execSync("git init && git add -A && git commit -m init", {
353
+ cwd: dir,
354
+ stdio: "ignore",
355
+ env: gitEnv,
356
+ });
357
+ execSync("git remote add origin https://github.com/test/test-plugin.git", {
358
+ cwd: dir,
359
+ stdio: "ignore",
360
+ env: gitEnv,
361
+ });
362
+
363
+ const ok = await runPublish(
364
+ { path: dir, force: false, json: false },
365
+ {
366
+ confirmPrompt: async () => "denied",
367
+ },
368
+ );
369
+
370
+ expect(ok).toBe(false);
371
+ rmSync(dir, { recursive: true });
372
+ });
373
+
374
+ it("returns true on --print without submitting", async () => {
375
+ const dir = makePluginDir(tmpdir(), validPkg, { hooks: true });
376
+
377
+ const ok = await runPublish(
378
+ { path: dir, print: true, json: true },
379
+ {
380
+ confirmPrompt: async () => {
381
+ throw new Error("should not be called");
382
+ },
383
+ },
384
+ );
385
+
386
+ expect(ok).toBe(true);
387
+ rmSync(dir, { recursive: true });
388
+ });
389
+
390
+ it("returns false when no package.json found", async () => {
391
+ const dir = mkdtempSync(join(tmpdir(), "no-pkg-"));
392
+ const ok = await runPublish(
393
+ { path: dir, force: true, json: true },
394
+ {
395
+ confirmPrompt: async () => {
396
+ throw new Error("should not be called");
397
+ },
398
+ },
399
+ );
400
+
401
+ expect(ok).toBe(false);
402
+ rmSync(dir, { recursive: true });
403
+ });
404
+ });