@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
@@ -1,373 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- mock.module("../util/logger.js", () => ({
4
- getLogger: () =>
5
- new Proxy({} as Record<string, unknown>, {
6
- get: () => () => {},
7
- }),
8
- }));
9
-
10
- import {
11
- createCallSession,
12
- createPendingQuestion,
13
- } from "../calls/call-store.js";
14
- import { getDb } from "../memory/db-connection.js";
15
- import { initializeDb } from "../memory/db-init.js";
16
- import {
17
- createGuardianActionRequest,
18
- expireGuardianActionRequest,
19
- finalizeFollowup,
20
- getGuardianActionRequest,
21
- markTimedOutWithReason,
22
- progressFollowupState,
23
- resolveGuardianActionRequest,
24
- startFollowupFromExpiredRequest,
25
- } from "../memory/guardian-action-store.js";
26
- import { conversations } from "../memory/schema.js";
27
-
28
- initializeDb();
29
-
30
- function ensureConversation(id: string): void {
31
- const db = getDb();
32
- const now = Date.now();
33
- db.insert(conversations)
34
- .values({
35
- id,
36
- title: `Conversation ${id}`,
37
- createdAt: now,
38
- updatedAt: now,
39
- })
40
- .run();
41
- }
42
-
43
- function resetTables(): void {
44
- const db = getDb();
45
- db.run("DELETE FROM guardian_action_deliveries");
46
- db.run("DELETE FROM guardian_action_requests");
47
- db.run("DELETE FROM call_pending_questions");
48
- db.run("DELETE FROM call_events");
49
- db.run("DELETE FROM call_sessions");
50
- db.run("DELETE FROM messages");
51
- db.run("DELETE FROM conversations");
52
- }
53
-
54
- function createTestRequest(convId: string) {
55
- ensureConversation(convId);
56
- const session = createCallSession({
57
- conversationId: convId,
58
- provider: "twilio",
59
- fromNumber: "+15550001111",
60
- toNumber: "+15550002222",
61
- });
62
- const pq = createPendingQuestion(session.id, "What is the gate code?");
63
- return createGuardianActionRequest({
64
- kind: "ask_guardian",
65
- sourceChannel: "phone",
66
- sourceConversationId: convId,
67
- callSessionId: session.id,
68
- pendingQuestionId: pq.id,
69
- questionText: pq.questionText,
70
- expiresAt: Date.now() + 60_000,
71
- });
72
- }
73
-
74
- describe("guardian-action-followup-store", () => {
75
- beforeEach(() => {
76
- resetTables();
77
- });
78
-
79
- // ── markTimedOutWithReason ──────────────────────────────────────────
80
-
81
- test("markTimedOutWithReason sets expired_reason correctly for call_timeout", () => {
82
- const request = createTestRequest("conv-followup-1");
83
- const result = markTimedOutWithReason(request.id, "call_timeout");
84
-
85
- expect(result).not.toBeNull();
86
- expect(result!.status).toBe("expired");
87
- expect(result!.expiredReason).toBe("call_timeout");
88
- });
89
-
90
- test("markTimedOutWithReason sets expired_reason correctly for sweep_timeout", () => {
91
- const request = createTestRequest("conv-followup-2");
92
- const result = markTimedOutWithReason(request.id, "sweep_timeout");
93
-
94
- expect(result).not.toBeNull();
95
- expect(result!.status).toBe("expired");
96
- expect(result!.expiredReason).toBe("sweep_timeout");
97
- });
98
-
99
- test("markTimedOutWithReason returns null for already-expired request", () => {
100
- const request = createTestRequest("conv-followup-3");
101
-
102
- // First call succeeds
103
- const first = markTimedOutWithReason(request.id, "call_timeout");
104
- expect(first).not.toBeNull();
105
-
106
- // Second call returns null (already expired)
107
- const second = markTimedOutWithReason(request.id, "sweep_timeout");
108
- expect(second).toBeNull();
109
-
110
- // Verify the original reason is preserved
111
- const reloaded = getGuardianActionRequest(request.id);
112
- expect(reloaded!.expiredReason).toBe("call_timeout");
113
- });
114
-
115
- test("markTimedOutWithReason returns null for answered request", () => {
116
- const request = createTestRequest("conv-followup-4");
117
- resolveGuardianActionRequest(request.id, "The code is 1234", "telegram");
118
-
119
- const result = markTimedOutWithReason(request.id, "call_timeout");
120
- expect(result).toBeNull();
121
- });
122
-
123
- // ── startFollowupFromExpiredRequest ─────────────────────────────────
124
-
125
- test("startFollowupFromExpiredRequest transitions correctly", () => {
126
- const request = createTestRequest("conv-followup-5");
127
- markTimedOutWithReason(request.id, "call_timeout");
128
-
129
- const result = startFollowupFromExpiredRequest(
130
- request.id,
131
- "The code is 5678",
132
- );
133
- expect(result).not.toBeNull();
134
- expect(result!.followupState).toBe("awaiting_guardian_choice");
135
- expect(result!.lateAnswerText).toBe("The code is 5678");
136
- expect(result!.lateAnsweredAt).toBeGreaterThan(0);
137
- });
138
-
139
- test("startFollowupFromExpiredRequest rejects pending request", () => {
140
- const request = createTestRequest("conv-followup-6");
141
-
142
- const result = startFollowupFromExpiredRequest(request.id, "Late answer");
143
- expect(result).toBeNull();
144
-
145
- // Verify followup_state unchanged
146
- const reloaded = getGuardianActionRequest(request.id);
147
- expect(reloaded!.followupState).toBe("none");
148
- });
149
-
150
- test("startFollowupFromExpiredRequest rejects answered request", () => {
151
- const request = createTestRequest("conv-followup-7");
152
- resolveGuardianActionRequest(request.id, "Original answer", "telegram");
153
-
154
- const result = startFollowupFromExpiredRequest(request.id, "Late answer");
155
- expect(result).toBeNull();
156
- });
157
-
158
- test("startFollowupFromExpiredRequest rejects already-in-followup request", () => {
159
- const request = createTestRequest("conv-followup-8");
160
- markTimedOutWithReason(request.id, "call_timeout");
161
- startFollowupFromExpiredRequest(request.id, "First late answer");
162
-
163
- // Second attempt should fail
164
- const result = startFollowupFromExpiredRequest(
165
- request.id,
166
- "Another late answer",
167
- );
168
- expect(result).toBeNull();
169
-
170
- // Verify original late answer preserved
171
- const reloaded = getGuardianActionRequest(request.id);
172
- expect(reloaded!.lateAnswerText).toBe("First late answer");
173
- });
174
-
175
- // ── progressFollowupState ───────────────────────────────────────────
176
-
177
- test("progressFollowupState valid transition: awaiting_guardian_choice -> dispatching", () => {
178
- const request = createTestRequest("conv-followup-9");
179
- markTimedOutWithReason(request.id, "call_timeout");
180
- startFollowupFromExpiredRequest(request.id, "Late answer");
181
-
182
- const result = progressFollowupState(
183
- request.id,
184
- "dispatching",
185
- "call_back",
186
- );
187
- expect(result).not.toBeNull();
188
- expect(result!.followupState).toBe("dispatching");
189
- expect(result!.followupAction).toBe("call_back");
190
- });
191
-
192
- test("progressFollowupState rejects terminal transition: awaiting_guardian_choice -> declined", () => {
193
- const request = createTestRequest("conv-followup-10");
194
- markTimedOutWithReason(request.id, "call_timeout");
195
- startFollowupFromExpiredRequest(request.id, "Late answer");
196
-
197
- // Terminal transitions must go through finalizeFollowup, not progressFollowupState
198
- const result = progressFollowupState(request.id, "declined", "decline");
199
- expect(result).toBeNull();
200
-
201
- // Verify state unchanged
202
- const reloaded = getGuardianActionRequest(request.id);
203
- expect(reloaded!.followupState).toBe("awaiting_guardian_choice");
204
- });
205
-
206
- test("progressFollowupState rejects invalid transition: none -> dispatching", () => {
207
- const request = createTestRequest("conv-followup-11");
208
- markTimedOutWithReason(request.id, "call_timeout");
209
-
210
- // followup_state is 'none', cannot jump to 'dispatching'
211
- const result = progressFollowupState(request.id, "dispatching");
212
- expect(result).toBeNull();
213
- });
214
-
215
- test("progressFollowupState rejects invalid transition: dispatching -> awaiting_guardian_choice", () => {
216
- const request = createTestRequest("conv-followup-12");
217
- markTimedOutWithReason(request.id, "call_timeout");
218
- startFollowupFromExpiredRequest(request.id, "Late answer");
219
- progressFollowupState(request.id, "dispatching", "call_back");
220
-
221
- // Cannot go back to awaiting_guardian_choice
222
- const result = progressFollowupState(
223
- request.id,
224
- "awaiting_guardian_choice",
225
- );
226
- expect(result).toBeNull();
227
- });
228
-
229
- test("progressFollowupState rejects transition from terminal state", () => {
230
- const request = createTestRequest("conv-followup-13");
231
- markTimedOutWithReason(request.id, "call_timeout");
232
- startFollowupFromExpiredRequest(request.id, "Late answer");
233
- progressFollowupState(request.id, "dispatching", "call_back");
234
- finalizeFollowup(request.id, "completed");
235
-
236
- // completed is terminal — progressFollowupState cannot leave it
237
- const result = progressFollowupState(request.id, "dispatching");
238
- expect(result).toBeNull();
239
- });
240
-
241
- test("progressFollowupState rejects none -> awaiting_guardian_choice even on expired request", () => {
242
- const request = createTestRequest("conv-followup-13b");
243
- markTimedOutWithReason(request.id, "call_timeout");
244
-
245
- // none -> awaiting_guardian_choice must only go through startFollowupFromExpiredRequest
246
- // (which atomically sets lateAnswerText and lateAnsweredAt)
247
- const result = progressFollowupState(
248
- request.id,
249
- "awaiting_guardian_choice",
250
- );
251
- expect(result).toBeNull();
252
-
253
- // Verify followup_state unchanged
254
- const reloaded = getGuardianActionRequest(request.id);
255
- expect(reloaded!.followupState).toBe("none");
256
- expect(reloaded!.status).toBe("expired");
257
- });
258
-
259
- test("progressFollowupState rejects non-expired request", () => {
260
- const request = createTestRequest("conv-followup-13c");
261
-
262
- // Request is still 'pending', not 'expired' — follow-up transitions must not apply
263
- const result = progressFollowupState(
264
- request.id,
265
- "awaiting_guardian_choice",
266
- );
267
- expect(result).toBeNull();
268
-
269
- // Verify followup_state unchanged
270
- const reloaded = getGuardianActionRequest(request.id);
271
- expect(reloaded!.followupState).toBe("none");
272
- expect(reloaded!.status).toBe("pending");
273
- });
274
-
275
- // ── finalizeFollowup ────────────────────────────────────────────────
276
-
277
- test("finalizeFollowup sets followup_completed_at for completed", () => {
278
- const request = createTestRequest("conv-followup-14");
279
- markTimedOutWithReason(request.id, "call_timeout");
280
- startFollowupFromExpiredRequest(request.id, "Late answer");
281
- progressFollowupState(request.id, "dispatching", "call_back");
282
-
283
- const result = finalizeFollowup(request.id, "completed");
284
- expect(result).not.toBeNull();
285
- expect(result!.followupState).toBe("completed");
286
- expect(result!.followupCompletedAt).toBeGreaterThan(0);
287
- });
288
-
289
- test("finalizeFollowup sets followup_completed_at for failed", () => {
290
- const request = createTestRequest("conv-followup-15");
291
- markTimedOutWithReason(request.id, "call_timeout");
292
- startFollowupFromExpiredRequest(request.id, "Late answer");
293
- progressFollowupState(request.id, "dispatching", "call_back");
294
-
295
- const result = finalizeFollowup(request.id, "failed");
296
- expect(result).not.toBeNull();
297
- expect(result!.followupState).toBe("failed");
298
- expect(result!.followupCompletedAt).toBeGreaterThan(0);
299
- });
300
-
301
- test("finalizeFollowup with declined from awaiting_guardian_choice", () => {
302
- const request = createTestRequest("conv-followup-16");
303
- markTimedOutWithReason(request.id, "call_timeout");
304
- startFollowupFromExpiredRequest(request.id, "Late answer");
305
-
306
- const result = finalizeFollowup(request.id, "declined");
307
- expect(result).not.toBeNull();
308
- expect(result!.followupState).toBe("declined");
309
- expect(result!.followupCompletedAt).toBeGreaterThan(0);
310
- });
311
-
312
- test("finalizeFollowup rejects invalid transition from none", () => {
313
- const request = createTestRequest("conv-followup-17");
314
- markTimedOutWithReason(request.id, "call_timeout");
315
-
316
- // followup_state is 'none', cannot finalize
317
- const result = finalizeFollowup(request.id, "completed");
318
- expect(result).toBeNull();
319
- });
320
-
321
- test("finalizeFollowup rejects non-expired request", () => {
322
- const request = createTestRequest("conv-followup-17b");
323
-
324
- // Request is still 'pending', not 'expired' — finalize must not apply
325
- const result = finalizeFollowup(request.id, "completed");
326
- expect(result).toBeNull();
327
-
328
- // Verify followup_state unchanged
329
- const reloaded = getGuardianActionRequest(request.id);
330
- expect(reloaded!.followupState).toBe("none");
331
- expect(reloaded!.status).toBe("pending");
332
- });
333
-
334
- // ── Existing behavior preserved ─────────────────────────────────────
335
-
336
- test("resolve/expire behavior unchanged: resolveGuardianActionRequest still works", () => {
337
- const request = createTestRequest("conv-followup-18");
338
-
339
- const resolved = resolveGuardianActionRequest(
340
- request.id,
341
- "Answer here",
342
- "telegram",
343
- "user-1",
344
- );
345
- expect(resolved).not.toBeNull();
346
- expect(resolved!.status).toBe("answered");
347
- expect(resolved!.answerText).toBe("Answer here");
348
- // Follow-up fields remain at defaults
349
- expect(resolved!.followupState).toBe("none");
350
- expect(resolved!.expiredReason).toBeNull();
351
- });
352
-
353
- test("expireGuardianActionRequest sets explicit reason", () => {
354
- const request = createTestRequest("conv-followup-19");
355
-
356
- expireGuardianActionRequest(request.id, "call_timeout");
357
-
358
- const reloaded = getGuardianActionRequest(request.id);
359
- expect(reloaded!.status).toBe("expired");
360
- expect(reloaded!.expiredReason).toBe("call_timeout");
361
- });
362
-
363
- test("new fields default correctly on freshly created request", () => {
364
- const request = createTestRequest("conv-followup-21");
365
-
366
- expect(request.expiredReason).toBeNull();
367
- expect(request.followupState).toBe("none");
368
- expect(request.lateAnswerText).toBeNull();
369
- expect(request.lateAnsweredAt).toBeNull();
370
- expect(request.followupAction).toBeNull();
371
- expect(request.followupCompletedAt).toBeNull();
372
- });
373
- });