@vellumai/assistant 0.7.2 → 0.7.3

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 (347) hide show
  1. package/ARCHITECTURE.md +16 -1
  2. package/docs/architecture/memory.md +5 -2
  3. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  4. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  5. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  6. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  7. package/openapi.yaml +449 -22
  8. package/package.json +1 -1
  9. package/src/__tests__/app-control-flow.test.ts +21 -11
  10. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  11. package/src/__tests__/assistant-event.test.ts +0 -10
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  14. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  15. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  16. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  18. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  19. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  20. package/src/__tests__/config-schema.test.ts +1 -0
  21. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  22. package/src/__tests__/config-watcher.test.ts +140 -69
  23. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  24. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  25. package/src/__tests__/context-search-fanout.test.ts +0 -1
  26. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  27. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  28. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  29. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  30. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  31. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  32. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  33. package/src/__tests__/conversation-error.test.ts +150 -3
  34. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  35. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  36. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  37. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  38. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  39. package/src/__tests__/conversation-store.test.ts +0 -18
  40. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  41. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  42. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  43. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  44. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  45. package/src/__tests__/credentials-cli.test.ts +7 -0
  46. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  47. package/src/__tests__/date-context.test.ts +164 -2
  48. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  49. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  50. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  51. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  52. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  53. package/src/__tests__/disk-usage.test.ts +150 -0
  54. package/src/__tests__/events-client-registration.test.ts +52 -0
  55. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  56. package/src/__tests__/file-write-tool.test.ts +4 -10
  57. package/src/__tests__/filing-service.test.ts +3 -4
  58. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  59. package/src/__tests__/heartbeat-service.test.ts +260 -11
  60. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  61. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  62. package/src/__tests__/host-bash-routes.test.ts +178 -13
  63. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  64. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  65. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  66. package/src/__tests__/host-file-proxy.test.ts +268 -6
  67. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  68. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  69. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  70. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  71. package/src/__tests__/injector-chain.test.ts +18 -6
  72. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  73. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  74. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  75. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  77. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  78. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  79. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  80. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  81. package/src/__tests__/openai-provider.test.ts +45 -8
  82. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  83. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  84. package/src/__tests__/platform.test.ts +2 -1
  85. package/src/__tests__/playbook-execution.test.ts +0 -43
  86. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  87. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  88. package/src/__tests__/provider-tool-name.test.ts +23 -0
  89. package/src/__tests__/relay-server.test.ts +15 -4
  90. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  91. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  92. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  93. package/src/__tests__/suggestion-routes.test.ts +46 -0
  94. package/src/__tests__/twilio-validation.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  96. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  97. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  98. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  99. package/src/approvals/guardian-decision-primitive.ts +13 -0
  100. package/src/approvals/guardian-request-resolvers.ts +16 -17
  101. package/src/backup/snapshot-lock.ts +2 -27
  102. package/src/bundler/compiler-tools.ts +3 -2
  103. package/src/calls/call-conversation-messages.ts +46 -10
  104. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  105. package/src/cli/commands/bash.ts +35 -108
  106. package/src/cli/commands/contacts.ts +64 -25
  107. package/src/cli/commands/credentials.ts +56 -0
  108. package/src/cli/commands/memory-v2.ts +7 -6
  109. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  110. package/src/cli/commands/oauth/connect.ts +127 -1
  111. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  112. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  113. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  114. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  115. package/src/cli/commands/platform/index.ts +16 -7
  116. package/src/cli/commands/status.ts +57 -0
  117. package/src/cli/program.ts +4 -2
  118. package/src/config/assistant-feature-flags.ts +13 -3
  119. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  120. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  121. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  122. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  123. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  124. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  125. package/src/config/env.ts +0 -8
  126. package/src/config/feature-flag-registry.json +27 -3
  127. package/src/config/loader.ts +127 -8
  128. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  129. package/src/config/schemas/call-site-catalog.ts +14 -0
  130. package/src/config/schemas/channels.ts +0 -5
  131. package/src/config/schemas/heartbeat.ts +1 -1
  132. package/src/config/schemas/llm.ts +2 -0
  133. package/src/config/schemas/memory-lifecycle.ts +13 -0
  134. package/src/config/schemas/memory-v2.ts +75 -11
  135. package/src/config/schemas/platform.ts +43 -3
  136. package/src/config/schemas/services.ts +28 -0
  137. package/src/config/seed-inference-profiles.ts +230 -33
  138. package/src/contacts/contact-store.ts +0 -25
  139. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  140. package/src/daemon/assistant-attachments.ts +4 -4
  141. package/src/daemon/config-watcher.ts +85 -57
  142. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  143. package/src/daemon/conversation-agent-loop.ts +170 -33
  144. package/src/daemon/conversation-error.ts +87 -15
  145. package/src/daemon/conversation-lifecycle.ts +1 -3
  146. package/src/daemon/conversation-process.ts +8 -0
  147. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  148. package/src/daemon/conversation-store.ts +2 -2
  149. package/src/daemon/conversation-surfaces.ts +195 -15
  150. package/src/daemon/conversation-tool-setup.ts +57 -14
  151. package/src/daemon/conversation.ts +17 -22
  152. package/src/daemon/date-context.ts +71 -22
  153. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  154. package/src/daemon/disk-pressure-guard.ts +343 -0
  155. package/src/daemon/disk-pressure-policy.ts +163 -0
  156. package/src/daemon/handlers/shared.ts +0 -1
  157. package/src/daemon/handlers/skills.ts +3 -4
  158. package/src/daemon/host-app-control-proxy.ts +137 -41
  159. package/src/daemon/host-bash-proxy.ts +46 -21
  160. package/src/daemon/host-cu-proxy.ts +49 -3
  161. package/src/daemon/host-file-proxy.ts +43 -7
  162. package/src/daemon/host-transfer-proxy.ts +95 -4
  163. package/src/daemon/lifecycle.ts +79 -28
  164. package/src/daemon/meet-host-supervisor.ts +4 -4
  165. package/src/daemon/meet-manifest-loader.ts +0 -1
  166. package/src/daemon/memory-v2-startup.ts +14 -4
  167. package/src/daemon/message-protocol.ts +3 -0
  168. package/src/daemon/message-types/conversations.ts +4 -0
  169. package/src/daemon/message-types/disk-pressure.ts +9 -0
  170. package/src/daemon/message-types/messages.ts +3 -0
  171. package/src/daemon/profiler-run-store.ts +5 -5
  172. package/src/daemon/tool-setup-types.ts +2 -2
  173. package/src/documents/document-store.ts +85 -0
  174. package/src/filing/filing-service.ts +30 -5
  175. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  176. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  177. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  178. package/src/heartbeat/heartbeat-service.ts +205 -31
  179. package/src/home/feed-scheduler.ts +18 -0
  180. package/src/inbound/platform-callback-registration.ts +8 -15
  181. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  182. package/src/ipc/assistant-server.ts +56 -2
  183. package/src/ipc/gateway-client.ts +37 -3
  184. package/src/live-voice/live-voice-archive.ts +4 -4
  185. package/src/live-voice/protocol.ts +5 -7
  186. package/src/media/image-service.ts +1 -7
  187. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  188. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  189. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  190. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  191. package/src/memory/admin.ts +5 -9
  192. package/src/memory/context-search/agent-runner.ts +19 -2
  193. package/src/memory/context-search/sources/conversations.ts +2 -11
  194. package/src/memory/context-search/sources/memory-v2.ts +5 -4
  195. package/src/memory/context-search/sources/memory.ts +0 -1
  196. package/src/memory/context-search/types.ts +0 -1
  197. package/src/memory/conversation-crud.ts +4 -12
  198. package/src/memory/db-init.ts +2 -0
  199. package/src/memory/embedding-runtime-manager.ts +119 -5
  200. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
  201. package/src/memory/graph/conversation-graph-memory.ts +42 -54
  202. package/src/memory/graph/extraction.ts +1 -3
  203. package/src/memory/graph/graph-search.test.ts +10 -67
  204. package/src/memory/graph/graph-search.ts +1 -20
  205. package/src/memory/graph/retriever.test.ts +6 -0
  206. package/src/memory/graph/retriever.ts +6 -10
  207. package/src/memory/indexer.ts +54 -45
  208. package/src/memory/job-handlers/backfill.ts +2 -11
  209. package/src/memory/job-handlers/cleanup.ts +43 -0
  210. package/src/memory/job-handlers/embedding.ts +6 -8
  211. package/src/memory/job-handlers/summarization.ts +2 -7
  212. package/src/memory/jobs-store.ts +48 -0
  213. package/src/memory/jobs-worker.ts +81 -43
  214. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  215. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  216. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  217. package/src/memory/migrations/index.ts +1 -0
  218. package/src/memory/pkb/pkb-search.test.ts +6 -0
  219. package/src/memory/qdrant-client.ts +0 -13
  220. package/src/memory/rerank-local.ts +374 -0
  221. package/src/memory/search/semantic.ts +6 -67
  222. package/src/memory/trace-event-store.ts +1 -17
  223. package/src/memory/v2/__tests__/activation.test.ts +311 -250
  224. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  225. package/src/memory/v2/__tests__/injection.test.ts +157 -167
  226. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  227. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  228. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  229. package/src/memory/v2/__tests__/sim.test.ts +5 -199
  230. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  231. package/src/memory/v2/__tests__/static-context.test.ts +76 -1
  232. package/src/memory/v2/activation.ts +149 -156
  233. package/src/memory/v2/consolidation-job.ts +62 -12
  234. package/src/memory/v2/injection.ts +47 -60
  235. package/src/memory/v2/prompts/consolidation.ts +36 -1
  236. package/src/memory/v2/qdrant.ts +99 -0
  237. package/src/memory/v2/reranker.ts +177 -0
  238. package/src/memory/v2/sim.ts +10 -84
  239. package/src/memory/v2/skill-content.ts +4 -3
  240. package/src/memory/v2/skill-store.ts +82 -59
  241. package/src/memory/v2/static-context.ts +22 -0
  242. package/src/memory/v2/types.ts +10 -10
  243. package/src/notifications/copy-composer.ts +13 -0
  244. package/src/notifications/signal.ts +4 -0
  245. package/src/oauth/AGENTS.md +3 -1
  246. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  247. package/src/oauth/connect-orchestrator.ts +2 -0
  248. package/src/oauth/connection-resolver.test.ts +66 -1
  249. package/src/oauth/connection-resolver.ts +55 -1
  250. package/src/oauth/oauth-connect-state.ts +77 -0
  251. package/src/oauth/seed-providers.ts +58 -1
  252. package/src/plugins/defaults/injectors.ts +35 -2
  253. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  254. package/src/plugins/types.ts +7 -0
  255. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  256. package/src/proactive-artifact/decision.test.ts +226 -0
  257. package/src/proactive-artifact/decision.ts +165 -0
  258. package/src/proactive-artifact/index.ts +7 -0
  259. package/src/proactive-artifact/job.test.ts +867 -0
  260. package/src/proactive-artifact/job.ts +352 -0
  261. package/src/proactive-artifact/message-copy.ts +41 -0
  262. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  263. package/src/proactive-artifact/trigger-state.ts +119 -0
  264. package/src/prompts/normalize-onboarding.ts +80 -0
  265. package/src/prompts/persona-resolver.ts +101 -9
  266. package/src/prompts/system-prompt.ts +21 -7
  267. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  268. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  269. package/src/providers/model-intents.ts +7 -0
  270. package/src/providers/openrouter/client.ts +8 -0
  271. package/src/providers/retry.ts +50 -0
  272. package/src/providers/types.ts +1 -0
  273. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  274. package/src/runtime/agent-wake.ts +238 -100
  275. package/src/runtime/assistant-event-hub.ts +36 -6
  276. package/src/runtime/assistant-event.ts +0 -1
  277. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  278. package/src/runtime/auth/route-policy.ts +14 -1
  279. package/src/runtime/auth/same-actor.ts +216 -0
  280. package/src/runtime/channel-retry-sweep.ts +65 -1
  281. package/src/runtime/guardian-reply-router.ts +10 -0
  282. package/src/runtime/local-actor-identity.ts +52 -11
  283. package/src/runtime/pending-interactions.ts +8 -0
  284. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  286. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  287. package/src/runtime/routes/client-routes.ts +20 -2
  288. package/src/runtime/routes/contact-routes.ts +0 -25
  289. package/src/runtime/routes/conversation-routes.ts +35 -26
  290. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  291. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  292. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  293. package/src/runtime/routes/documents-routes.ts +2 -75
  294. package/src/runtime/routes/events-routes.ts +41 -9
  295. package/src/runtime/routes/host-bash-routes.ts +23 -3
  296. package/src/runtime/routes/host-cu-routes.ts +33 -6
  297. package/src/runtime/routes/host-file-routes.ts +32 -6
  298. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  299. package/src/runtime/routes/identity-routes.ts +7 -138
  300. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  301. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  302. package/src/runtime/routes/index.ts +6 -0
  303. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  304. package/src/runtime/routes/memory-v2-routes.ts +33 -0
  305. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  306. package/src/runtime/verification-outbound-actions.ts +4 -4
  307. package/src/schedule/run-script.ts +37 -5
  308. package/src/schedule/scheduler.ts +20 -1
  309. package/src/security/encrypted-store.ts +2 -0
  310. package/src/security/secure-keys.ts +55 -0
  311. package/src/skills/remote-skill-policy.ts +4 -10
  312. package/src/subagent/index.ts +1 -7
  313. package/src/subagent/manager.ts +1 -15
  314. package/src/tasks/task-runner.ts +0 -1
  315. package/src/tasks/task-store.ts +0 -3
  316. package/src/tools/background-tool-registry.ts +17 -3
  317. package/src/tools/host-filesystem/edit.test.ts +151 -0
  318. package/src/tools/host-filesystem/edit.ts +43 -1
  319. package/src/tools/host-filesystem/read.test.ts +129 -0
  320. package/src/tools/host-filesystem/read.ts +43 -1
  321. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  322. package/src/tools/host-filesystem/transfer.ts +56 -11
  323. package/src/tools/host-filesystem/write.test.ts +134 -0
  324. package/src/tools/host-filesystem/write.ts +43 -1
  325. package/src/tools/host-terminal/host-shell.ts +13 -6
  326. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  327. package/src/tools/memory/register.test.ts +12 -9
  328. package/src/tools/memory/register.ts +1 -2
  329. package/src/tools/provider-tool-name.ts +28 -0
  330. package/src/tools/registry.ts +30 -9
  331. package/src/tools/terminal/shell.ts +9 -1
  332. package/src/tools/tool-approval-handler.ts +31 -6
  333. package/src/tools/types.ts +24 -2
  334. package/src/tts/provider-catalog.ts +3 -5
  335. package/src/util/disk-usage.ts +138 -0
  336. package/src/util/platform.ts +21 -11
  337. package/src/util/process-liveness.ts +26 -0
  338. package/src/workspace/heartbeat-service.ts +19 -0
  339. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  340. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  341. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  342. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  343. package/src/workspace/migrations/registry.ts +8 -0
  344. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  345. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  346. package/src/memory/v2/skill-qdrant.ts +0 -404
  347. package/src/signals/bash.ts +0 -198
@@ -0,0 +1,150 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ const MIB = 1024 * 1024;
4
+ const GIB = 1024 * MIB;
5
+
6
+ let existingPaths = new Set<string>();
7
+ const workspaceDir = "/workspace";
8
+ let minikubeStorageSize: string | undefined;
9
+ let statfsResult = {
10
+ bsize: 4096,
11
+ blocks: 0,
12
+ bavail: 0,
13
+ };
14
+ let spawnResult: {
15
+ status: number | null;
16
+ stdout: string;
17
+ } = {
18
+ status: 0,
19
+ stdout: "",
20
+ };
21
+ let spawnCalls: Array<{ command: string; args: string[] }> = [];
22
+
23
+ mock.module("node:fs", () => ({
24
+ existsSync: (path: string) => existingPaths.has(path),
25
+ statfsSync: () => statfsResult,
26
+ }));
27
+
28
+ mock.module("node:child_process", () => ({
29
+ spawnSync: (command: string, args: string[]) => {
30
+ spawnCalls.push({ command, args });
31
+ return spawnResult;
32
+ },
33
+ }));
34
+
35
+ mock.module("../config/env-registry.js", () => ({
36
+ getMinikubeStorageSize: () => minikubeStorageSize,
37
+ }));
38
+
39
+ mock.module("../util/platform.js", () => ({
40
+ getWorkspaceDir: () => workspaceDir,
41
+ }));
42
+
43
+ const { __resetDiskUsageCacheForTests, getDiskUsageInfo, parseK8sMemoryBytes } =
44
+ await import("../util/disk-usage.js");
45
+
46
+ function statfsFor(totalBytes: number, freeBytes: number) {
47
+ return {
48
+ bsize: MIB,
49
+ blocks: totalBytes / MIB,
50
+ bavail: freeBytes / MIB,
51
+ };
52
+ }
53
+
54
+ describe("disk usage sampler", () => {
55
+ beforeEach(() => {
56
+ existingPaths = new Set([workspaceDir]);
57
+ minikubeStorageSize = undefined;
58
+ statfsResult = statfsFor(100 * MIB, 25 * MIB);
59
+ spawnResult = { status: 0, stdout: "" };
60
+ spawnCalls = [];
61
+ __resetDiskUsageCacheForTests();
62
+ });
63
+
64
+ test("reports regular statfs usage", () => {
65
+ const usage = getDiskUsageInfo();
66
+
67
+ expect(usage).toEqual({
68
+ path: "/workspace",
69
+ totalMb: 100,
70
+ usedMb: 75,
71
+ freeMb: 25,
72
+ });
73
+ expect(spawnCalls).toHaveLength(0);
74
+ });
75
+
76
+ test("falls back to root when the workspace path does not exist", () => {
77
+ existingPaths = new Set();
78
+
79
+ const usage = getDiskUsageInfo();
80
+
81
+ expect(usage?.path).toBe("/");
82
+ });
83
+
84
+ test("uses PVC capacity and du usage when host filesystem is larger", () => {
85
+ minikubeStorageSize = "1Gi";
86
+ statfsResult = statfsFor(10 * GIB, 8 * GIB);
87
+ spawnResult = {
88
+ status: 0,
89
+ stdout: `${100 * MIB}\t/workspace\n`,
90
+ };
91
+
92
+ const usage = getDiskUsageInfo();
93
+
94
+ expect(usage).toEqual({
95
+ path: "/workspace",
96
+ totalMb: 1024,
97
+ usedMb: 100,
98
+ freeMb: 924,
99
+ });
100
+ expect(spawnCalls).toEqual([
101
+ { command: "du", args: ["-sb", "/workspace"] },
102
+ ]);
103
+ });
104
+
105
+ test("includes /data in PVC du usage when it exists separately", () => {
106
+ existingPaths = new Set([workspaceDir, "/data"]);
107
+ minikubeStorageSize = "1Gi";
108
+ statfsResult = statfsFor(10 * GIB, 8 * GIB);
109
+ spawnResult = {
110
+ status: 0,
111
+ stdout: `${100 * MIB}\t/workspace\n${20 * MIB}\t/data\n`,
112
+ };
113
+
114
+ const usage = getDiskUsageInfo();
115
+
116
+ expect(usage?.usedMb).toBe(120);
117
+ expect(spawnCalls).toEqual([
118
+ { command: "du", args: ["-sb", "/workspace", "/data"] },
119
+ ]);
120
+ });
121
+
122
+ test("returns null for malformed Kubernetes memory strings", () => {
123
+ expect(parseK8sMemoryBytes("")).toBeNull();
124
+ expect(parseK8sMemoryBytes("abc")).toBeNull();
125
+ expect(parseK8sMemoryBytes("12Zi")).toBeNull();
126
+ expect(parseK8sMemoryBytes("-1Gi")).toBeNull();
127
+ expect(parseK8sMemoryBytes("0Gi")).toBeNull();
128
+ });
129
+
130
+ test("falls back to statfs when du fails in PVC mode", () => {
131
+ minikubeStorageSize = "1Gi";
132
+ statfsResult = statfsFor(10 * GIB, 8 * GIB);
133
+ spawnResult = {
134
+ status: 1,
135
+ stdout: "",
136
+ };
137
+
138
+ const usage = getDiskUsageInfo();
139
+
140
+ expect(usage).toEqual({
141
+ path: "/workspace",
142
+ totalMb: 10240,
143
+ usedMb: 2048,
144
+ freeMb: 8192,
145
+ });
146
+ expect(spawnCalls).toEqual([
147
+ { command: "du", args: ["-sb", "/workspace"] },
148
+ ]);
149
+ });
150
+ });
@@ -395,6 +395,58 @@ describe("events client registration", () => {
395
395
  ac2.abort();
396
396
  });
397
397
 
398
+ // ── actorPrincipalId capture ──────────────────────────────────────────────
399
+
400
+ test("captures actorPrincipalId from x-vellum-actor-principal-id header", () => {
401
+ const ac = new AbortController();
402
+ const hub = new AssistantEventHub();
403
+
404
+ handleSubscribeAssistantEvents(
405
+ {
406
+ headers: {
407
+ "x-vellum-client-id": "principal-client-001",
408
+ "x-vellum-interface-id": "macos",
409
+ "x-vellum-actor-principal-id": "user-A",
410
+ },
411
+ abortSignal: ac.signal,
412
+ },
413
+ { hub },
414
+ );
415
+
416
+ const entry = hub.getClientById("principal-client-001");
417
+ expect(entry?.actorPrincipalId).toBe("user-A");
418
+ expect(hub.getActorPrincipalIdForClient("principal-client-001")).toBe(
419
+ "user-A",
420
+ );
421
+
422
+ ac.abort();
423
+ });
424
+
425
+ test("registers client with undefined actorPrincipalId when header is absent", () => {
426
+ const ac = new AbortController();
427
+ const hub = new AssistantEventHub();
428
+
429
+ handleSubscribeAssistantEvents(
430
+ {
431
+ headers: {
432
+ "x-vellum-client-id": "principal-client-002",
433
+ "x-vellum-interface-id": "macos",
434
+ },
435
+ abortSignal: ac.signal,
436
+ },
437
+ { hub },
438
+ );
439
+
440
+ const entry = hub.getClientById("principal-client-002");
441
+ expect(entry).toBeDefined();
442
+ expect(entry?.actorPrincipalId).toBeUndefined();
443
+ expect(
444
+ hub.getActorPrincipalIdForClient("principal-client-002"),
445
+ ).toBeUndefined();
446
+
447
+ ac.abort();
448
+ });
449
+
398
450
  // ── disposeClient (force disconnect) ──────────────────────────────────────
399
451
 
400
452
  test("disposeClient removes all subscribers for the clientId", () => {
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Regression tests for the SSE registration dev-bypass actor principal
3
+ * translation.
4
+ *
5
+ * In `DISABLE_HTTP_AUTH=true` (platform-managed) deployments the synthetic
6
+ * dev-bypass `AuthContext` injects `actorPrincipalId="dev-bypass"` for every
7
+ * request. Tool-side trust resolution still resolves to the real local
8
+ * guardian's principalId via `resolveLocalTrustContext`. Without translation,
9
+ * `ClientEntry.actorPrincipalId === "dev-bypass"` and
10
+ * `ToolContext.sourceActorPrincipalId === "<real-guardian>"` mismatch, so the
11
+ * same-user check in HostBashProxy / HostFileProxy / HostCuProxy /
12
+ * conversation-surfaces rejects every targeted host proxy invocation and the
13
+ * auto-resolve path silently falls through to untargeted broadcast.
14
+ *
15
+ * The events-routes handler translates `"dev-bypass"` to the real guardian's
16
+ * principalId at registration time so both sides agree. This keeps targeted
17
+ * host proxy execution working on Docker / platform-managed deployments.
18
+ */
19
+ import { afterAll, describe, expect, mock, test } from "bun:test";
20
+
21
+ // ── Module mocks (must be set up before importing the route) ──────────────
22
+
23
+ let fakeHttpAuthDisabled = false;
24
+ let fakeGuardianPrincipalId: string | undefined = undefined;
25
+
26
+ mock.module("../config/env.js", () => ({
27
+ isHttpAuthDisabled: () => fakeHttpAuthDisabled,
28
+ hasUngatedHttpAuthDisabled: () => false,
29
+ }));
30
+
31
+ mock.module("../runtime/local-actor-identity.js", () => ({
32
+ findLocalGuardianPrincipalId: () => fakeGuardianPrincipalId,
33
+ }));
34
+
35
+ mock.module("../util/logger.js", () => ({
36
+ getLogger: () =>
37
+ new Proxy({} as Record<string, unknown>, {
38
+ get: () => () => {},
39
+ }),
40
+ }));
41
+
42
+ // ── Real imports (after mocks) ────────────────────────────────────────────
43
+
44
+ import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
45
+ import { handleSubscribeAssistantEvents } from "../runtime/routes/events-routes.js";
46
+
47
+ afterAll(() => {
48
+ mock.restore();
49
+ });
50
+
51
+ describe("events SSE registration — dev-bypass actor translation", () => {
52
+ test("translates 'dev-bypass' to the real guardian principalId when auth is disabled", () => {
53
+ fakeHttpAuthDisabled = true;
54
+ fakeGuardianPrincipalId = "guardian-real-id";
55
+
56
+ const ac = new AbortController();
57
+ const hub = new AssistantEventHub();
58
+
59
+ handleSubscribeAssistantEvents(
60
+ {
61
+ headers: {
62
+ "x-vellum-client-id": "devbypass-client-001",
63
+ "x-vellum-interface-id": "macos",
64
+ "x-vellum-actor-principal-id": "dev-bypass",
65
+ },
66
+ abortSignal: ac.signal,
67
+ },
68
+ { hub },
69
+ );
70
+
71
+ const entry = hub.getClientById("devbypass-client-001");
72
+ expect(entry?.actorPrincipalId).toBe("guardian-real-id");
73
+ expect(hub.getActorPrincipalIdForClient("devbypass-client-001")).toBe(
74
+ "guardian-real-id",
75
+ );
76
+
77
+ ac.abort();
78
+ });
79
+
80
+ test("registers without principalId when dev-bypass is set but no guardian is bound", () => {
81
+ fakeHttpAuthDisabled = true;
82
+ fakeGuardianPrincipalId = undefined;
83
+
84
+ const ac = new AbortController();
85
+ const hub = new AssistantEventHub();
86
+
87
+ handleSubscribeAssistantEvents(
88
+ {
89
+ headers: {
90
+ "x-vellum-client-id": "devbypass-client-002",
91
+ "x-vellum-interface-id": "macos",
92
+ "x-vellum-actor-principal-id": "dev-bypass",
93
+ },
94
+ abortSignal: ac.signal,
95
+ },
96
+ { hub },
97
+ );
98
+
99
+ const entry = hub.getClientById("devbypass-client-002");
100
+ expect(entry).toBeDefined();
101
+ expect(entry?.actorPrincipalId).toBeUndefined();
102
+
103
+ ac.abort();
104
+ });
105
+
106
+ test("does NOT translate when auth is enabled (production mode)", () => {
107
+ // Defense in depth: make sure we never silently rewrite a real
108
+ // principalId that legitimately happens to be the literal "dev-bypass"
109
+ // string in a non-dev-bypass deployment. The translation is gated on
110
+ // isHttpAuthDisabled() === true.
111
+ fakeHttpAuthDisabled = false;
112
+ fakeGuardianPrincipalId = "guardian-real-id";
113
+
114
+ const ac = new AbortController();
115
+ const hub = new AssistantEventHub();
116
+
117
+ handleSubscribeAssistantEvents(
118
+ {
119
+ headers: {
120
+ "x-vellum-client-id": "prod-client-003",
121
+ "x-vellum-interface-id": "macos",
122
+ "x-vellum-actor-principal-id": "dev-bypass",
123
+ },
124
+ abortSignal: ac.signal,
125
+ },
126
+ { hub },
127
+ );
128
+
129
+ const entry = hub.getClientById("prod-client-003");
130
+ expect(entry?.actorPrincipalId).toBe("dev-bypass");
131
+
132
+ ac.abort();
133
+ });
134
+
135
+ test("passes through non-dev-bypass principalId verbatim in dev-bypass mode", () => {
136
+ // Edge case: a service-token connection that happens to be made while
137
+ // the daemon runs in DISABLE_HTTP_AUTH=true mode should still register
138
+ // with its own principalId, not the guardian's.
139
+ fakeHttpAuthDisabled = true;
140
+ fakeGuardianPrincipalId = "guardian-real-id";
141
+
142
+ const ac = new AbortController();
143
+ const hub = new AssistantEventHub();
144
+
145
+ handleSubscribeAssistantEvents(
146
+ {
147
+ headers: {
148
+ "x-vellum-client-id": "service-client-004",
149
+ "x-vellum-interface-id": "macos",
150
+ "x-vellum-actor-principal-id": "service-account-A",
151
+ },
152
+ abortSignal: ac.signal,
153
+ },
154
+ { hub },
155
+ );
156
+
157
+ const entry = hub.getClientById("service-client-004");
158
+ expect(entry?.actorPrincipalId).toBe("service-account-A");
159
+
160
+ ac.abort();
161
+ });
162
+ });
@@ -197,26 +197,20 @@ describe("file_write tool PKB re-index hook", () => {
197
197
  });
198
198
  });
199
199
 
200
- test("always uses PKB_WORKSPACE_SCOPE regardless of context.memoryScopeId", async () => {
200
+ test("always uses PKB_WORKSPACE_SCOPE", async () => {
201
201
  const workingDir = makeTempDir();
202
202
  setWorkspaceDir(workingDir);
203
203
  mkdirSync(join(workingDir, "pkb"), { recursive: true });
204
204
 
205
- const ctx: ToolContext = {
206
- ...makeContext(workingDir),
207
- memoryScopeId: "subagent:abc123",
208
- };
209
-
210
205
  const result = await fileWriteTool.execute(
211
206
  { path: "pkb/private.md", content: "secret\n" },
212
- ctx,
207
+ makeContext(workingDir),
213
208
  );
214
209
 
215
210
  expect(result.isError).toBe(false);
216
211
  expect(enqueueCalls).toHaveLength(1);
217
- // PKB files are workspace-level — the per-conversation scopeId is NOT
218
- // threaded through so different conversations can't overwrite each
219
- // other's Qdrant points via target_id upsert deduplication.
212
+ // PKB files are workspace-level — points are keyed by PKB_WORKSPACE_SCOPE
213
+ // so all conversations share one PKB index.
220
214
  expect(enqueueCalls[0]?.memoryScopeId).toBe(PKB_WORKSPACE_SCOPE);
221
215
  });
222
216
 
@@ -342,16 +342,15 @@ describe("FilingService", () => {
342
342
  expect(service.nextCompactionAt).toBeNull();
343
343
  });
344
344
 
345
- test("start() schedules timers when only the flag is on", () => {
345
+ test("start() does not schedule timers when only the flag is on", () => {
346
346
  _setOverridesForTesting({ "memory-v2-enabled": true });
347
347
  mockConfig.memory.v2.enabled = false;
348
348
 
349
349
  const service = createService();
350
350
  service.start();
351
351
 
352
- expect(service.nextRunAt).not.toBeNull();
353
- expect(service.nextCompactionAt).not.toBeNull();
354
- service.stop();
352
+ expect(service.nextRunAt).toBeNull();
353
+ expect(service.nextCompactionAt).toBeNull();
355
354
  });
356
355
 
357
356
  test("start() schedules timers when only the config is on", () => {
@@ -0,0 +1,183 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ mock.module("../util/logger.js", () => ({
4
+ getLogger: () => ({
5
+ info: () => {},
6
+ debug: () => {},
7
+ warn: () => {},
8
+ error: () => {},
9
+ }),
10
+ }));
11
+
12
+ mock.module("../config/loader.js", () => ({
13
+ getConfig: () => ({
14
+ heartbeat: {
15
+ enabled: true,
16
+ intervalMs: 60_000,
17
+ cronExpression: null,
18
+ timezone: null,
19
+ activeHoursStart: undefined,
20
+ activeHoursEnd: undefined,
21
+ },
22
+ }),
23
+ loadConfig: () => ({}),
24
+ loadRawConfig: () => ({}),
25
+ saveRawConfig: () => {},
26
+ getConfigReadOnly: () => ({}),
27
+ applyNestedDefaults: (config: unknown) => config,
28
+ deepMergeOverwrite: (base: unknown) => base,
29
+ mergeDefaultWorkspaceConfig: () => {},
30
+ getNestedValue: () => undefined,
31
+ setNestedValue: () => {},
32
+ API_KEY_PROVIDERS: [],
33
+ _appendQuarantineBulletin: () => {},
34
+ invalidateConfigCache: () => {},
35
+ }));
36
+
37
+ const mockInsertPendingHeartbeatRun = mock(() => "run-1");
38
+ const mockStartHeartbeatRun = mock(() => true);
39
+ const mockCompleteHeartbeatRun = mock(() => true);
40
+ const mockSkipHeartbeatRun = mock(() => true);
41
+ const mockMarkStaleRunsAsMissed = mock(() => 0);
42
+ const mockMarkStaleRunningAsError = mock(() => 0);
43
+ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
44
+ insertPendingHeartbeatRun: mockInsertPendingHeartbeatRun,
45
+ startHeartbeatRun: mockStartHeartbeatRun,
46
+ completeHeartbeatRun: mockCompleteHeartbeatRun,
47
+ skipHeartbeatRun: mockSkipHeartbeatRun,
48
+ supersedePendingRun: mock(() => true),
49
+ markStaleRunsAsMissed: mockMarkStaleRunsAsMissed,
50
+ markStaleRunningAsError: mockMarkStaleRunningAsError,
51
+ countCompletedHeartbeatRuns: mock(() => 10),
52
+ }));
53
+
54
+ mock.module("../schedule/recurrence-engine.js", () => ({
55
+ computeNextRunAt: () => Date.now() + 60_000,
56
+ }));
57
+
58
+ const createdConversations: Array<{ conversationType: string }> = [];
59
+ mock.module("../memory/conversation-crud.js", () => ({
60
+ getConversation: () => null,
61
+ getMessages: () => [],
62
+ createConversation: (opts: { conversationType: string }) => {
63
+ createdConversations.push(opts);
64
+ return { id: "conv-1", ...opts };
65
+ },
66
+ }));
67
+
68
+ const mockProcessMessage = mock(() => Promise.resolve({ messageId: "msg-1" }));
69
+ mock.module("../daemon/process-message.js", () => ({
70
+ processMessage: mockProcessMessage,
71
+ }));
72
+
73
+ const mockEmitFeedEvent = mock(() => Promise.resolve());
74
+ mock.module("../home/emit-feed-event.js", () => ({
75
+ emitFeedEvent: mockEmitFeedEvent,
76
+ }));
77
+
78
+ mock.module("../prompts/persona-resolver.js", () => ({
79
+ GUARDIAN_PERSONA_TEMPLATE: "# User Profile\n",
80
+ resolveGuardianPersona: () => "# User Profile\n",
81
+ }));
82
+
83
+ mock.module("../memory/conversation-title-service.js", () => ({
84
+ GENERATING_TITLE: "Generating title...",
85
+ queueGenerateConversationTitle: () => {},
86
+ }));
87
+
88
+ mock.module("../daemon/disk-pressure-background-gate.js", () => ({
89
+ checkDiskPressureBackgroundGate: () => ({
90
+ action: "skip",
91
+ reason: "disk_pressure",
92
+ blockedCapability: "background-work",
93
+ status: {
94
+ enabled: true,
95
+ state: "critical",
96
+ locked: true,
97
+ acknowledged: true,
98
+ overrideActive: false,
99
+ effectivelyLocked: true,
100
+ lockId: "disk-pressure-test",
101
+ usagePercent: 98,
102
+ thresholdPercent: 95,
103
+ path: "/",
104
+ lastCheckedAt: "2026-05-05T00:00:00.000Z",
105
+ blockedCapabilities: ["agent-turns", "background-work", "remote-ingress"],
106
+ error: null,
107
+ },
108
+ }),
109
+ diskPressureBackgroundSkipLogFields: () => ({
110
+ reason: "disk_pressure",
111
+ thresholdPercent: 95,
112
+ usagePercent: 98,
113
+ blockedCapability: "background-work",
114
+ lockId: "disk-pressure-test",
115
+ path: "/",
116
+ }),
117
+ shouldLogDiskPressureBackgroundSkip: () => true,
118
+ }));
119
+
120
+ const { HeartbeatService } = await import("../heartbeat/heartbeat-service.js");
121
+
122
+ describe("HeartbeatService disk pressure gate", () => {
123
+ beforeEach(() => {
124
+ createdConversations.length = 0;
125
+ mockInsertPendingHeartbeatRun.mockClear();
126
+ mockStartHeartbeatRun.mockClear();
127
+ mockCompleteHeartbeatRun.mockClear();
128
+ mockSkipHeartbeatRun.mockClear();
129
+ mockMarkStaleRunsAsMissed.mockClear();
130
+ mockMarkStaleRunsAsMissed.mockImplementation(() => 0);
131
+ mockMarkStaleRunningAsError.mockClear();
132
+ mockMarkStaleRunningAsError.mockImplementation(() => 0);
133
+ mockProcessMessage.mockClear();
134
+ mockEmitFeedEvent.mockClear();
135
+ });
136
+
137
+ test("skips without creating heartbeat rows, conversations, or feed events", async () => {
138
+ const service = new HeartbeatService({
139
+ alerter: () => {},
140
+ });
141
+
142
+ const ran = await service.runOnce();
143
+
144
+ expect(ran).toBe(false);
145
+ expect(mockInsertPendingHeartbeatRun).not.toHaveBeenCalled();
146
+ expect(mockStartHeartbeatRun).not.toHaveBeenCalled();
147
+ expect(mockCompleteHeartbeatRun).not.toHaveBeenCalled();
148
+ expect(mockSkipHeartbeatRun).not.toHaveBeenCalled();
149
+ expect(createdConversations).toHaveLength(0);
150
+ expect(mockProcessMessage).not.toHaveBeenCalled();
151
+ expect(mockEmitFeedEvent).not.toHaveBeenCalled();
152
+ });
153
+
154
+ test("allows forced user-initiated heartbeat runs while locked", async () => {
155
+ const service = new HeartbeatService({
156
+ alerter: () => {},
157
+ });
158
+
159
+ const ran = await service.runOnce({ force: true });
160
+
161
+ expect(ran).toBe(true);
162
+ expect(mockStartHeartbeatRun).toHaveBeenCalled();
163
+ expect(mockCompleteHeartbeatRun).toHaveBeenCalledWith(
164
+ expect.any(String),
165
+ expect.objectContaining({ status: "ok" }),
166
+ );
167
+ expect(createdConversations).toHaveLength(1);
168
+ expect(mockProcessMessage).toHaveBeenCalledTimes(1);
169
+ });
170
+
171
+ test("start recovery skips missed-run feed events while locked", async () => {
172
+ mockMarkStaleRunsAsMissed.mockImplementationOnce(() => 1);
173
+ const service = new HeartbeatService({
174
+ alerter: () => {},
175
+ });
176
+
177
+ service.start();
178
+ await service.stop();
179
+
180
+ expect(mockMarkStaleRunsAsMissed).toHaveBeenCalledTimes(1);
181
+ expect(mockEmitFeedEvent).not.toHaveBeenCalled();
182
+ });
183
+ });