@vellumai/assistant 0.5.4 → 0.5.6

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 (151) hide show
  1. package/Dockerfile +17 -27
  2. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -0
  3. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +42 -0
  4. package/package.json +1 -1
  5. package/src/__tests__/actor-token-service.test.ts +113 -0
  6. package/src/__tests__/config-schema.test.ts +2 -2
  7. package/src/__tests__/context-window-manager.test.ts +78 -0
  8. package/src/__tests__/conversation-title-service.test.ts +30 -1
  9. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  10. package/src/__tests__/docker-signing-key-bootstrap.test.ts +207 -0
  11. package/src/__tests__/memory-regressions.test.ts +8 -30
  12. package/src/__tests__/openai-whisper.test.ts +93 -0
  13. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  14. package/src/__tests__/slack-messaging-token-resolution.test.ts +319 -0
  15. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  16. package/src/__tests__/tool-executor.test.ts +4 -0
  17. package/src/__tests__/volume-security-guard.test.ts +155 -0
  18. package/src/cli/commands/conversations.ts +0 -18
  19. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -0
  20. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +16 -37
  21. package/src/config/env-registry.ts +9 -0
  22. package/src/config/env.ts +8 -2
  23. package/src/config/feature-flag-registry.json +8 -8
  24. package/src/config/schema.ts +0 -12
  25. package/src/config/schemas/memory.ts +0 -4
  26. package/src/config/schemas/platform.ts +1 -1
  27. package/src/config/schemas/security.ts +4 -0
  28. package/src/context/window-manager.ts +53 -2
  29. package/src/credential-execution/managed-catalog.ts +5 -15
  30. package/src/daemon/conversation-agent-loop.ts +0 -60
  31. package/src/daemon/conversation-memory.ts +0 -117
  32. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  33. package/src/daemon/daemon-control.ts +7 -0
  34. package/src/daemon/handlers/conversations.ts +0 -11
  35. package/src/daemon/lifecycle.ts +10 -47
  36. package/src/daemon/providers-setup.ts +2 -1
  37. package/src/followups/followup-store.ts +5 -2
  38. package/src/hooks/manager.ts +7 -0
  39. package/src/instrument.ts +33 -1
  40. package/src/memory/conversation-crud.ts +0 -236
  41. package/src/memory/conversation-title-service.ts +26 -10
  42. package/src/memory/db-init.ts +5 -13
  43. package/src/memory/embedding-local.ts +11 -5
  44. package/src/memory/indexer.ts +15 -106
  45. package/src/memory/job-handlers/conversation-starters.ts +24 -36
  46. package/src/memory/job-handlers/embedding.ts +0 -79
  47. package/src/memory/job-utils.ts +1 -1
  48. package/src/memory/jobs-store.ts +0 -8
  49. package/src/memory/jobs-worker.ts +0 -20
  50. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  51. package/src/memory/migrations/index.ts +1 -3
  52. package/src/memory/qdrant-client.ts +4 -6
  53. package/src/memory/schema/conversations.ts +0 -3
  54. package/src/memory/schema/index.ts +0 -2
  55. package/src/messaging/draft-store.ts +2 -2
  56. package/src/messaging/provider.ts +9 -0
  57. package/src/messaging/providers/slack/adapter.ts +29 -2
  58. package/src/oauth/connection-resolver.test.ts +22 -18
  59. package/src/oauth/connection-resolver.ts +92 -7
  60. package/src/oauth/platform-connection.test.ts +78 -69
  61. package/src/oauth/platform-connection.ts +12 -19
  62. package/src/permissions/defaults.ts +3 -3
  63. package/src/permissions/trust-client.ts +332 -0
  64. package/src/permissions/trust-store-interface.ts +105 -0
  65. package/src/permissions/trust-store.ts +531 -39
  66. package/src/platform/client.test.ts +148 -0
  67. package/src/platform/client.ts +71 -0
  68. package/src/providers/speech-to-text/openai-whisper.test.ts +190 -0
  69. package/src/providers/speech-to-text/openai-whisper.ts +68 -0
  70. package/src/providers/speech-to-text/resolve.ts +9 -0
  71. package/src/providers/speech-to-text/types.ts +17 -0
  72. package/src/runtime/auth/route-policy.ts +14 -0
  73. package/src/runtime/auth/token-service.ts +133 -0
  74. package/src/runtime/http-server.ts +4 -2
  75. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  76. package/src/runtime/routes/conversation-query-routes.ts +44 -2
  77. package/src/runtime/routes/conversation-routes.ts +2 -1
  78. package/src/runtime/routes/inbound-message-handler.ts +27 -3
  79. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -1
  80. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +287 -0
  81. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +122 -0
  82. package/src/runtime/routes/log-export-routes.ts +1 -0
  83. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  84. package/src/runtime/routes/memory-item-routes.ts +124 -2
  85. package/src/runtime/routes/secret-routes.ts +4 -1
  86. package/src/runtime/routes/upgrade-broadcast-routes.ts +151 -0
  87. package/src/schedule/schedule-store.ts +0 -21
  88. package/src/security/ces-credential-client.ts +173 -0
  89. package/src/security/secure-keys.ts +65 -22
  90. package/src/signals/bash.ts +3 -0
  91. package/src/signals/cancel.ts +3 -0
  92. package/src/signals/confirm.ts +3 -0
  93. package/src/signals/conversation-undo.ts +3 -0
  94. package/src/signals/event-stream.ts +7 -0
  95. package/src/signals/shotgun.ts +3 -0
  96. package/src/signals/trust-rule.ts +3 -0
  97. package/src/skills/inline-command-render.ts +5 -1
  98. package/src/skills/inline-command-runner.ts +30 -2
  99. package/src/telemetry/usage-telemetry-reporter.test.ts +23 -36
  100. package/src/telemetry/usage-telemetry-reporter.ts +21 -19
  101. package/src/tools/memory/handlers.ts +1 -129
  102. package/src/tools/permission-checker.ts +18 -0
  103. package/src/tools/skills/load.ts +9 -2
  104. package/src/util/device-id.ts +70 -7
  105. package/src/util/logger.ts +35 -9
  106. package/src/util/platform.ts +29 -5
  107. package/src/util/xml.ts +8 -0
  108. package/src/workspace/heartbeat-service.ts +5 -24
  109. package/src/workspace/migrations/migrate-to-workspace-volume.ts +113 -0
  110. package/src/workspace/migrations/registry.ts +2 -0
  111. package/src/__tests__/archive-recall.test.ts +0 -560
  112. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  113. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  114. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  115. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  116. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  117. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  118. package/src/__tests__/memory-brief-time.test.ts +0 -285
  119. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  120. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  121. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  122. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  123. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  124. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  125. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  126. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  127. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  128. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  129. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  130. package/src/__tests__/memory-reducer.test.ts +0 -704
  131. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  132. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  133. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  134. package/src/config/schemas/memory-simplified.ts +0 -101
  135. package/src/memory/archive-recall.ts +0 -516
  136. package/src/memory/archive-store.ts +0 -400
  137. package/src/memory/brief-formatting.ts +0 -33
  138. package/src/memory/brief-open-loops.ts +0 -266
  139. package/src/memory/brief-time.ts +0 -162
  140. package/src/memory/brief.ts +0 -75
  141. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  142. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  143. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  144. package/src/memory/migrations/186-memory-archive.ts +0 -109
  145. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  146. package/src/memory/reducer-scheduler.ts +0 -242
  147. package/src/memory/reducer-store.ts +0 -271
  148. package/src/memory/reducer-types.ts +0 -106
  149. package/src/memory/reducer.ts +0 -467
  150. package/src/memory/schema/memory-archive.ts +0 -121
  151. package/src/memory/schema/memory-brief.ts +0 -55
@@ -176,11 +176,11 @@ async function generateStarters(scopeId: string): Promise<GeneratedStarter[]> {
176
176
  ? truncate(rawIdentityContext, 2000, "\n…[truncated]")
177
177
  : null;
178
178
 
179
- const systemPrompt = `You are generating 4 conversation starters for a personal assistant app. These appear as clickable chips on the empty conversation page — the first thing the user sees when they open the app.
179
+ const systemPrompt = `You are generating 4 conversation starters for a personal assistant app. These appear as clickable chips on the empty conversation page — the first thing the user sees when they open the app. Clicking a chip sends its prompt as a message from the user.
180
180
 
181
181
  ${timeContext}
182
182
 
183
- Your goal: look at what's going on in this person's life right now and suggest the 4 most useful things they could ask you to do. Think about what a thoughtful chief of staff would proactively bring up in a 30-second check-in.
183
+ Your goal: suggest the 4 most useful things this person could ask you to do right now.
184
184
 
185
185
  ${identityContext ? `## Assistant identity & user profile\n\n${identityContext}\n\n` : ""}## What you know
186
186
 
@@ -188,7 +188,9 @@ ${rollup}
188
188
  ${diff}
189
189
  ${skills}
190
190
 
191
- ## How to think about this
191
+ ## Selection
192
+
193
+ Generate exactly 4 starters, ranked #1 (best) to #4.
192
194
 
193
195
  Start from the user's situation, not from the skill list. Ask yourself:
194
196
  - What is this person likely dealing with right now (given the day/time and their context)?
@@ -197,11 +199,7 @@ Start from the user's situation, not from the skill list. Ask yourself:
197
199
 
198
200
  The skills list tells you what the assistant CAN do — use it to filter out suggestions the assistant can't actually help with, not as a menu to generate suggestions from.
199
201
 
200
- ## Selection
201
-
202
- Generate exactly 4 starters, ranked #1 (best) to #4.
203
-
204
- For each, you must be able to clearly answer:
202
+ For each starter, you must clearly answer:
205
203
  - Why now? (timing — day of week, recent activity, upcoming deadline)
206
204
  - Why this user? (grounded in their specific context, not generic)
207
205
  - Why would they be glad I suggested this? (genuine usefulness, not just relevance)
@@ -218,44 +216,34 @@ Favor what is live over what is merely true. Recent changes matter more than old
218
216
 
219
217
  ## Output format
220
218
 
221
- Return exactly 4 starters in rank order (best first).
222
-
223
219
  Each starter has:
224
- - label: 3-6 words, max 40 chars, starts with a verb. Should read as something the user wants to do these chips send a message as the user, so the label must be in the user's voice. Must sound natural when read aloud.
225
- - prompt: 1-2 natural sentences, written as the user would actually say them — not templated.
220
+ - label: 3-6 words, max 40 chars, starts with a verb. Written in the user's voicesomething they'd want to do, not something the assistant is offering.
221
+ - prompt: 1-2 natural sentences, as the user would actually say them.
226
222
  - category: one of ${CONVERSATION_STARTER_CATEGORIES.join(", ")}
227
223
 
228
- The 4 starters should feel like one coherent set of recommendations for this moment — similar abstraction level, no jarring mix of mundane chores and life strategy. Don't lift raw memory phrases, project names, or jargon into labels unless they already sound natural in conversation.
229
-
230
- Never include a chip whose primary meaning is configuration, setup, workflow creation, or "set up X for Y" unless it solves an urgent pain the user is actively feeling right now. Prefer the outcome over the mechanism — "Catch the emails that matter" beats "Set up a playbook for inbox."
231
-
232
- ## Topic diversity
233
-
234
- Each chip should cover a distinct topic or concern. Never have two chips about the same tool, project, or theme — even if there are multiple related issues. Pick the single most impactful angle and give the other slot to something different. Four chips about three topics is too narrow; four chips about four topics is right.
224
+ ## Constraints
235
225
 
236
- ## User-facingness check
226
+ **Voice**: The user clicks these chips to send a message. Every label must read as something the user is asking to do, never something the assistant is saying to the user.
237
227
 
238
- If a label sounds like an issue title, project ticket, or implementation task, rewrite it. Prefer the user-visible payoff over the internal object name. The chip should feel inviting and useful, not merely accurate.
228
+ **Coherence**: The 4 starters should feel like one set similar abstraction level, no jarring mix of mundane chores and life strategy.
239
229
 
240
- Prefer natural, flowing language over mechanical or operational phrasing. "Get Slack messages flowing" is better than "Restore outgoing Slack messages." The label should sound like something a helpful person would say, not a support ticket.
230
+ **Diversity**: Each chip covers a distinct topic. Never two chips about the same tool, project, or theme. Four topics, four chips.
241
231
 
242
- Voice: The user clicks these chips to send a message. Every label must make sense as something the user is asking to do, never something the assistant is saying to the user.
232
+ **No setup chips**: Never include a chip whose primary meaning is configuration or "set up X for Y" unless it solves an urgent pain the user is actively feeling. Prefer the outcome over the mechanism.
243
233
 
244
- Before finalizing each label, ask yourself: would this feel good to click? Or does it sound like a backlog item? If it sounds like a backlog item, rewrite it.
234
+ **Natural language**: No jargon, project names, or raw memory phrases in labels unless they already sound natural in conversation. If a label sounds like a ticket title or backlog item, rewrite it as something the user would actually say.
245
235
 
246
- Examples of bad vs good:
247
- - BAD: "Fix Slack Socket Mode blocker" → GOOD: "Fix Slack so it just works"
248
- - BAD: "Rewire messaging for Socket Mode" → GOOD: "Get Socket Mode stable"
249
- - BAD: "Review this week's calendar" → GOOD: "Protect this week's focus"
250
- - BAD: "Model the coaching transition" → GOOD: "Plan the coaching transition"
251
- - BAD: "Restore outgoing Slack messages" → GOOD: "Get Slack messages flowing"
252
- - BAD: "Set up a playbook for inbox" → GOOD: "Catch the emails that matter"
236
+ ## Examples
253
237
 
254
- Assistant-voice vs user-voice:
255
- - BAD: "You've got a busy week ahead" → GOOD: "Plan my week ahead"
256
- - BAD: "Let me check your calendar" → GOOD: "Check my Thursday schedule"
238
+ Bad → Good (ticket-speak natural):
239
+ - "Fix Slack Socket Mode blocker" → "Fix Slack so it just works"
240
+ - "Restore outgoing Slack messages" → "Get Slack messages flowing"
241
+ - "Review this week's calendar" → "Protect this week's focus"
242
+ - "Set up a playbook for inbox" → "Triage my inbox"
257
243
 
258
- The good versions emphasize the user's payoff in the user's own voice, not the internal mechanism or the assistant's perspective.`;
244
+ Bad Good (assistant voice user voice):
245
+ - "You've got a busy week ahead" → "Plan my week ahead"
246
+ - "Let me check your calendar" → "Check my Thursday schedule"`;
259
247
 
260
248
  const { signal, cleanup } = createTimeout(20000);
261
249
  try {
@@ -280,7 +268,7 @@ The good versions emphasize the user's payoff in the user's own voice, not the i
280
268
  label: {
281
269
  type: "string",
282
270
  description:
283
- "User-voice chip text (2-7 words, max 40 chars, starts with a verb)",
271
+ "User-voice chip label (2-7 words, max 40 chars, verb-first)",
284
272
  },
285
273
  prompt: {
286
274
  type: "string",
@@ -11,10 +11,7 @@ import type { MemoryJob } from "../jobs-store.js";
11
11
  import { extractMediaBlocks } from "../message-content.js";
12
12
  import {
13
13
  mediaAssets,
14
- memoryChunks,
15
- memoryEpisodes,
16
14
  memoryItems,
17
- memoryObservations,
18
15
  memorySegments,
19
16
  memorySummaries,
20
17
  messages,
@@ -93,26 +90,6 @@ export async function embedSummaryJob(
93
90
  );
94
91
  }
95
92
 
96
- export async function embedChunkJob(
97
- job: MemoryJob,
98
- config: AssistantConfig,
99
- ): Promise<void> {
100
- const chunkId = asString(job.payload.chunkId);
101
- if (!chunkId) return;
102
- const db = getDb();
103
- const chunk = db
104
- .select()
105
- .from(memoryChunks)
106
- .where(eq(memoryChunks.id, chunkId))
107
- .get();
108
- if (!chunk) return;
109
- await embedAndUpsert(config, "chunk", chunk.id, chunk.content, {
110
- observation_id: chunk.observationId,
111
- created_at: chunk.createdAt,
112
- memory_scope_id: chunk.scopeId,
113
- });
114
- }
115
-
116
93
  export async function embedMediaJob(
117
94
  job: MemoryJob,
118
95
  config: AssistantConfig,
@@ -146,40 +123,6 @@ export async function embedMediaJob(
146
123
  });
147
124
  }
148
125
 
149
- export async function embedObservationJob(
150
- job: MemoryJob,
151
- config: AssistantConfig,
152
- ): Promise<void> {
153
- const observationId = asString(job.payload.observationId);
154
- const chunkId = asString(job.payload.chunkId);
155
- if (!observationId || !chunkId) return;
156
-
157
- const db = getDb();
158
- const observation = db
159
- .select()
160
- .from(memoryObservations)
161
- .where(eq(memoryObservations.id, observationId))
162
- .get();
163
- if (!observation) return;
164
-
165
- const chunk = db
166
- .select()
167
- .from(memoryChunks)
168
- .where(eq(memoryChunks.id, chunkId))
169
- .get();
170
- if (!chunk) return;
171
-
172
- await embedAndUpsert(config, "observation", chunk.id, chunk.content, {
173
- observation_id: observationId,
174
- conversation_id: observation.conversationId,
175
- role: observation.role,
176
- modality: observation.modality,
177
- source: observation.source,
178
- created_at: observation.createdAt,
179
- memory_scope_id: observation.scopeId,
180
- });
181
- }
182
-
183
126
  export async function embedAttachmentJob(
184
127
  job: MemoryJob,
185
128
  config: AssistantConfig,
@@ -216,25 +159,3 @@ export async function embedAttachmentJob(
216
159
  memory_scope_id: memoryScopeId,
217
160
  });
218
161
  }
219
-
220
- export async function embedEpisodeJob(
221
- job: MemoryJob,
222
- config: AssistantConfig,
223
- ): Promise<void> {
224
- const episodeId = asString(job.payload.episodeId);
225
- if (!episodeId) return;
226
- const db = getDb();
227
- const episode = db
228
- .select()
229
- .from(memoryEpisodes)
230
- .where(eq(memoryEpisodes.id, episodeId))
231
- .get();
232
- if (!episode) return;
233
- const text = `[episode] ${episode.title}: ${episode.summary}`;
234
- await embedAndUpsert(config, "episode", episode.id, text, {
235
- conversation_id: episode.conversationId,
236
- created_at: episode.startAt,
237
- last_seen_at: episode.endAt,
238
- memory_scope_id: episode.scopeId,
239
- });
240
- }
@@ -142,7 +142,7 @@ export function truncate(text: string, max: number): string {
142
142
 
143
143
  export async function embedAndUpsert(
144
144
  config: AssistantConfig,
145
- targetType: "segment" | "item" | "summary" | "observation" | "chunk" | "episode" | "media",
145
+ targetType: "segment" | "item" | "summary" | "media",
146
146
  targetId: string,
147
147
  input: EmbeddingInput,
148
148
  extraPayload?: Record<string, unknown>,
@@ -12,9 +12,6 @@ export type MemoryJobType =
12
12
  | "embed_segment"
13
13
  | "embed_item"
14
14
  | "embed_summary"
15
- | "embed_chunk"
16
- | "embed_episode"
17
- | "embed_observation"
18
15
  | "extract_items"
19
16
  | "extract_entities"
20
17
  | "cleanup_stale_superseded_items"
@@ -30,8 +27,6 @@ export type MemoryJobType =
30
27
  | "embed_media"
31
28
  | "embed_attachment"
32
29
  | "generate_conversation_starters"
33
- | "reduce_conversation_memory"
34
- | "backfill_simplified_memory"
35
30
  | "generate_capability_cards" // legacy compat — silently dropped by worker (capability cards removed)
36
31
  | "generate_thread_starters"; // legacy compat — silently dropped by worker (renamed to generate_conversation_starters)
37
32
 
@@ -39,9 +34,6 @@ const EMBED_JOB_TYPES: MemoryJobType[] = [
39
34
  "embed_segment",
40
35
  "embed_item",
41
36
  "embed_summary",
42
- "embed_chunk",
43
- "embed_episode",
44
- "embed_observation",
45
37
  "embed_media",
46
38
  "embed_attachment",
47
39
  ];
@@ -3,7 +3,6 @@ import type { AssistantConfig } from "../config/types.js";
3
3
  import { getLogger } from "../util/logger.js";
4
4
  import { rawRun } from "./db.js";
5
5
  import { backfillJob } from "./job-handlers/backfill.js";
6
- import { backfillSimplifiedMemoryJob } from "./job-handlers/backfill-simplified-memory.js";
7
6
  import {
8
7
  cleanupStaleSupersededItemsJob,
9
8
  pruneOldConversationsJob,
@@ -12,11 +11,8 @@ import { generateConversationStartersJob } from "./job-handlers/conversation-sta
12
11
  // ── Per-job-type handlers ──────────────────────────────────────────
13
12
  import {
14
13
  embedAttachmentJob,
15
- embedChunkJob,
16
- embedEpisodeJob,
17
14
  embedItemJob,
18
15
  embedMediaJob,
19
- embedObservationJob,
20
16
  embedSegmentJob,
21
17
  embedSummaryJob,
22
18
  } from "./job-handlers/embedding.js";
@@ -26,7 +22,6 @@ import {
26
22
  rebuildIndexJob,
27
23
  } from "./job-handlers/index-maintenance.js";
28
24
  import { mediaProcessingJob } from "./job-handlers/media-processing.js";
29
- import { reduceConversationMemoryJob } from "./job-handlers/reduce-conversation-memory.js";
30
25
  import { buildConversationSummaryJob } from "./job-handlers/summarization.js";
31
26
  import {
32
27
  BackendUnavailableError,
@@ -272,15 +267,6 @@ async function processJob(
272
267
  case "embed_summary":
273
268
  await embedSummaryJob(job, config);
274
269
  return;
275
- case "embed_chunk":
276
- await embedChunkJob(job, config);
277
- return;
278
- case "embed_episode":
279
- await embedEpisodeJob(job, config);
280
- return;
281
- case "embed_observation":
282
- await embedObservationJob(job, config);
283
- return;
284
270
  case "extract_items":
285
271
  await extractItemsJob(job);
286
272
  return;
@@ -321,12 +307,6 @@ async function processJob(
321
307
  case "embed_attachment":
322
308
  await embedAttachmentJob(job, config);
323
309
  return;
324
- case "reduce_conversation_memory":
325
- await reduceConversationMemoryJob(job);
326
- return;
327
- case "backfill_simplified_memory":
328
- await backfillSimplifiedMemoryJob(job);
329
- return;
330
310
  case "generate_conversation_starters":
331
311
  await generateConversationStartersJob(job);
332
312
  return;
@@ -0,0 +1,42 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Drop simplified-memory tables and reducer checkpoint columns added by
6
+ * the simplified-memory-v1 plan, reverting to the legacy item/tier/XML
7
+ * memory system.
8
+ */
9
+ export function migrateDropSimplifiedMemory(database: DrizzleDb): void {
10
+ const raw = getSqliteFrom(database);
11
+
12
+ // Drop simplified-memory tables (idempotent — IF EXISTS).
13
+ raw.exec(`DROP TABLE IF EXISTS time_contexts`);
14
+ raw.exec(`DROP TABLE IF EXISTS open_loops`);
15
+ raw.exec(`DROP TABLE IF EXISTS memory_observations`);
16
+ raw.exec(`DROP TABLE IF EXISTS memory_chunks`);
17
+ raw.exec(`DROP TABLE IF EXISTS memory_episodes`);
18
+
19
+ // Remove reducer checkpoint columns from conversations.
20
+ // SQLite doesn't support DROP COLUMN before 3.35.0, but Bun's built-in
21
+ // SQLite is >= 3.38, so this is safe.
22
+ for (const col of [
23
+ "memory_reduced_through_message_id",
24
+ "memory_dirty_tail_since_message_id",
25
+ "memory_last_reduced_at",
26
+ ]) {
27
+ try {
28
+ raw.exec(`ALTER TABLE conversations DROP COLUMN ${col}`);
29
+ } catch {
30
+ // Column doesn't exist — already cleaned up.
31
+ }
32
+ }
33
+
34
+ // Remove embedding rows for archive target types that no longer exist.
35
+ try {
36
+ raw.exec(
37
+ `DELETE FROM memory_embeddings WHERE target_type IN ('observation', 'chunk', 'episode')`,
38
+ );
39
+ } catch {
40
+ // Column doesn't exist — table was never migrated to include target_type.
41
+ }
42
+ }
@@ -126,10 +126,8 @@ export { migrateRenameThreadStartersCheckpoints } from "./181-rename-thread-star
126
126
  export { migrateOAuthProvidersDisplayMetadata } from "./182-oauth-providers-display-metadata.js";
127
127
  export { migrateConversationForkLineage } from "./183-add-conversation-fork-lineage.js";
128
128
  export { migrateLlmRequestLogProvider } from "./184-llm-request-log-provider.js";
129
- export { migrateMemoryBriefState } from "./185-memory-brief-state.js";
130
- export { migrateMemoryArchiveTables } from "./186-memory-archive.js";
131
- export { migrateMemoryReducerCheckpoints } from "./187-memory-reducer-checkpoints.js";
132
129
  export { migrateScheduleQuietFlag } from "./188-schedule-quiet-flag.js";
130
+ export { migrateDropSimplifiedMemory } from "./189-drop-simplified-memory.js";
133
131
  export {
134
132
  MIGRATION_REGISTRY,
135
133
  type MigrationRegistryEntry,
@@ -20,7 +20,7 @@ export interface QdrantClientConfig {
20
20
  }
21
21
 
22
22
  export interface QdrantPointPayload {
23
- target_type: "segment" | "item" | "summary" | "observation" | "chunk" | "episode" | "media";
23
+ target_type: "segment" | "item" | "summary" | "media";
24
24
  target_id: string;
25
25
  text: string;
26
26
  kind?: string;
@@ -230,7 +230,7 @@ export class VellumQdrantClient {
230
230
  }
231
231
 
232
232
  async upsert(
233
- targetType: "segment" | "item" | "summary" | "observation" | "chunk" | "episode" | "media",
233
+ targetType: "segment" | "item" | "summary" | "media",
234
234
  targetId: string,
235
235
  vector: number[],
236
236
  payload: Omit<QdrantPointPayload, "target_type" | "target_id">,
@@ -324,9 +324,7 @@ export class VellumQdrantClient {
324
324
  async searchWithFilter(
325
325
  vector: number[],
326
326
  limit: number,
327
- targetTypes: Array<
328
- "segment" | "item" | "summary" | "media" | "chunk" | "episode"
329
- >,
327
+ targetTypes: Array<"segment" | "item" | "summary" | "media">,
330
328
  excludeMessageIds?: string[],
331
329
  scopeIds?: string[],
332
330
  ): Promise<QdrantSearchResult[]> {
@@ -349,7 +347,7 @@ export class VellumQdrantClient {
349
347
  },
350
348
  {
351
349
  key: "target_type",
352
- match: { any: ["segment", "summary", "media", "chunk"] },
350
+ match: { any: ["segment", "summary", "media"] },
353
351
  },
354
352
  ],
355
353
  });
@@ -30,9 +30,6 @@ export const conversations = sqliteTable(
30
30
  forkParentMessageId: text("fork_parent_message_id"),
31
31
  isAutoTitle: integer("is_auto_title").notNull().default(1),
32
32
  scheduleJobId: text("schedule_job_id"),
33
- memoryReducedThroughMessageId: text("memory_reduced_through_message_id"),
34
- memoryDirtyTailSinceMessageId: text("memory_dirty_tail_since_message_id"),
35
- memoryLastReducedAt: integer("memory_last_reduced_at"),
36
33
  },
37
34
  (table) => [
38
35
  index("idx_conversations_updated_at").on(table.updatedAt),
@@ -3,8 +3,6 @@ export * from "./contacts.js";
3
3
  export * from "./conversations.js";
4
4
  export * from "./guardian.js";
5
5
  export * from "./infrastructure.js";
6
- export * from "./memory-archive.js";
7
- export * from "./memory-brief.js";
8
6
  export * from "./memory-core.js";
9
7
  export * from "./notifications.js";
10
8
  export * from "./oauth.js";
@@ -10,7 +10,7 @@ import { readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
 
12
12
  import { ensureDir, pathExists } from "../util/fs.js";
13
- import { getRootDir } from "../util/platform.js";
13
+ import { getWorkspaceDir } from "../util/platform.js";
14
14
 
15
15
  export interface Draft {
16
16
  id: string;
@@ -25,7 +25,7 @@ export interface Draft {
25
25
  }
26
26
 
27
27
  function getDraftsDir(platform: string): string {
28
- const dir = join(getRootDir(), "workspace", "data", "drafts", platform);
28
+ const dir = join(getWorkspaceDir(), "data", "drafts", platform);
29
29
  ensureDir(dir);
30
30
  return dir;
31
31
  }
@@ -89,6 +89,15 @@ export interface MessagingProvider {
89
89
  */
90
90
  isConnected?(): Promise<boolean>;
91
91
 
92
+ /**
93
+ * Custom credential resolution for providers with non-standard credential
94
+ * paths (e.g. Slack Socket Mode stores tokens under "slack_channel" rather
95
+ * than the OAuth provider key). When present, getProviderConnection() calls
96
+ * this instead of resolveOAuthConnection(), giving the provider full control
97
+ * over credential lookup including fallback strategies.
98
+ */
99
+ resolveConnection?(account?: string): Promise<OAuthConnection | string>;
100
+
92
101
  /** Platform-specific capabilities for tool routing (e.g. 'reactions', 'threads', 'labels'). */
93
102
  capabilities: Set<string>;
94
103
  }
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * Slack messaging provider adapter.
3
3
  *
4
- * Maps Slack API responses to the platform-agnostic messaging types
5
- * and implements the MessagingProvider interface.
4
+ * Maps Slack API responses to the platform-agnostic messaging types and
5
+ * implements the MessagingProvider interface.
6
6
  */
7
7
 
8
8
  import type { OAuthConnection } from "../../../oauth/connection.js";
9
+ import { resolveOAuthConnection } from "../../../oauth/connection-resolver.js";
10
+ import { isProviderConnected } from "../../../oauth/oauth-store.js";
11
+ import { credentialKey } from "../../../security/credential-key.js";
12
+ import { getSecureKeyAsync } from "../../../security/secure-keys.js";
9
13
  import type { MessagingProvider } from "../../provider.js";
10
14
  import type {
11
15
  ConnectionInfo,
@@ -112,6 +116,29 @@ export const slackProvider: MessagingProvider = {
112
116
  credentialService: "integration:slack",
113
117
  capabilities: new Set(["reactions", "threads", "leave_channel"]),
114
118
 
119
+ async isConnected(): Promise<boolean> {
120
+ // Socket Mode: check for bot token directly in credential store.
121
+ // The token is the source of truth; the slack_channel connection row
122
+ // is advisory (backfill can fail non-fatally on startup).
123
+ const botToken = await getSecureKeyAsync(
124
+ credentialKey("slack_channel", "bot_token"),
125
+ );
126
+ if (botToken) return true;
127
+ // Preserve existing OAuth path (integration:slack) for backwards compat.
128
+ return isProviderConnected("integration:slack");
129
+ },
130
+
131
+ async resolveConnection(account?: string): Promise<OAuthConnection | string> {
132
+ // Socket Mode: return raw bot token if available.
133
+ // Token presence is sufficient — no connection row required.
134
+ const botToken = await getSecureKeyAsync(
135
+ credentialKey("slack_channel", "bot_token"),
136
+ );
137
+ if (botToken) return botToken;
138
+ // Preserve existing OAuth path (integration:slack) for backwards compat.
139
+ return resolveOAuthConnection("integration:slack", { account });
140
+ },
141
+
115
142
  async testConnection(
116
143
  connectionOrToken: OAuthConnection | string,
117
144
  ): Promise<ConnectionInfo> {
@@ -8,12 +8,7 @@ let mockProvider: Record<string, unknown> | undefined;
8
8
  let mockConnection: Record<string, unknown> | undefined;
9
9
  let mockAccessToken: string | undefined;
10
10
  let mockConfig: Record<string, unknown> = {};
11
- let mockManagedProxyCtx = {
12
- enabled: false,
13
- platformBaseUrl: "",
14
- assistantApiKey: "",
15
- };
16
- let mockAssistantId = "";
11
+ let mockPlatformClient: Record<string, unknown> | null = null;
17
12
 
18
13
  // ---------------------------------------------------------------------------
19
14
  // Module mocks (must precede imports of the module under test)
@@ -48,12 +43,10 @@ mock.module("../config/loader.js", () => ({
48
43
  getConfig: () => mockConfig,
49
44
  }));
50
45
 
51
- mock.module("../config/env.js", () => ({
52
- getPlatformAssistantId: () => mockAssistantId,
53
- }));
54
-
55
- mock.module("../providers/managed-proxy/context.js", () => ({
56
- resolveManagedProxyContext: async () => mockManagedProxyCtx,
46
+ mock.module("../platform/client.js", () => ({
47
+ VellumPlatformClient: {
48
+ create: async () => mockPlatformClient,
49
+ },
57
50
  }));
58
51
 
59
52
  // ---------------------------------------------------------------------------
@@ -68,6 +61,22 @@ import { PlatformOAuthConnection } from "./platform-connection.js";
68
61
  // Helpers
69
62
  // ---------------------------------------------------------------------------
70
63
 
64
+ function makeMockClient() {
65
+ return {
66
+ baseUrl: "https://platform.example.com",
67
+ assistantApiKey: "sk-test-key",
68
+ platformAssistantId: "asst-123",
69
+ fetch: mock(async () => {
70
+ return new Response(
71
+ JSON.stringify({
72
+ results: [{ id: "platform-conn-1", account_label: null }],
73
+ }),
74
+ { status: 200 },
75
+ );
76
+ }),
77
+ };
78
+ }
79
+
71
80
  function setupDefaults(): void {
72
81
  mockProvider = {
73
82
  providerKey: "integration:google",
@@ -100,12 +109,7 @@ function setupDefaults(): void {
100
109
  "google-oauth": { mode: "managed" },
101
110
  },
102
111
  };
103
- mockManagedProxyCtx = {
104
- enabled: true,
105
- platformBaseUrl: "https://platform.example.com",
106
- assistantApiKey: "sk-test-key",
107
- };
108
- mockAssistantId = "asst-123";
112
+ mockPlatformClient = makeMockClient();
109
113
  }
110
114
 
111
115
  // ---------------------------------------------------------------------------