@vellumai/assistant 0.8.11 → 0.8.12-staging.2

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 (244) hide show
  1. package/ARCHITECTURE.md +15 -17
  2. package/README.md +0 -6
  3. package/bun.lock +6 -122
  4. package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
  5. package/node_modules/@vellumai/gateway-client/package.json +3 -1
  6. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
  7. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
  9. package/openapi.yaml +633 -4
  10. package/package.json +1 -3
  11. package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
  12. package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
  13. package/src/__tests__/anthropic-provider.test.ts +129 -0
  14. package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
  15. package/src/__tests__/btw-routes.test.ts +7 -34
  16. package/src/__tests__/checker.test.ts +6 -12
  17. package/src/__tests__/config-loader-backfill.test.ts +4 -2
  18. package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
  19. package/src/__tests__/config-watcher.test.ts +2 -2
  20. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  21. package/src/__tests__/conversation-error.test.ts +2 -6
  22. package/src/__tests__/conversation-history-web-search.test.ts +8 -0
  23. package/src/__tests__/conversation-title-service.test.ts +2 -1
  24. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  25. package/src/__tests__/disk-pressure-tools.test.ts +1 -1
  26. package/src/__tests__/exploration-drift-hook.test.ts +692 -0
  27. package/src/__tests__/filing-service.test.ts +8 -3
  28. package/src/__tests__/guardian-action-store.test.ts +0 -167
  29. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
  30. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
  31. package/src/__tests__/heartbeat-service.test.ts +5 -2
  32. package/src/__tests__/identity-intro-cache.test.ts +12 -5
  33. package/src/__tests__/identity-routes.test.ts +16 -57
  34. package/src/__tests__/injector-chain.test.ts +8 -3
  35. package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
  36. package/src/__tests__/llm-catalog-parity.test.ts +16 -0
  37. package/src/__tests__/llm-usage-store.test.ts +11 -0
  38. package/src/__tests__/log-export-workspace.test.ts +468 -3
  39. package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
  40. package/src/__tests__/model-intents.test.ts +1 -1
  41. package/src/__tests__/oauth-cli.test.ts +19 -8
  42. package/src/__tests__/openai-provider.test.ts +34 -0
  43. package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
  44. package/src/__tests__/recurrence-engine.test.ts +45 -0
  45. package/src/__tests__/schedule-routes.test.ts +34 -0
  46. package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
  48. package/src/__tests__/secret-fixtures.ts +20 -0
  49. package/src/__tests__/skill-tool-factory.test.ts +49 -0
  50. package/src/__tests__/subagent-role-registry.test.ts +24 -1
  51. package/src/__tests__/subagent-tools.test.ts +1 -0
  52. package/src/__tests__/system-prompt.test.ts +109 -11
  53. package/src/__tests__/tool-approval-handler.test.ts +85 -0
  54. package/src/__tests__/tool-audit-listener.test.ts +86 -0
  55. package/src/__tests__/tool-error-hook.test.ts +1 -0
  56. package/src/__tests__/tool-result-spool.test.ts +337 -0
  57. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
  58. package/src/__tests__/validate-input.test.ts +95 -1
  59. package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
  60. package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
  61. package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +174 -0
  62. package/src/__tests__/workspace-migration-101-upgrade-balanced-economy-to-minimax-m3.test.ts +162 -0
  63. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
  64. package/src/acp/__tests__/agent-process.test.ts +315 -2
  65. package/src/acp/__tests__/prepare-agent-env.test.ts +79 -5
  66. package/src/acp/agent-process.ts +163 -34
  67. package/src/acp/prepare-agent-env.ts +55 -15
  68. package/src/agent/loop.ts +81 -24
  69. package/src/api/events/usage-progress.ts +28 -0
  70. package/src/api/index.ts +6 -0
  71. package/src/background-wake/wake-intent-hooks.test.ts +2 -0
  72. package/src/bundler/app-bundler.ts +25 -42
  73. package/src/bundler/app-compiler.ts +8 -0
  74. package/src/calls/call-controller.ts +1 -1
  75. package/src/cli/commands/plugins.ts +248 -15
  76. package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
  77. package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
  78. package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
  79. package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
  80. package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
  81. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +307 -0
  82. package/src/cli/lib/inspect-plugin.ts +252 -0
  83. package/src/cli/lib/install-from-github.ts +214 -21
  84. package/src/cli/lib/list-installed-plugins.ts +17 -6
  85. package/src/cli/lib/plugin-artifact.ts +103 -0
  86. package/src/cli/lib/plugin-details.ts +18 -1
  87. package/src/cli/lib/plugin-fingerprint.ts +197 -0
  88. package/src/cli/lib/upgrade-plugin.ts +225 -0
  89. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  90. package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
  91. package/src/config/call-site-defaults.ts +13 -2
  92. package/src/config/feature-flag-registry.json +8 -16
  93. package/src/config/loader.ts +52 -59
  94. package/src/config/schema.ts +0 -2
  95. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  96. package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
  97. package/src/config/schemas/llm.ts +10 -0
  98. package/src/config/schemas/memory-v2.ts +13 -0
  99. package/src/config/schemas/memory-v3.ts +92 -0
  100. package/src/config/seed-inference-profiles.ts +4 -8
  101. package/src/context/post-turn-tool-result-truncation.ts +32 -18
  102. package/src/context/tool-result-spool.ts +104 -0
  103. package/src/credential-execution/feature-gates.ts +0 -1
  104. package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
  105. package/src/daemon/conversation-error.ts +6 -15
  106. package/src/daemon/conversation.ts +9 -0
  107. package/src/daemon/disk-pressure-policy.ts +0 -1
  108. package/src/daemon/lifecycle.ts +1 -20
  109. package/src/daemon/message-types/conversations.ts +2 -15
  110. package/src/daemon/trust-context.ts +1 -1
  111. package/src/events/tool-audit-listener.ts +40 -9
  112. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  113. package/src/home/__tests__/home-content-refresh.test.ts +114 -0
  114. package/src/home/__tests__/suggested-prompts.test.ts +86 -5
  115. package/src/home/home-content-refresh.ts +43 -31
  116. package/src/home/home-greeting-cache.ts +8 -1
  117. package/src/home/home-greeting.ts +13 -9
  118. package/src/home/suggested-prompts.ts +77 -24
  119. package/src/ipc/routes/trust-rules.test.ts +66 -72
  120. package/src/media/image-credentials.ts +2 -2
  121. package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
  122. package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
  123. package/src/memory/conversation-attention-store.ts +1 -0
  124. package/src/memory/conversation-bootstrap.ts +18 -9
  125. package/src/memory/conversation-crud.ts +12 -2
  126. package/src/memory/conversation-title-service.ts +53 -9
  127. package/src/memory/delivery-channels.ts +0 -69
  128. package/src/memory/graph/extraction-job.ts +0 -15
  129. package/src/memory/guardian-action-store.ts +1 -376
  130. package/src/memory/llm-usage-store.ts +5 -1
  131. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
  132. package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
  133. package/src/memory/v2/__tests__/injection.test.ts +70 -0
  134. package/src/memory/v2/__tests__/static-context.test.ts +12 -0
  135. package/src/memory/v2/consolidation-job.ts +93 -9
  136. package/src/memory/v2/injection.ts +53 -0
  137. package/src/memory/v2/prompts/consolidation.ts +1 -0
  138. package/src/memory/v2/static-context.ts +13 -1
  139. package/src/memory/v2/sweep-job.ts +1 -1
  140. package/src/memory/v2/types.ts +5 -0
  141. package/src/plugin-api/types.ts +7 -0
  142. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
  143. package/src/plugins/defaults/exploration-drift/package.json +15 -0
  144. package/src/plugins/defaults/index.ts +25 -0
  145. package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
  146. package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
  147. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
  148. package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
  149. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  150. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
  151. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
  152. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
  153. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
  154. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
  155. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
  156. package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
  157. package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
  158. package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
  159. package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
  160. package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
  161. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
  162. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
  163. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
  164. package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
  165. package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
  166. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
  167. package/src/prompts/cache-boundary.ts +17 -0
  168. package/src/prompts/sections.ts +50 -17
  169. package/src/prompts/system-prompt.ts +12 -4
  170. package/src/prompts/templates/system-sections.ts +22 -0
  171. package/src/providers/__tests__/unparseable-tool-args.test.ts +53 -0
  172. package/src/providers/anthropic/client.ts +74 -28
  173. package/src/providers/gemini/client.ts +5 -1
  174. package/src/providers/minimax/client.ts +9 -0
  175. package/src/providers/model-catalog.ts +28 -0
  176. package/src/providers/model-intents.ts +3 -3
  177. package/src/providers/openai/chat-completions-provider.ts +4 -2
  178. package/src/providers/openai/responses-provider.ts +7 -2
  179. package/src/providers/retry.ts +8 -0
  180. package/src/providers/types.ts +11 -0
  181. package/src/providers/unparseable-tool-args.ts +56 -0
  182. package/src/runtime/AGENTS.md +6 -0
  183. package/src/runtime/__tests__/agent-wake.test.ts +2 -2
  184. package/src/runtime/agent-wake.ts +5 -5
  185. package/src/runtime/background-job-runner.ts +2 -2
  186. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
  187. package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
  188. package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
  189. package/src/runtime/migrations/vbundle-importer.ts +9 -4
  190. package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
  191. package/src/runtime/pre-first-message-gate.ts +1 -1
  192. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
  193. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +132 -0
  194. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
  195. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
  196. package/src/runtime/routes/__tests__/plugins-routes.test.ts +348 -0
  197. package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
  198. package/src/runtime/routes/btw-routes.ts +0 -14
  199. package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
  200. package/src/runtime/routes/conversation-list-routes.ts +77 -5
  201. package/src/runtime/routes/conversation-management-routes.ts +54 -0
  202. package/src/runtime/routes/conversation-query-routes.ts +79 -4
  203. package/src/runtime/routes/gateway-log-routes.ts +14 -64
  204. package/src/runtime/routes/home-feed-routes.ts +10 -0
  205. package/src/runtime/routes/identity-intro-cache.ts +1 -1
  206. package/src/runtime/routes/identity-routes.ts +76 -20
  207. package/src/runtime/routes/inbound-message-handler.ts +0 -36
  208. package/src/runtime/routes/log-export-routes.ts +143 -96
  209. package/src/runtime/routes/plugins-routes.ts +380 -0
  210. package/src/runtime/routes/redact-staged-export.ts +259 -0
  211. package/src/runtime/routes/schedule-routes.ts +19 -2
  212. package/src/runtime/routes/trust-rules-routes.ts +14 -67
  213. package/src/schedule/recurrence-engine.ts +34 -0
  214. package/src/schedule/scheduler.ts +1 -0
  215. package/src/security/redact-json.ts +61 -0
  216. package/src/skills/validate-input.ts +41 -1
  217. package/src/subagent/types.ts +26 -1
  218. package/src/telemetry/types.ts +15 -1
  219. package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
  220. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  221. package/src/tools/apps/executors.ts +1 -1
  222. package/src/tools/skills/skill-tool-factory.ts +19 -8
  223. package/src/tools/tool-approval-handler.ts +31 -0
  224. package/src/usage/types.ts +8 -1
  225. package/src/util/platform.ts +16 -0
  226. package/src/watcher/engine.ts +1 -0
  227. package/src/workspace/adaptive-thinking-repair.ts +113 -0
  228. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
  229. package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
  230. package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
  231. package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +86 -0
  232. package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +70 -0
  233. package/src/workspace/migrations/registry.ts +8 -0
  234. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
  235. package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
  236. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
  237. package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
  238. package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
  239. package/src/__tests__/update-bulletin-job.test.ts +0 -292
  240. package/src/config/schemas/updates.ts +0 -14
  241. package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
  242. package/src/memory/conversation-starters-cadence.ts +0 -78
  243. package/src/prompts/update-bulletin-job.ts +0 -180
  244. package/src/runtime/guardian-action-followup-executor.ts +0 -306
@@ -40,75 +40,6 @@ function normalizePositiveIntegerIndexes(value: unknown): number[] {
40
40
  return [...new Set(indexes)].sort((a, b) => a - b);
41
41
  }
42
42
 
43
- // ── Pending verification reply helpers ───────────────────────────────
44
- //
45
- // When a guardian verification succeeds but the confirmation reply fails
46
- // to deliver, we persist the reply details on the inbound event so that
47
- // gateway retries (which arrive as duplicates) can re-attempt delivery.
48
-
49
- export interface PendingVerificationReply {
50
- __pendingVerificationReply: true;
51
- chatId: string;
52
- text: string;
53
- assistantId: string;
54
- }
55
-
56
- /**
57
- * Store a pending verification reply on an inbound event. Called when
58
- * `deliverChannelReply` fails after challenge consumption so the reply
59
- * can be retried on subsequent duplicate deliveries.
60
- */
61
- export function storePendingVerificationReply(
62
- eventId: string,
63
- reply: Omit<PendingVerificationReply, "__pendingVerificationReply">,
64
- ): void {
65
- const db = getDb();
66
- const payload: PendingVerificationReply = {
67
- __pendingVerificationReply: true,
68
- ...reply,
69
- };
70
- db.update(channelInboundEvents)
71
- .set({ rawPayload: JSON.stringify(payload), updatedAt: Date.now() })
72
- .where(eq(channelInboundEvents.id, eventId))
73
- .run();
74
- }
75
-
76
- /**
77
- * Retrieve a pending verification reply for a given event, if one exists.
78
- */
79
- export function getPendingVerificationReply(
80
- eventId: string,
81
- ): PendingVerificationReply | null {
82
- const db = getDb();
83
- const row = db
84
- .select({ rawPayload: channelInboundEvents.rawPayload })
85
- .from(channelInboundEvents)
86
- .where(eq(channelInboundEvents.id, eventId))
87
- .get();
88
-
89
- if (!row?.rawPayload) return null;
90
- try {
91
- const parsed = JSON.parse(row.rawPayload);
92
- if (parsed && parsed.__pendingVerificationReply === true) {
93
- return parsed as PendingVerificationReply;
94
- }
95
- return null;
96
- } catch {
97
- return null;
98
- }
99
- }
100
-
101
- /**
102
- * Clear a pending verification reply after successful delivery.
103
- */
104
- export function clearPendingVerificationReply(eventId: string): void {
105
- const db = getDb();
106
- db.update(channelInboundEvents)
107
- .set({ rawPayload: null, updatedAt: Date.now() })
108
- .where(eq(channelInboundEvents.id, eventId))
109
- .run();
110
- }
111
-
112
43
  // ── Per-segment delivery progress ──────────────────────────────────
113
44
  //
114
45
  // When a split reply (multiple text segments from tool boundaries) fails
@@ -9,7 +9,6 @@
9
9
  import type { AssistantConfig } from "../../config/types.js";
10
10
  import { getLogger } from "../../util/logger.js";
11
11
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "../checkpoints.js";
12
- import { maybeEnqueueConversationStartersJob } from "../conversation-starters-cadence.js";
13
12
  import { asString } from "../job-utils.js";
14
13
  import type { MemoryJob } from "../jobs-store.js";
15
14
  import { runGraphExtraction } from "./extraction.js";
@@ -65,20 +64,6 @@ export async function graphExtractJob(
65
64
  },
66
65
  "Graph extraction job complete",
67
66
  );
68
-
69
- try {
70
- maybeEnqueueConversationStartersJob(scopeId);
71
- } catch (cadenceErr) {
72
- log.warn(
73
- {
74
- err:
75
- cadenceErr instanceof Error
76
- ? cadenceErr.message
77
- : String(cadenceErr),
78
- },
79
- "Conversation starters cadence check failed (non-fatal)",
80
- );
81
- }
82
67
  } catch (err) {
83
68
  log.error(
84
69
  { conversationId, err: err instanceof Error ? err.message : String(err) },
@@ -10,13 +10,10 @@
10
10
  import { and, desc, eq, inArray, lt } from "drizzle-orm";
11
11
  import { v4 as uuid } from "uuid";
12
12
 
13
- import { getLogger } from "../util/logger.js";
14
13
  import { getDb } from "./db-connection.js";
15
14
  import { rawChanges } from "./raw-query.js";
16
15
  import { guardianActionDeliveries, guardianActionRequests } from "./schema.js";
17
16
 
18
- const log = getLogger("guardian-action-store");
19
-
20
17
  // ---------------------------------------------------------------------------
21
18
  // Types
22
19
  // ---------------------------------------------------------------------------
@@ -45,6 +42,7 @@ export type FollowupState =
45
42
  | "completed"
46
43
  | "declined"
47
44
  | "failed";
45
+ /** Follow-up action recorded on a request row (column retained). */
48
46
  export type FollowupAction = "call_back" | "decline";
49
47
 
50
48
  export interface GuardianActionRequest {
@@ -432,180 +430,6 @@ export function cancelGuardianActionRequest(id: string): void {
432
430
  .run();
433
431
  }
434
432
 
435
- // ---------------------------------------------------------------------------
436
- // Follow-up lifecycle helpers
437
- // ---------------------------------------------------------------------------
438
-
439
- /** Valid non-terminal followup_state transitions for progressFollowupState.
440
- * Terminal states (completed, declined, failed) are only reachable via
441
- * finalizeFollowup, which properly sets followupCompletedAt. */
442
- const FOLLOWUP_TRANSITIONS: Record<FollowupState, FollowupState[]> = {
443
- none: [],
444
- awaiting_guardian_choice: ["dispatching"],
445
- dispatching: [],
446
- completed: [],
447
- declined: [],
448
- failed: [],
449
- };
450
-
451
- /** Valid terminal transitions for finalizeFollowup. Maps from current
452
- * followup_state to the terminal states reachable from it. */
453
- const FOLLOWUP_FINALIZE_TRANSITIONS: Partial<
454
- Record<FollowupState, FollowupState[]>
455
- > = {
456
- awaiting_guardian_choice: ["declined"],
457
- dispatching: ["completed", "failed"],
458
- };
459
-
460
- /**
461
- * Atomically set status='expired' and expired_reason on a pending request.
462
- * Returns the updated request on success, or null if the request was not
463
- * in 'pending' status (first-writer-wins).
464
- */
465
- export function markTimedOutWithReason(
466
- id: string,
467
- reason: ExpiredReason,
468
- ): GuardianActionRequest | null {
469
- const db = getDb();
470
- const now = Date.now();
471
-
472
- db.update(guardianActionRequests)
473
- .set({ status: "expired", expiredReason: reason, updatedAt: now })
474
- .where(
475
- and(
476
- eq(guardianActionRequests.id, id),
477
- eq(guardianActionRequests.status, "pending"),
478
- ),
479
- )
480
- .run();
481
-
482
- if (rawChanges() === 0) return null;
483
-
484
- // Also expire active deliveries
485
- db.update(guardianActionDeliveries)
486
- .set({ status: "expired", updatedAt: now })
487
- .where(
488
- and(
489
- eq(guardianActionDeliveries.requestId, id),
490
- inArray(guardianActionDeliveries.status, ["pending", "sent"]),
491
- ),
492
- )
493
- .run();
494
-
495
- return getGuardianActionRequest(id);
496
- }
497
-
498
- /**
499
- * Atomically transition an expired request into the follow-up flow.
500
- * Sets followup_state='awaiting_guardian_choice', records the late answer
501
- * text and timestamp. Only succeeds if status='expired' and followup_state='none'.
502
- * Returns the updated request on success, or null on conflict.
503
- */
504
- export function startFollowupFromExpiredRequest(
505
- id: string,
506
- lateAnswerText: string,
507
- ): GuardianActionRequest | null {
508
- const db = getDb();
509
- const now = Date.now();
510
-
511
- db.update(guardianActionRequests)
512
- .set({
513
- followupState: "awaiting_guardian_choice",
514
- lateAnswerText,
515
- lateAnsweredAt: now,
516
- updatedAt: now,
517
- })
518
- .where(
519
- and(
520
- eq(guardianActionRequests.id, id),
521
- eq(guardianActionRequests.status, "expired"),
522
- eq(guardianActionRequests.followupState, "none"),
523
- ),
524
- )
525
- .run();
526
-
527
- if (rawChanges() === 0) return null;
528
- return getGuardianActionRequest(id);
529
- }
530
-
531
- /**
532
- * Atomically progress the followup_state. Validates that the transition
533
- * is allowed (see FOLLOWUP_TRANSITIONS). Optionally sets the followup_action.
534
- * Returns the updated request on success, or null if the transition was
535
- * invalid or the prior state didn't match.
536
- */
537
- export function progressFollowupState(
538
- id: string,
539
- newState: FollowupState,
540
- action?: FollowupAction,
541
- ): GuardianActionRequest | null {
542
- const request = getGuardianActionRequest(id);
543
- if (!request) return null;
544
-
545
- const allowed = FOLLOWUP_TRANSITIONS[request.followupState];
546
- if (!allowed.includes(newState)) return null;
547
-
548
- const db = getDb();
549
- const now = Date.now();
550
-
551
- const updates: Record<string, unknown> = {
552
- followupState: newState,
553
- updatedAt: now,
554
- };
555
- if (action !== undefined) updates.followupAction = action;
556
-
557
- db.update(guardianActionRequests)
558
- .set(updates)
559
- .where(
560
- and(
561
- eq(guardianActionRequests.id, id),
562
- eq(guardianActionRequests.status, "expired"),
563
- eq(guardianActionRequests.followupState, request.followupState),
564
- ),
565
- )
566
- .run();
567
-
568
- if (rawChanges() === 0) return null;
569
- return getGuardianActionRequest(id);
570
- }
571
-
572
- /**
573
- * Finalize a follow-up by setting the terminal followup_state and
574
- * recording followup_completed_at. Only succeeds from a non-terminal state
575
- * and only on expired requests.
576
- */
577
- export function finalizeFollowup(
578
- id: string,
579
- finalState: "completed" | "declined" | "failed",
580
- ): GuardianActionRequest | null {
581
- const request = getGuardianActionRequest(id);
582
- if (!request) return null;
583
-
584
- const allowed = FOLLOWUP_FINALIZE_TRANSITIONS[request.followupState];
585
- if (!allowed?.includes(finalState)) return null;
586
-
587
- const db = getDb();
588
- const now = Date.now();
589
-
590
- db.update(guardianActionRequests)
591
- .set({
592
- followupState: finalState,
593
- followupCompletedAt: now,
594
- updatedAt: now,
595
- })
596
- .where(
597
- and(
598
- eq(guardianActionRequests.id, id),
599
- eq(guardianActionRequests.status, "expired"),
600
- eq(guardianActionRequests.followupState, request.followupState),
601
- ),
602
- )
603
- .run();
604
-
605
- if (rawChanges() === 0) return null;
606
- return getGuardianActionRequest(id);
607
- }
608
-
609
433
  // ---------------------------------------------------------------------------
610
434
  // Guardian Action Deliveries
611
435
  // ---------------------------------------------------------------------------
@@ -640,205 +464,6 @@ export function createGuardianActionDelivery(params: {
640
464
  return rowToDelivery(row);
641
465
  }
642
466
 
643
- /**
644
- * Look up all pending deliveries by destination conversation ID.
645
- * Used for disambiguation when a reused vellum conversation has multiple active
646
- * guardian requests.
647
- */
648
- export function getPendingDeliveriesByConversation(
649
- conversationId: string,
650
- ): GuardianActionDelivery[] {
651
- try {
652
- const db = getDb();
653
- const rows = db
654
- .select({ delivery: guardianActionDeliveries })
655
- .from(guardianActionDeliveries)
656
- .innerJoin(
657
- guardianActionRequests,
658
- eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
659
- )
660
- .where(
661
- and(
662
- eq(
663
- guardianActionDeliveries.destinationConversationId,
664
- conversationId,
665
- ),
666
- eq(guardianActionDeliveries.status, "sent"),
667
- eq(guardianActionRequests.status, "pending"),
668
- ),
669
- )
670
- .all();
671
- return rows.map((r) => rowToDelivery(r.delivery));
672
- } catch (err) {
673
- if (err instanceof Error && err.message.includes("no such table")) {
674
- log.warn({ err }, "guardian tables not yet created");
675
- return [];
676
- }
677
- throw err;
678
- }
679
- }
680
-
681
- /**
682
- * Look up sent deliveries for expired requests eligible for follow-up.
683
- * Used by inbound message routing to match late guardian answers to expired requests.
684
- */
685
- export function getExpiredDeliveriesByDestination(
686
- channel: string,
687
- chatId: string,
688
- ): GuardianActionDelivery[] {
689
- try {
690
- const db = getDb();
691
-
692
- const rows = db
693
- .select({
694
- delivery: guardianActionDeliveries,
695
- })
696
- .from(guardianActionDeliveries)
697
- .innerJoin(
698
- guardianActionRequests,
699
- eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
700
- )
701
- .where(
702
- and(
703
- eq(guardianActionRequests.status, "expired"),
704
- eq(guardianActionRequests.followupState, "none"),
705
- eq(guardianActionDeliveries.destinationChannel, channel),
706
- eq(guardianActionDeliveries.destinationChatId, chatId),
707
- eq(guardianActionDeliveries.status, "expired"),
708
- ),
709
- )
710
- .all();
711
-
712
- return rows.map((r) => rowToDelivery(r.delivery));
713
- } catch (err) {
714
- if (err instanceof Error && err.message.includes("no such table")) {
715
- log.warn({ err }, "guardian tables not yet created");
716
- return [];
717
- }
718
- throw err;
719
- }
720
- }
721
-
722
- /**
723
- * Look up all expired deliveries by destination conversation ID.
724
- * Used for disambiguation when a reused vellum conversation has multiple expired
725
- * guardian requests eligible for follow-up.
726
- */
727
- export function getExpiredDeliveriesByConversation(
728
- conversationId: string,
729
- ): GuardianActionDelivery[] {
730
- try {
731
- const db = getDb();
732
- const rows = db
733
- .select({ delivery: guardianActionDeliveries })
734
- .from(guardianActionDeliveries)
735
- .innerJoin(
736
- guardianActionRequests,
737
- eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
738
- )
739
- .where(
740
- and(
741
- eq(
742
- guardianActionDeliveries.destinationConversationId,
743
- conversationId,
744
- ),
745
- eq(guardianActionDeliveries.status, "expired"),
746
- eq(guardianActionRequests.status, "expired"),
747
- eq(guardianActionRequests.followupState, "none"),
748
- ),
749
- )
750
- .all();
751
- return rows.map((r) => rowToDelivery(r.delivery));
752
- } catch (err) {
753
- if (err instanceof Error && err.message.includes("no such table")) {
754
- log.warn({ err }, "guardian tables not yet created");
755
- return [];
756
- }
757
- throw err;
758
- }
759
- }
760
-
761
- /**
762
- * Look up deliveries for requests in `awaiting_guardian_choice` follow-up state.
763
- * Used by inbound message routing to intercept guardian follow-up replies
764
- * on channel paths (Telegram, WhatsApp).
765
- */
766
- export function getFollowupDeliveriesByDestination(
767
- channel: string,
768
- chatId: string,
769
- ): GuardianActionDelivery[] {
770
- try {
771
- const db = getDb();
772
-
773
- const rows = db
774
- .select({
775
- delivery: guardianActionDeliveries,
776
- })
777
- .from(guardianActionDeliveries)
778
- .innerJoin(
779
- guardianActionRequests,
780
- eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
781
- )
782
- .where(
783
- and(
784
- eq(guardianActionRequests.status, "expired"),
785
- eq(guardianActionRequests.followupState, "awaiting_guardian_choice"),
786
- eq(guardianActionDeliveries.destinationChannel, channel),
787
- eq(guardianActionDeliveries.destinationChatId, chatId),
788
- eq(guardianActionDeliveries.status, "expired"),
789
- ),
790
- )
791
- .all();
792
-
793
- return rows.map((r) => rowToDelivery(r.delivery));
794
- } catch (err) {
795
- if (err instanceof Error && err.message.includes("no such table")) {
796
- log.warn({ err }, "guardian tables not yet created");
797
- return [];
798
- }
799
- throw err;
800
- }
801
- }
802
-
803
- /**
804
- * Look up all deliveries for requests in `awaiting_guardian_choice` follow-up
805
- * state by destination conversation ID. Used for disambiguation when a reused
806
- * vellum conversation has multiple follow-up guardian requests.
807
- */
808
- export function getFollowupDeliveriesByConversation(
809
- conversationId: string,
810
- ): GuardianActionDelivery[] {
811
- try {
812
- const db = getDb();
813
- const rows = db
814
- .select({ delivery: guardianActionDeliveries })
815
- .from(guardianActionDeliveries)
816
- .innerJoin(
817
- guardianActionRequests,
818
- eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
819
- )
820
- .where(
821
- and(
822
- eq(
823
- guardianActionDeliveries.destinationConversationId,
824
- conversationId,
825
- ),
826
- eq(guardianActionDeliveries.status, "expired"),
827
- eq(guardianActionRequests.status, "expired"),
828
- eq(guardianActionRequests.followupState, "awaiting_guardian_choice"),
829
- ),
830
- )
831
- .all();
832
- return rows.map((r) => rowToDelivery(r.delivery));
833
- } catch (err) {
834
- if (err instanceof Error && err.message.includes("no such table")) {
835
- log.warn({ err }, "guardian tables not yet created");
836
- return [];
837
- }
838
- throw err;
839
- }
840
- }
841
-
842
467
  export function updateDeliveryStatus(
843
468
  deliveryId: string,
844
469
  status: GuardianActionDeliveryStatus,
@@ -43,6 +43,7 @@ export function recordUsageEvent(
43
43
  createdAt: Date.now(),
44
44
  ...input,
45
45
  callSite: input.callSite ?? null,
46
+ llmCallCount: input.llmCallCount ?? 1,
46
47
  inferenceProfile: input.inferenceProfile ?? null,
47
48
  inferenceProfileSource: input.inferenceProfileSource ?? null,
48
49
  estimatedCostUsd: pricing.estimatedCostUsd,
@@ -69,7 +70,7 @@ export function recordUsageEvent(
69
70
  rawUsage: event.rawUsage === null ? null : JSON.stringify(event.rawUsage),
70
71
  estimatedCostUsd: event.estimatedCostUsd,
71
72
  pricingStatus: event.pricingStatus,
72
- llmCallCount: event.llmCallCount ?? 1,
73
+ llmCallCount: event.llmCallCount,
73
74
  metadataJson: null,
74
75
  // Capture the assistant's version at RECORD time so a batch flush
75
76
  // days later doesn't mis-attribute this row to whatever version
@@ -105,6 +106,7 @@ function rowToUsageEvent(row: {
105
106
  rawUsage: string | null;
106
107
  estimatedCostUsd: number | null;
107
108
  pricingStatus: string;
109
+ llmCallCount: number | null;
108
110
  assistantVersion: string | null;
109
111
  }): UsageEvent {
110
112
  return {
@@ -127,6 +129,7 @@ function rowToUsageEvent(row: {
127
129
  rawUsage: parseRawUsage(row.rawUsage),
128
130
  estimatedCostUsd: row.estimatedCostUsd,
129
131
  pricingStatus: row.pricingStatus as "priced" | "unpriced",
132
+ llmCallCount: row.llmCallCount,
130
133
  assistantVersion: row.assistantVersion,
131
134
  };
132
135
  }
@@ -230,6 +233,7 @@ export function queryUnreportedUsageEvents(
230
233
  estimatedCostUsd: llmUsageEvents.estimatedCostUsd,
231
234
  pricingStatus: llmUsageEvents.pricingStatus,
232
235
  assistantVersion: llmUsageEvents.assistantVersion,
236
+ llmCallCount: llmUsageEvents.llmCallCount,
233
237
  conversationType: conversations.conversationType,
234
238
  // Null when conversationId is null (no parent conversation).
235
239
  // Otherwise the count of eligible user turns up to and including
@@ -4,8 +4,8 @@ import { withCrashRecovery } from "./validate-migration-state.js";
4
4
 
5
5
  /**
6
6
  * Rename checkpoint keys from the old `thread_starters:` prefix to
7
- * `conversation_starters:` so that the renamed code paths in
8
- * `conversation-starters-cadence.ts` and `conversation-starters.ts`
7
+ * `conversation_starters:` so that the renamed code paths (see
8
+ * `conversation-starter-checkpoints.ts` and `conversation-starters.ts`)
9
9
  * find existing generation state and avoid unnecessary re-generation.
10
10
  *
11
11
  * This was originally appended to migration 174, but that migration