@elizaos/ui 2.0.0-alpha.192 → 2.0.0-alpha.196

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 (273) hide show
  1. package/apps/app-companion/src/components/companion/companion-app.js +2 -2
  2. package/apps/app-lifeops/src/action.d.ts.map +1 -1
  3. package/apps/app-lifeops/src/action.js +21 -2
  4. package/apps/app-lifeops/src/actions/app-blocker.d.ts +3 -1
  5. package/apps/app-lifeops/src/actions/app-blocker.d.ts.map +1 -1
  6. package/apps/app-lifeops/src/actions/app-blocker.js +2 -0
  7. package/apps/app-lifeops/src/actions/calendar.d.ts.map +1 -1
  8. package/apps/app-lifeops/src/actions/calendar.js +140 -7
  9. package/apps/app-lifeops/src/actions/computer-use.d.ts.map +1 -1
  10. package/apps/app-lifeops/src/actions/computer-use.js +248 -17
  11. package/apps/app-lifeops/src/actions/cross-channel-send.d.ts +3 -1
  12. package/apps/app-lifeops/src/actions/cross-channel-send.d.ts.map +1 -1
  13. package/apps/app-lifeops/src/actions/cross-channel-send.js +1 -0
  14. package/apps/app-lifeops/src/actions/dossier.d.ts.map +1 -1
  15. package/apps/app-lifeops/src/actions/dossier.js +51 -34
  16. package/apps/app-lifeops/src/actions/gmail.d.ts.map +1 -1
  17. package/apps/app-lifeops/src/actions/gmail.js +5 -0
  18. package/apps/app-lifeops/src/actions/inbox.d.ts.map +1 -1
  19. package/apps/app-lifeops/src/actions/inbox.js +8 -1
  20. package/apps/app-lifeops/src/actions/life.d.ts.map +1 -1
  21. package/apps/app-lifeops/src/actions/life.extractor.d.ts.map +1 -1
  22. package/apps/app-lifeops/src/actions/life.extractor.js +103 -4
  23. package/apps/app-lifeops/src/actions/life.js +8 -0
  24. package/apps/app-lifeops/src/actions/non-actionable-request.d.ts +6 -0
  25. package/apps/app-lifeops/src/actions/non-actionable-request.d.ts.map +1 -0
  26. package/apps/app-lifeops/src/actions/non-actionable-request.js +33 -0
  27. package/apps/app-lifeops/src/actions/password-manager.d.ts.map +1 -1
  28. package/apps/app-lifeops/src/actions/password-manager.js +11 -2
  29. package/apps/app-lifeops/src/actions/relationships.d.ts +3 -1
  30. package/apps/app-lifeops/src/actions/relationships.d.ts.map +1 -1
  31. package/apps/app-lifeops/src/actions/relationships.js +167 -11
  32. package/apps/app-lifeops/src/actions/remote-desktop.js +1 -1
  33. package/apps/app-lifeops/src/actions/screen-time.d.ts.map +1 -1
  34. package/apps/app-lifeops/src/actions/screen-time.js +8 -2
  35. package/apps/app-lifeops/src/actions/start-remote-session.js +1 -1
  36. package/apps/app-lifeops/src/actions/subscriptions.d.ts +5 -0
  37. package/apps/app-lifeops/src/actions/subscriptions.d.ts.map +1 -0
  38. package/apps/app-lifeops/src/actions/subscriptions.js +251 -0
  39. package/apps/app-lifeops/src/actions/twilio-call.d.ts.map +1 -1
  40. package/apps/app-lifeops/src/actions/twilio-call.js +48 -3
  41. package/apps/app-lifeops/src/actions/update-owner-profile.d.ts.map +1 -1
  42. package/apps/app-lifeops/src/actions/update-owner-profile.js +2 -1
  43. package/apps/app-lifeops/src/actions/website-blocker.d.ts +6 -2
  44. package/apps/app-lifeops/src/actions/website-blocker.d.ts.map +1 -1
  45. package/apps/app-lifeops/src/actions/website-blocker.js +38 -0
  46. package/apps/app-lifeops/src/actions/x-read.d.ts.map +1 -1
  47. package/apps/app-lifeops/src/actions/x-read.js +27 -3
  48. package/apps/app-lifeops/src/dossier/action.d.ts.map +1 -1
  49. package/apps/app-lifeops/src/dossier/action.js +8 -7
  50. package/apps/app-lifeops/src/followup/actions/listOverdueFollowups.d.ts.map +1 -1
  51. package/apps/app-lifeops/src/followup/actions/listOverdueFollowups.js +1 -0
  52. package/apps/app-lifeops/src/followup/actions/markFollowupDone.d.ts.map +1 -1
  53. package/apps/app-lifeops/src/followup/actions/markFollowupDone.js +2 -1
  54. package/apps/app-lifeops/src/followup/actions/setFollowupThreshold.d.ts.map +1 -1
  55. package/apps/app-lifeops/src/followup/actions/setFollowupThreshold.js +2 -1
  56. package/apps/app-lifeops/src/lifeops/calendly-client.d.ts.map +1 -1
  57. package/apps/app-lifeops/src/lifeops/calendly-client.js +4 -2
  58. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.d.ts.map +1 -1
  59. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.js +6 -5
  60. package/apps/app-lifeops/src/lifeops/password-manager-bridge.d.ts +1 -1
  61. package/apps/app-lifeops/src/lifeops/password-manager-bridge.d.ts.map +1 -1
  62. package/apps/app-lifeops/src/lifeops/password-manager-bridge.js +84 -6
  63. package/apps/app-lifeops/src/lifeops/remote-desktop.d.ts.map +1 -1
  64. package/apps/app-lifeops/src/lifeops/remote-desktop.js +30 -0
  65. package/apps/app-lifeops/src/lifeops/repository.d.ts +15 -0
  66. package/apps/app-lifeops/src/lifeops/repository.d.ts.map +1 -1
  67. package/apps/app-lifeops/src/lifeops/repository.js +330 -1
  68. package/apps/app-lifeops/src/lifeops/service-mixin-discord.js +222 -42
  69. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.d.ts +2 -0
  70. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.d.ts.map +1 -0
  71. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.js +763 -0
  72. package/apps/app-lifeops/src/lifeops/service.d.ts +64 -1
  73. package/apps/app-lifeops/src/lifeops/service.d.ts.map +1 -1
  74. package/apps/app-lifeops/src/lifeops/service.js +2 -1
  75. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.d.ts +54 -0
  76. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.d.ts.map +1 -0
  77. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.js +213 -0
  78. package/apps/app-lifeops/src/lifeops/subscriptions-types.d.ts +80 -0
  79. package/apps/app-lifeops/src/lifeops/subscriptions-types.d.ts.map +1 -0
  80. package/apps/app-lifeops/src/lifeops/twilio.d.ts.map +1 -1
  81. package/apps/app-lifeops/src/lifeops/twilio.js +4 -2
  82. package/apps/app-lifeops/src/lifeops/whatsapp-client.d.ts.map +1 -1
  83. package/apps/app-lifeops/src/lifeops/whatsapp-client.js +4 -2
  84. package/apps/app-lifeops/src/lifeops/x-poster.d.ts.map +1 -1
  85. package/apps/app-lifeops/src/lifeops/x-poster.js +12 -6
  86. package/apps/app-lifeops/src/lifeops/x-reader.d.ts.map +1 -1
  87. package/apps/app-lifeops/src/lifeops/x-reader.js +7 -5
  88. package/apps/app-lifeops/src/plugin.d.ts.map +1 -1
  89. package/apps/app-lifeops/src/plugin.js +31 -24
  90. package/apps/app-lifeops/src/provider.d.ts.map +1 -1
  91. package/apps/app-lifeops/src/provider.js +4 -4
  92. package/apps/app-lifeops/src/providers/inbox-triage.js +2 -2
  93. package/apps/app-lifeops/src/providers/lifeops.d.ts.map +1 -1
  94. package/apps/app-lifeops/src/providers/lifeops.js +19 -10
  95. package/apps/app-lifeops/src/providers/website-blocker.js +1 -1
  96. package/apps/app-lifeops/src/public.d.ts +4 -0
  97. package/apps/app-lifeops/src/public.d.ts.map +1 -0
  98. package/apps/app-lifeops/src/public.js +3 -0
  99. package/apps/app-lifeops/src/routes/plugin.d.ts +18 -0
  100. package/apps/app-lifeops/src/routes/plugin.d.ts.map +1 -0
  101. package/apps/app-lifeops/src/routes/plugin.js +270 -0
  102. package/apps/app-lifeops/src/website-blocker/chat-integration/actions/blockUntilTaskComplete.d.ts.map +1 -1
  103. package/apps/app-lifeops/src/website-blocker/chat-integration/actions/blockUntilTaskComplete.js +18 -2
  104. package/package.json +1 -1
  105. package/packages/agent/src/actions/browser-session.d.ts +3 -0
  106. package/packages/agent/src/actions/browser-session.d.ts.map +1 -0
  107. package/packages/agent/src/actions/browser-session.js +207 -0
  108. package/packages/agent/src/actions/context-signal.d.ts +1 -1
  109. package/packages/agent/src/actions/context-signal.d.ts.map +1 -1
  110. package/packages/agent/src/actions/context-signal.js +1 -1
  111. package/packages/agent/src/actions/extract-page.d.ts +3 -0
  112. package/packages/agent/src/actions/extract-page.d.ts.map +1 -0
  113. package/packages/agent/src/actions/extract-page.js +124 -0
  114. package/packages/agent/src/actions/grounded-action-reply.js +1 -1
  115. package/packages/agent/src/actions/recent-conversation-texts.d.ts +9 -0
  116. package/packages/agent/src/actions/recent-conversation-texts.d.ts.map +1 -0
  117. package/packages/agent/src/actions/recent-conversation-texts.js +81 -0
  118. package/packages/agent/src/actions/set-user-name.js +1 -1
  119. package/packages/agent/src/actions/web-search.d.ts +4 -10
  120. package/packages/agent/src/actions/web-search.d.ts.map +1 -1
  121. package/packages/agent/src/actions/web-search.js +89 -19
  122. package/packages/agent/src/api/binance-skill-helpers.d.ts.map +1 -1
  123. package/packages/agent/src/api/binance-skill-helpers.js +1 -1
  124. package/packages/agent/src/api/chat-augmentation.d.ts +1 -20
  125. package/packages/agent/src/api/chat-augmentation.d.ts.map +1 -1
  126. package/packages/agent/src/api/chat-augmentation.js +2 -110
  127. package/packages/agent/src/api/conversation-metadata.d.ts +9 -0
  128. package/packages/agent/src/api/conversation-metadata.d.ts.map +1 -0
  129. package/packages/agent/src/api/conversation-metadata.js +98 -0
  130. package/packages/agent/src/api/conversation-routes.d.ts.map +1 -1
  131. package/packages/agent/src/api/conversation-routes.js +40 -9
  132. package/packages/agent/src/api/permissions-routes.js +1 -1
  133. package/packages/agent/src/api/server-helpers-auth.js +1 -1
  134. package/packages/agent/src/api/server-helpers.d.ts +2 -1
  135. package/packages/agent/src/api/server-helpers.d.ts.map +1 -1
  136. package/packages/agent/src/api/server-helpers.js +2 -110
  137. package/packages/agent/src/api/server-types.d.ts +14 -0
  138. package/packages/agent/src/api/server-types.d.ts.map +1 -1
  139. package/packages/agent/src/api/server.d.ts.map +1 -1
  140. package/packages/agent/src/api/server.js +3 -13
  141. package/packages/agent/src/api/workbench-routes.d.ts.map +1 -1
  142. package/packages/agent/src/api/workbench-routes.js +0 -14
  143. package/packages/agent/src/auth/credentials.d.ts.map +1 -1
  144. package/packages/agent/src/auth/credentials.js +8 -0
  145. package/packages/agent/src/providers/automation-terminal-bridge.d.ts +3 -0
  146. package/packages/agent/src/providers/automation-terminal-bridge.d.ts.map +1 -0
  147. package/packages/agent/src/providers/automation-terminal-bridge.js +71 -0
  148. package/packages/agent/src/providers/recent-conversations.d.ts.map +1 -1
  149. package/packages/agent/src/providers/recent-conversations.js +5 -0
  150. package/packages/agent/src/providers/relevant-conversations.d.ts.map +1 -1
  151. package/packages/agent/src/providers/relevant-conversations.js +5 -0
  152. package/packages/agent/src/providers/user-name.d.ts.map +1 -1
  153. package/packages/agent/src/providers/user-name.js +1 -1
  154. package/packages/agent/src/runtime/eliza-plugin.d.ts.map +1 -1
  155. package/packages/agent/src/runtime/eliza-plugin.js +6 -0
  156. package/packages/agent/src/runtime/plugin-resolver.d.ts.map +1 -1
  157. package/packages/agent/src/runtime/plugin-resolver.js +115 -0
  158. package/packages/agent/src/services/browser-workspace-types.d.ts +5 -1
  159. package/packages/agent/src/services/browser-workspace-types.d.ts.map +1 -1
  160. package/packages/agent/src/services/browser-workspace.d.ts.map +1 -1
  161. package/packages/agent/src/services/browser-workspace.js +178 -7
  162. package/packages/agent/src/services/escalation.d.ts +0 -11
  163. package/packages/agent/src/services/escalation.d.ts.map +1 -1
  164. package/packages/agent/src/services/escalation.js +19 -58
  165. package/packages/agent/src/services/hosted-tools.d.ts +70 -0
  166. package/packages/agent/src/services/hosted-tools.d.ts.map +1 -0
  167. package/packages/agent/src/services/hosted-tools.js +87 -0
  168. package/packages/agent/src/services/owner-name.d.ts +4 -0
  169. package/packages/agent/src/services/owner-name.d.ts.map +1 -0
  170. package/packages/agent/src/services/owner-name.js +46 -0
  171. package/packages/agent/src/services/registry-client-queries.d.ts +1 -1
  172. package/packages/agent/src/services/registry-client-queries.js +1 -1
  173. package/packages/agent/src/services/registry-client-types.d.ts +1 -1
  174. package/packages/app-core/src/api/client-automations.d.ts +8 -0
  175. package/packages/app-core/src/api/client-automations.d.ts.map +1 -0
  176. package/packages/app-core/src/api/client-automations.js +7 -0
  177. package/packages/app-core/src/api/client-browser-workspace.d.ts +3 -0
  178. package/packages/app-core/src/api/client-browser-workspace.d.ts.map +1 -1
  179. package/packages/app-core/src/api/client-browser-workspace.js +3 -0
  180. package/packages/app-core/src/api/client-chat.d.ts +8 -2
  181. package/packages/app-core/src/api/client-chat.d.ts.map +1 -1
  182. package/packages/app-core/src/api/client-chat.js +14 -1
  183. package/packages/app-core/src/api/client-types-chat.d.ts +4 -0
  184. package/packages/app-core/src/api/client-types-chat.d.ts.map +1 -1
  185. package/packages/app-core/src/api/client-types-config.d.ts +67 -2
  186. package/packages/app-core/src/api/client-types-config.d.ts.map +1 -1
  187. package/packages/app-core/src/api/client.d.ts +1 -0
  188. package/packages/app-core/src/api/client.d.ts.map +1 -1
  189. package/packages/app-core/src/api/client.js +1 -0
  190. package/packages/app-core/src/components/apps/helpers.d.ts +1 -1
  191. package/packages/app-core/src/components/apps/helpers.d.ts.map +1 -1
  192. package/packages/app-core/src/components/apps/helpers.js +5 -9
  193. package/packages/app-core/src/components/apps/internal-tool-apps.d.ts.map +1 -1
  194. package/packages/app-core/src/components/apps/internal-tool-apps.js +10 -1
  195. package/packages/app-core/src/components/conversations/conversation-sidebar-model.d.ts.map +1 -1
  196. package/packages/app-core/src/components/conversations/conversation-sidebar-model.js +6 -0
  197. package/packages/app-core/src/components/pages/AutomationRoomChatPane.d.ts +16 -0
  198. package/packages/app-core/src/components/pages/AutomationRoomChatPane.d.ts.map +1 -0
  199. package/packages/app-core/src/components/pages/AutomationRoomChatPane.js +202 -0
  200. package/packages/app-core/src/components/pages/AutomationsView.d.ts +1 -4
  201. package/packages/app-core/src/components/pages/AutomationsView.d.ts.map +1 -1
  202. package/packages/app-core/src/components/pages/AutomationsView.js +565 -265
  203. package/packages/app-core/src/components/pages/BrowserWorkspaceView.d.ts.map +1 -1
  204. package/packages/app-core/src/components/pages/BrowserWorkspaceView.js +118 -24
  205. package/packages/app-core/src/components/pages/automation-conversations.d.ts +15 -0
  206. package/packages/app-core/src/components/pages/automation-conversations.d.ts.map +1 -0
  207. package/packages/app-core/src/components/pages/automation-conversations.js +190 -0
  208. package/packages/app-core/src/components/pages/plugin-list-utils.js +4 -4
  209. package/packages/app-core/src/config/boot-config-store.d.ts +1 -1
  210. package/packages/app-core/src/config/boot-config-store.d.ts.map +1 -1
  211. package/packages/app-core/src/state/useOnboardingCallbacks.d.ts +14 -0
  212. package/packages/app-core/src/state/useOnboardingCallbacks.d.ts.map +1 -1
  213. package/packages/app-core/src/state/useOnboardingCallbacks.js +1 -1
  214. package/packages/app-core/src/state/usePluginsSkillsState.d.ts +2 -2
  215. package/packages/app-core/src/utils/desktop-workspace.d.ts +2 -2
  216. package/packages/app-core/src/utils/desktop-workspace.d.ts.map +1 -1
  217. package/packages/app-core/src/utils/desktop-workspace.js +7 -2
  218. package/packages/shared/src/contracts/lifeops.d.ts +26 -0
  219. package/packages/shared/src/contracts/lifeops.d.ts.map +1 -1
  220. package/packages/shared/src/contracts/lifeops.js +28 -0
  221. package/packages/typescript/src/actions.d.ts.map +1 -1
  222. package/packages/typescript/src/actions.js +12 -0
  223. package/packages/typescript/src/features/advanced-capabilities/actions/scheduleFollowUp.d.ts.map +1 -1
  224. package/packages/typescript/src/features/advanced-capabilities/actions/scheduleFollowUp.js +8 -2
  225. package/packages/typescript/src/features/advanced-capabilities/providers/settings.d.ts.map +1 -1
  226. package/packages/typescript/src/features/advanced-capabilities/providers/settings.js +11 -3
  227. package/packages/typescript/src/features/basic-capabilities/providers/actions.d.ts.map +1 -1
  228. package/packages/typescript/src/features/basic-capabilities/providers/actions.js +23 -1
  229. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.d.ts +5 -0
  230. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.d.ts.map +1 -0
  231. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.js +22 -0
  232. package/packages/typescript/src/features/basic-capabilities/providers/providers.d.ts.map +1 -1
  233. package/packages/typescript/src/features/basic-capabilities/providers/providers.js +10 -5
  234. package/packages/typescript/src/features/knowledge/documents-provider.d.ts.map +1 -1
  235. package/packages/typescript/src/features/knowledge/documents-provider.js +1 -0
  236. package/packages/typescript/src/features/shared/schedule-follow-up-response.d.ts +10 -0
  237. package/packages/typescript/src/features/shared/schedule-follow-up-response.d.ts.map +1 -0
  238. package/packages/typescript/src/features/shared/schedule-follow-up-response.js +49 -0
  239. package/packages/typescript/src/features/trajectories/TrajectoriesService.d.ts +1 -0
  240. package/packages/typescript/src/features/trajectories/TrajectoriesService.d.ts.map +1 -1
  241. package/packages/typescript/src/features/trajectories/TrajectoriesService.js +21 -2
  242. package/packages/typescript/src/features/trust/providers/settings.d.ts.map +1 -1
  243. package/packages/typescript/src/features/trust/providers/settings.js +11 -1
  244. package/packages/typescript/src/prompts.d.ts +4 -4
  245. package/packages/typescript/src/prompts.d.ts.map +1 -1
  246. package/packages/typescript/src/prompts.js +10 -2
  247. package/packages/typescript/src/services/message.d.ts +1 -0
  248. package/packages/typescript/src/services/message.d.ts.map +1 -1
  249. package/packages/typescript/src/services/message.js +483 -18
  250. package/packages/typescript/src/types/components.d.ts +6 -0
  251. package/packages/typescript/src/types/components.d.ts.map +1 -1
  252. package/packages/typescript/src/utils/context-routing.d.ts.map +1 -1
  253. package/packages/typescript/src/utils/context-routing.js +5 -1
  254. package/packages/typescript/src/utils/toon.d.ts.map +1 -1
  255. package/packages/typescript/src/utils/toon.js +23 -2
  256. package/packages/typescript/src/utils.d.ts +1 -0
  257. package/packages/typescript/src/utils.d.ts.map +1 -1
  258. package/packages/typescript/src/utils.js +1 -0
  259. package/apps/app-lifeops/src/types/app-blocker-settings-card.d.ts +0 -2
  260. package/apps/app-lifeops/src/types/app-blocker-settings-card.d.ts.map +0 -1
  261. package/apps/app-lifeops/src/types/index.d.ts +0 -3
  262. package/apps/app-lifeops/src/types/index.d.ts.map +0 -1
  263. package/apps/app-lifeops/src/types/index.js +0 -1
  264. package/apps/app-lifeops/src/types/website-blocker-settings-card.d.ts +0 -2
  265. package/apps/app-lifeops/src/types/website-blocker-settings-card.d.ts.map +0 -1
  266. package/apps/app-lifeops/src/types/website-blocker-settings-card.js +0 -1
  267. package/packages/agent/src/contracts/lifeops.d.ts +0 -2
  268. package/packages/agent/src/contracts/lifeops.d.ts.map +0 -1
  269. package/packages/agent/src/contracts/lifeops.js +0 -1
  270. package/packages/app-core/src/components/pages/N8nWorkflowsPanel.d.ts +0 -18
  271. package/packages/app-core/src/components/pages/N8nWorkflowsPanel.d.ts.map +0 -1
  272. package/packages/app-core/src/components/pages/N8nWorkflowsPanel.js +0 -542
  273. /package/apps/app-lifeops/src/{types/app-blocker-settings-card.js → lifeops/subscriptions-types.js} +0 -0
@@ -0,0 +1,763 @@
1
+ import { createLifeOpsSubscriptionAudit, createLifeOpsSubscriptionCancellation, createLifeOpsSubscriptionCandidate, } from "./repository.js";
2
+ import { fail, normalizeOptionalBoolean, normalizeOptionalString, requireNonEmptyString, } from "./service-normalize.js";
3
+ import { findLifeOpsSubscriptionPlaybook, listLifeOpsSubscriptionPlaybooks, } from "./subscriptions-playbooks.js";
4
+ const MAX_AUDIT_MESSAGES = 80;
5
+ const DEFAULT_AUDIT_WINDOW_DAYS = 180;
6
+ function normalizeSubscriptionLookup(value) {
7
+ return value
8
+ .trim()
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9]+/g, " ")
11
+ .replace(/\s+/g, " ")
12
+ .trim();
13
+ }
14
+ function slugifySubscriptionValue(value) {
15
+ return value
16
+ .trim()
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9]+/g, "_")
19
+ .replace(/^_+|_+$/g, "");
20
+ }
21
+ function guessCadence(message) {
22
+ const blob = `${message.subject} ${message.snippet}`.toLowerCase();
23
+ if (/\bannual\b|\byearly\b|\byear\b|\b12 month\b|\b12-month\b/.test(blob)) {
24
+ return "annual";
25
+ }
26
+ if (/\bmonth\b|\bmonthly\b|\brenewal\b|\bsubscription\b|\bbilling\b/.test(blob)) {
27
+ return "monthly";
28
+ }
29
+ return "unknown";
30
+ }
31
+ function guessState(message) {
32
+ const blob = `${message.subject} ${message.snippet}`.toLowerCase();
33
+ if (/\bcancelled\b|\bcanceled\b|\bended\b|\bexpires on\b|\bexpired\b/.test(blob)) {
34
+ return "canceled";
35
+ }
36
+ if (/\brenewal\b|\breceipt\b|\bbilled\b|\bpayment\b/.test(blob)) {
37
+ return "active";
38
+ }
39
+ return "uncertain";
40
+ }
41
+ function parseUsdAmount(message) {
42
+ const blob = `${message.subject} ${message.snippet}`;
43
+ const match = blob.match(/\$([0-9]+(?:\.[0-9]{1,2})?)/);
44
+ if (!match) {
45
+ return null;
46
+ }
47
+ const value = Number(match[1]);
48
+ return Number.isFinite(value) ? value : null;
49
+ }
50
+ function annualizeAmount(amount, cadence) {
51
+ if (amount === null) {
52
+ return null;
53
+ }
54
+ if (cadence === "monthly") {
55
+ return Number((amount * 12).toFixed(2));
56
+ }
57
+ if (cadence === "annual") {
58
+ return Number(amount.toFixed(2));
59
+ }
60
+ return null;
61
+ }
62
+ function summarizeEvidence(serviceName, evidence) {
63
+ const latest = evidence[0];
64
+ if (!latest) {
65
+ return `No recent email evidence found for ${serviceName}.`;
66
+ }
67
+ return `${serviceName}: ${evidence.length} matching email${evidence.length === 1 ? "" : "s"}, latest "${latest.subject}" on ${latest.receivedAt}.`;
68
+ }
69
+ function messageBlob(message) {
70
+ return [
71
+ message.subject,
72
+ message.snippet,
73
+ message.from,
74
+ message.fromEmail ?? "",
75
+ ]
76
+ .join(" ")
77
+ .toLowerCase();
78
+ }
79
+ function scoreMessageAgainstPlaybook(message, playbook) {
80
+ const blob = messageBlob(message);
81
+ let score = 0;
82
+ for (const alias of [playbook.serviceName, ...playbook.aliases]) {
83
+ if (blob.includes(alias.toLowerCase())) {
84
+ score += 2;
85
+ }
86
+ }
87
+ for (const keyword of playbook.auditSubjectKeywords) {
88
+ if (blob.includes(keyword.toLowerCase())) {
89
+ score += 1;
90
+ }
91
+ }
92
+ for (const domain of playbook.auditDomains) {
93
+ if (blob.includes(domain.toLowerCase())) {
94
+ score += 1;
95
+ }
96
+ }
97
+ return score;
98
+ }
99
+ function resolvePlaybookFromMessage(text) {
100
+ return findLifeOpsSubscriptionPlaybook(text);
101
+ }
102
+ function resolvePlaybookFromCandidate(candidate) {
103
+ return (findLifeOpsSubscriptionPlaybook(candidate.serviceSlug) ??
104
+ findLifeOpsSubscriptionPlaybook(candidate.serviceName));
105
+ }
106
+ function toUserBrowserActions(playbook) {
107
+ const actions = [];
108
+ for (const step of playbook.steps) {
109
+ switch (step.kind) {
110
+ case "open":
111
+ case "navigate":
112
+ actions.push({
113
+ kind: step.kind,
114
+ label: `${playbook.serviceName}: ${step.kind}`,
115
+ url: step.url,
116
+ selector: null,
117
+ text: null,
118
+ accountAffecting: false,
119
+ requiresConfirmation: false,
120
+ metadata: { playbookKey: playbook.key },
121
+ });
122
+ break;
123
+ case "click_text":
124
+ actions.push({
125
+ kind: "click",
126
+ label: `${playbook.serviceName}: click ${step.text}`,
127
+ url: null,
128
+ selector: null,
129
+ text: step.text,
130
+ accountAffecting: true,
131
+ requiresConfirmation: step.destructive ?? false,
132
+ metadata: { playbookKey: playbook.key },
133
+ });
134
+ break;
135
+ case "click_selector":
136
+ actions.push({
137
+ kind: "click",
138
+ label: `${playbook.serviceName}: click selector`,
139
+ url: null,
140
+ selector: step.selector,
141
+ text: null,
142
+ accountAffecting: true,
143
+ requiresConfirmation: step.destructive ?? false,
144
+ metadata: { playbookKey: playbook.key },
145
+ });
146
+ break;
147
+ case "wait_text":
148
+ case "assert_text":
149
+ case "wait_selector":
150
+ case "screenshot":
151
+ actions.push({
152
+ kind: "read_page",
153
+ label: `${playbook.serviceName}: inspect page`,
154
+ url: null,
155
+ selector: null,
156
+ text: null,
157
+ accountAffecting: false,
158
+ requiresConfirmation: false,
159
+ metadata: {
160
+ playbookKey: playbook.key,
161
+ expected: step.kind === "wait_selector"
162
+ ? step.selector
163
+ : "text" in step
164
+ ? step.text
165
+ : step.label,
166
+ },
167
+ });
168
+ break;
169
+ }
170
+ }
171
+ return actions;
172
+ }
173
+ function browserResultText(result) {
174
+ return [
175
+ result.message ?? "",
176
+ typeof result.content === "string" ? result.content : "",
177
+ typeof result.url === "string" ? result.url : "",
178
+ typeof result.title === "string" ? result.title : "",
179
+ result.error ?? "",
180
+ result.data ? JSON.stringify(result.data) : "",
181
+ ]
182
+ .join(" ")
183
+ .toLowerCase();
184
+ }
185
+ function summarizeCancellationStatus(cancellation) {
186
+ switch (cancellation.status) {
187
+ case "completed":
188
+ return `${cancellation.serviceName} cancellation completed.`;
189
+ case "awaiting_confirmation":
190
+ return `Cancellation for ${cancellation.serviceName} is ready for final confirmation.`;
191
+ case "needs_login":
192
+ return `${cancellation.serviceName} needs the user to sign in before cancellation can continue.`;
193
+ case "needs_mfa":
194
+ return `${cancellation.serviceName} needs multi-factor verification before cancellation can continue.`;
195
+ case "phone_only":
196
+ return `${cancellation.serviceName} can only be canceled by phone.`;
197
+ case "chat_only":
198
+ return `${cancellation.serviceName} can only be canceled through support chat.`;
199
+ case "already_canceled":
200
+ return `${cancellation.serviceName} already appears to be canceled.`;
201
+ case "failed":
202
+ return `Cancellation for ${cancellation.serviceName} failed${cancellation.error ? `: ${cancellation.error}` : "."}`;
203
+ default:
204
+ return `${cancellation.serviceName} cancellation status: ${cancellation.status}.`;
205
+ }
206
+ }
207
+ function extractEvidenceMessages(messages) {
208
+ return messages.slice(0, 5).map((message) => ({
209
+ messageId: message.id,
210
+ subject: message.subject,
211
+ from: message.from,
212
+ receivedAt: message.receivedAt,
213
+ snippet: message.snippet,
214
+ htmlLink: message.htmlLink,
215
+ }));
216
+ }
217
+ async function probeBrowserSignals(computerUse, playbook) {
218
+ const dom = await computerUse.executeBrowserAction({ action: "get_dom" });
219
+ const blob = browserResultText(dom);
220
+ for (const marker of playbook.cancellationMarkers) {
221
+ if (blob.includes(marker.toLowerCase())) {
222
+ return { status: "completed", detail: marker };
223
+ }
224
+ }
225
+ for (const marker of playbook.phoneOnlyMarkers) {
226
+ if (blob.includes(marker.toLowerCase())) {
227
+ return { status: "phone_only", detail: marker };
228
+ }
229
+ }
230
+ for (const marker of playbook.chatOnlyMarkers) {
231
+ if (blob.includes(marker.toLowerCase())) {
232
+ return { status: "chat_only", detail: marker };
233
+ }
234
+ }
235
+ for (const marker of playbook.mfaMarkers) {
236
+ if (blob.includes(marker.toLowerCase())) {
237
+ return { status: "needs_mfa", detail: marker };
238
+ }
239
+ }
240
+ for (const marker of playbook.loginMarkers) {
241
+ if (blob.includes(marker.toLowerCase())) {
242
+ return { status: "needs_login", detail: marker };
243
+ }
244
+ }
245
+ return { status: "clear", detail: null };
246
+ }
247
+ async function executeBrowserStep(computerUse, step) {
248
+ let params;
249
+ switch (step.kind) {
250
+ case "open":
251
+ params = { action: "open", url: step.url };
252
+ break;
253
+ case "navigate":
254
+ params = { action: "navigate", url: step.url };
255
+ break;
256
+ case "wait_text":
257
+ params = {
258
+ action: "wait",
259
+ text: step.text,
260
+ timeout: step.timeoutMs,
261
+ };
262
+ break;
263
+ case "wait_selector":
264
+ params = {
265
+ action: "wait",
266
+ selector: step.selector,
267
+ timeout: step.timeoutMs,
268
+ };
269
+ break;
270
+ case "click_text":
271
+ params = { action: "click", text: step.text };
272
+ break;
273
+ case "click_selector":
274
+ params = { action: "click", selector: step.selector };
275
+ break;
276
+ case "assert_text":
277
+ params = { action: "get_dom" };
278
+ break;
279
+ case "screenshot":
280
+ params = { action: "screenshot" };
281
+ break;
282
+ }
283
+ return computerUse.executeBrowserAction(params);
284
+ }
285
+ function findServiceInText(text) {
286
+ const playbook = resolvePlaybookFromMessage(text);
287
+ if (!playbook) {
288
+ return null;
289
+ }
290
+ return {
291
+ serviceName: playbook.serviceName,
292
+ serviceSlug: playbook.key,
293
+ };
294
+ }
295
+ /** @internal */
296
+ export function withSubscriptions(Base) {
297
+ class LifeOpsSubscriptionsServiceMixin extends Base {
298
+ async listSubscriptionPlaybooks() {
299
+ return [...listLifeOpsSubscriptionPlaybooks()];
300
+ }
301
+ async getLatestSubscriptionAudit() {
302
+ const audit = await this.repository.getLatestSubscriptionAudit(this.agentId());
303
+ if (!audit) {
304
+ return null;
305
+ }
306
+ const candidates = await this.repository.listSubscriptionCandidatesForAudit(this.agentId(), audit.id);
307
+ return { audit, candidates };
308
+ }
309
+ async auditSubscriptions(requestUrl, request = {}) {
310
+ const queryWindowDays = Math.max(1, Math.min(365, Number.isFinite(request.queryWindowDays)
311
+ ? Math.trunc(request.queryWindowDays)
312
+ : DEFAULT_AUDIT_WINDOW_DAYS));
313
+ const serviceQuery = normalizeOptionalString(request.serviceQuery) ?? null;
314
+ let messages = [];
315
+ let source = "gmail";
316
+ try {
317
+ const triage = await this.getGmailTriage(requestUrl, {
318
+ maxResults: MAX_AUDIT_MESSAGES,
319
+ });
320
+ const sinceMs = Date.now() - queryWindowDays * 86_400_000;
321
+ messages = triage.messages.filter((message) => {
322
+ const receivedMs = Date.parse(message.receivedAt);
323
+ return !Number.isNaN(receivedMs) && receivedMs >= sinceMs;
324
+ });
325
+ }
326
+ catch (error) {
327
+ source = serviceQuery ? "manual" : "gmail";
328
+ this.logLifeOpsWarn("subscriptions_audit", `gmail discovery unavailable: ${error instanceof Error ? error.message : String(error)}`);
329
+ }
330
+ const playbooks = serviceQuery
331
+ ? listLifeOpsSubscriptionPlaybooks().filter((playbook) => {
332
+ const lookup = normalizeSubscriptionLookup(serviceQuery);
333
+ return (normalizeSubscriptionLookup(playbook.serviceName) === lookup ||
334
+ playbook.aliases.some((alias) => normalizeSubscriptionLookup(alias) === lookup) ||
335
+ normalizeSubscriptionLookup(playbook.key) === lookup);
336
+ })
337
+ : listLifeOpsSubscriptionPlaybooks();
338
+ const candidates = [];
339
+ for (const playbook of playbooks) {
340
+ const evidence = messages
341
+ .map((message) => ({
342
+ message,
343
+ score: scoreMessageAgainstPlaybook(message, playbook),
344
+ }))
345
+ .filter((candidate) => candidate.score > 0)
346
+ .sort((left, right) => right.score - left.score);
347
+ if (evidence.length === 0 && source !== "manual") {
348
+ continue;
349
+ }
350
+ const bestEvidence = evidence[0] ?? null;
351
+ const bestMessage = bestEvidence?.message ?? null;
352
+ const cadence = bestMessage ? guessCadence(bestMessage) : "unknown";
353
+ const state = bestMessage ? guessState(bestMessage) : "uncertain";
354
+ const amount = bestMessage ? parseUsdAmount(bestMessage) : null;
355
+ const confidence = bestEvidence
356
+ ? Math.min(0.98, 0.45 + bestEvidence.score * 0.12)
357
+ : 0.4;
358
+ const candidate = createLifeOpsSubscriptionCandidate({
359
+ agentId: this.agentId(),
360
+ auditId: "",
361
+ serviceSlug: playbook.key,
362
+ serviceName: playbook.serviceName,
363
+ provider: bestMessage?.fromEmail ??
364
+ bestMessage?.from ??
365
+ playbook.auditDomains[0] ??
366
+ playbook.serviceName,
367
+ cadence,
368
+ state,
369
+ confidence,
370
+ annualCostEstimateUsd: annualizeAmount(amount, cadence),
371
+ managementUrl: playbook.managementUrl,
372
+ latestEvidenceAt: bestMessage?.receivedAt ?? null,
373
+ evidenceJson: extractEvidenceMessages(evidence.map((item) => item.message)),
374
+ metadata: {
375
+ playbookKey: playbook.key,
376
+ evidenceCount: evidence.length,
377
+ source,
378
+ },
379
+ });
380
+ candidates.push(candidate);
381
+ }
382
+ const audit = createLifeOpsSubscriptionAudit({
383
+ agentId: this.agentId(),
384
+ source,
385
+ queryWindowDays,
386
+ status: "completed",
387
+ totalCandidates: candidates.length,
388
+ activeCandidates: candidates.filter((candidate) => candidate.state === "active").length,
389
+ canceledCandidates: candidates.filter((candidate) => candidate.state === "canceled").length,
390
+ uncertainCandidates: candidates.filter((candidate) => candidate.state === "uncertain").length,
391
+ summary: candidates.length === 0
392
+ ? source === "manual"
393
+ ? "No matching subscription playbooks were found for the requested service."
394
+ : "No subscription evidence was found in recent Gmail receipts."
395
+ : `Found ${candidates.length} likely subscription${candidates.length === 1 ? "" : "s"} from recent LifeOps signals.`,
396
+ metadata: {
397
+ serviceQuery,
398
+ scannedMessageCount: messages.length,
399
+ playbookCount: playbooks.length,
400
+ },
401
+ });
402
+ await this.repository.createSubscriptionAudit(audit);
403
+ for (const candidate of candidates) {
404
+ const persisted = {
405
+ ...candidate,
406
+ auditId: audit.id,
407
+ };
408
+ await this.repository.createSubscriptionCandidate(persisted);
409
+ }
410
+ const persistedCandidates = await this.repository.listSubscriptionCandidatesForAudit(this.agentId(), audit.id);
411
+ return { audit, candidates: persistedCandidates };
412
+ }
413
+ async getSubscriptionCancellationStatus(args) {
414
+ const serviceSlug = normalizeOptionalString(args.serviceSlug);
415
+ let cancellation = normalizeOptionalString(args.cancellationId) !== undefined
416
+ ? await this.repository.getSubscriptionCancellation(this.agentId(), requireNonEmptyString(args.cancellationId, "cancellationId"))
417
+ : await this.repository.getLatestSubscriptionCancellation(this.agentId(), serviceSlug);
418
+ if (!cancellation && normalizeOptionalString(args.serviceName)) {
419
+ const playbook = resolvePlaybookFromMessage(requireNonEmptyString(args.serviceName, "serviceName"));
420
+ cancellation = await this.repository.getLatestSubscriptionCancellation(this.agentId(), playbook?.key);
421
+ }
422
+ if (!cancellation) {
423
+ return null;
424
+ }
425
+ if (cancellation.browserSessionId) {
426
+ const session = await this.repository.getBrowserSession(this.agentId(), cancellation.browserSessionId);
427
+ if (session) {
428
+ const nextStatus = session.status === "done"
429
+ ? "completed"
430
+ : session.status === "failed"
431
+ ? "failed"
432
+ : session.status === "awaiting_confirmation"
433
+ ? "awaiting_confirmation"
434
+ : "running";
435
+ if (nextStatus !== cancellation.status) {
436
+ cancellation = {
437
+ ...cancellation,
438
+ status: nextStatus,
439
+ evidenceSummary: cancellation.evidenceSummary ??
440
+ `LifeOps Browser session ${session.status}.`,
441
+ error: nextStatus === "failed"
442
+ ? JSON.stringify(session.result)
443
+ : cancellation.error,
444
+ updatedAt: new Date().toISOString(),
445
+ finishedAt: nextStatus === "completed" || nextStatus === "failed"
446
+ ? new Date().toISOString()
447
+ : cancellation.finishedAt,
448
+ };
449
+ await this.repository.updateSubscriptionCancellation(cancellation);
450
+ }
451
+ }
452
+ }
453
+ const candidate = cancellation.candidateId
454
+ ? await this.repository.getSubscriptionCandidate(this.agentId(), cancellation.candidateId)
455
+ : null;
456
+ return { cancellation, candidate };
457
+ }
458
+ async cancelSubscription(request) {
459
+ const candidate = request.candidateId
460
+ ? await this.repository.getSubscriptionCandidate(this.agentId(), request.candidateId)
461
+ : null;
462
+ const requestedServiceName = normalizeOptionalString(request.serviceName);
463
+ const requestedServiceSlug = normalizeOptionalString(request.serviceSlug);
464
+ const playbook = (candidate ? resolvePlaybookFromCandidate(candidate) : null) ??
465
+ (requestedServiceSlug
466
+ ? resolvePlaybookFromMessage(requestedServiceSlug)
467
+ : null) ??
468
+ (requestedServiceName
469
+ ? resolvePlaybookFromMessage(requestedServiceName)
470
+ : null);
471
+ if (!candidate && !playbook && !requestedServiceName) {
472
+ fail(400, "cancelSubscription requires a known candidateId or recognizable serviceName/serviceSlug");
473
+ }
474
+ const serviceName = candidate?.serviceName ??
475
+ playbook?.serviceName ??
476
+ requestedServiceName;
477
+ const serviceSlug = candidate?.serviceSlug ??
478
+ playbook?.key ??
479
+ requestedServiceSlug ??
480
+ slugifySubscriptionValue(serviceName);
481
+ const connectedCompanions = await this.listBrowserCompanions();
482
+ const explicitExecutor = normalizeOptionalString(request.executor);
483
+ const executor = (explicitExecutor ??
484
+ (connectedCompanions.some((companion) => companion.connectionState === "connected")
485
+ ? "user_browser"
486
+ : (playbook?.executorPreference ??
487
+ "agent_browser")));
488
+ const confirmed = normalizeOptionalBoolean(request.confirmed, "confirmed") ?? false;
489
+ let cancellation = createLifeOpsSubscriptionCancellation({
490
+ agentId: this.agentId(),
491
+ auditId: candidate?.auditId ?? null,
492
+ candidateId: candidate?.id ?? null,
493
+ serviceSlug,
494
+ serviceName,
495
+ executor,
496
+ status: "draft",
497
+ confirmed,
498
+ currentStep: null,
499
+ browserSessionId: null,
500
+ evidenceSummary: null,
501
+ artifactCount: 0,
502
+ managementUrl: candidate?.managementUrl ?? playbook?.managementUrl ?? null,
503
+ error: null,
504
+ metadata: {
505
+ playbookKey: playbook?.key ?? null,
506
+ candidateState: candidate?.state ?? null,
507
+ },
508
+ finishedAt: null,
509
+ });
510
+ await this.repository.createSubscriptionCancellation(cancellation);
511
+ if (!playbook) {
512
+ cancellation = {
513
+ ...cancellation,
514
+ status: "unsupported_surface",
515
+ error: "No known cancellation playbook for this service.",
516
+ updatedAt: new Date().toISOString(),
517
+ finishedAt: new Date().toISOString(),
518
+ };
519
+ await this.repository.updateSubscriptionCancellation(cancellation);
520
+ return { cancellation, candidate };
521
+ }
522
+ if (candidate?.state === "canceled") {
523
+ cancellation = {
524
+ ...cancellation,
525
+ status: "already_canceled",
526
+ evidenceSummary: summarizeEvidence(serviceName, []),
527
+ updatedAt: new Date().toISOString(),
528
+ finishedAt: new Date().toISOString(),
529
+ };
530
+ await this.repository.updateSubscriptionCancellation(cancellation);
531
+ return { cancellation, candidate };
532
+ }
533
+ if (executor === "user_browser") {
534
+ const companion = connectedCompanions.find((entry) => entry.connectionState === "connected");
535
+ if (!companion) {
536
+ cancellation = {
537
+ ...cancellation,
538
+ status: "blocked",
539
+ error: "No connected LifeOps Browser companion is available.",
540
+ updatedAt: new Date().toISOString(),
541
+ finishedAt: new Date().toISOString(),
542
+ };
543
+ await this.repository.updateSubscriptionCancellation(cancellation);
544
+ return { cancellation, candidate };
545
+ }
546
+ const session = await this.createBrowserSession({
547
+ title: `Manage ${serviceName} subscription`,
548
+ browser: companion.browser,
549
+ companionId: companion.id,
550
+ profileId: companion.profileId,
551
+ actions: toUserBrowserActions(playbook),
552
+ });
553
+ cancellation = {
554
+ ...cancellation,
555
+ status: session.status === "awaiting_confirmation"
556
+ ? "awaiting_confirmation"
557
+ : "running",
558
+ currentStep: "browser_session_created",
559
+ browserSessionId: session.id,
560
+ updatedAt: new Date().toISOString(),
561
+ metadata: {
562
+ ...cancellation.metadata,
563
+ browserSessionStatus: session.status,
564
+ },
565
+ };
566
+ await this.repository.updateSubscriptionCancellation(cancellation);
567
+ return { cancellation, candidate };
568
+ }
569
+ const computerUse = this.runtime.getService("computeruse");
570
+ if (!computerUse) {
571
+ cancellation = {
572
+ ...cancellation,
573
+ status: "failed",
574
+ error: "Computer-use service is not available.",
575
+ updatedAt: new Date().toISOString(),
576
+ finishedAt: new Date().toISOString(),
577
+ };
578
+ await this.repository.updateSubscriptionCancellation(cancellation);
579
+ return { cancellation, candidate };
580
+ }
581
+ const artifacts = [];
582
+ cancellation = {
583
+ ...cancellation,
584
+ status: "running",
585
+ currentStep: "starting_playbook",
586
+ updatedAt: new Date().toISOString(),
587
+ };
588
+ await this.repository.updateSubscriptionCancellation(cancellation);
589
+ for (const step of playbook.steps) {
590
+ if ("destructive" in step && step.destructive && !confirmed) {
591
+ cancellation = {
592
+ ...cancellation,
593
+ status: "awaiting_confirmation",
594
+ currentStep: step.kind === "click_text"
595
+ ? step.text
596
+ : step.kind === "click_selector"
597
+ ? step.selector
598
+ : "destructive_step",
599
+ evidenceSummary: cancellation.evidenceSummary ??
600
+ `Ready to confirm ${serviceName} cancellation.`,
601
+ artifactCount: artifacts.length,
602
+ metadata: {
603
+ ...cancellation.metadata,
604
+ artifacts,
605
+ },
606
+ updatedAt: new Date().toISOString(),
607
+ };
608
+ await this.repository.updateSubscriptionCancellation(cancellation);
609
+ return { cancellation, candidate };
610
+ }
611
+ const result = await executeBrowserStep(computerUse, step);
612
+ if (!result.success) {
613
+ cancellation = {
614
+ ...cancellation,
615
+ status: "failed",
616
+ currentStep: step.kind,
617
+ error: result.error ?? result.message ?? "browser step failed",
618
+ artifactCount: artifacts.length,
619
+ metadata: {
620
+ ...cancellation.metadata,
621
+ artifacts,
622
+ lastBrowserResult: result,
623
+ },
624
+ updatedAt: new Date().toISOString(),
625
+ finishedAt: new Date().toISOString(),
626
+ };
627
+ await this.repository.updateSubscriptionCancellation(cancellation);
628
+ return { cancellation, candidate };
629
+ }
630
+ if (step.kind === "screenshot" && result.screenshot) {
631
+ artifacts.push({
632
+ kind: "screenshot",
633
+ label: step.label,
634
+ detail: `screenshot:${result.screenshot.length}`,
635
+ });
636
+ }
637
+ const probe = await probeBrowserSignals(computerUse, playbook);
638
+ if (probe.status === "needs_login") {
639
+ cancellation = {
640
+ ...cancellation,
641
+ status: "needs_login",
642
+ currentStep: step.kind,
643
+ evidenceSummary: probe.detail,
644
+ artifactCount: artifacts.length,
645
+ metadata: {
646
+ ...cancellation.metadata,
647
+ artifacts,
648
+ },
649
+ updatedAt: new Date().toISOString(),
650
+ finishedAt: new Date().toISOString(),
651
+ };
652
+ await this.repository.updateSubscriptionCancellation(cancellation);
653
+ return { cancellation, candidate };
654
+ }
655
+ if (probe.status === "needs_mfa") {
656
+ cancellation = {
657
+ ...cancellation,
658
+ status: "needs_mfa",
659
+ currentStep: step.kind,
660
+ evidenceSummary: probe.detail,
661
+ artifactCount: artifacts.length,
662
+ metadata: {
663
+ ...cancellation.metadata,
664
+ artifacts,
665
+ },
666
+ updatedAt: new Date().toISOString(),
667
+ finishedAt: new Date().toISOString(),
668
+ };
669
+ await this.repository.updateSubscriptionCancellation(cancellation);
670
+ return { cancellation, candidate };
671
+ }
672
+ if (probe.status === "phone_only" || probe.status === "chat_only") {
673
+ cancellation = {
674
+ ...cancellation,
675
+ status: probe.status,
676
+ currentStep: step.kind,
677
+ evidenceSummary: probe.detail,
678
+ artifactCount: artifacts.length,
679
+ metadata: {
680
+ ...cancellation.metadata,
681
+ artifacts,
682
+ },
683
+ updatedAt: new Date().toISOString(),
684
+ finishedAt: new Date().toISOString(),
685
+ };
686
+ await this.repository.updateSubscriptionCancellation(cancellation);
687
+ return { cancellation, candidate };
688
+ }
689
+ }
690
+ const finalProbe = await probeBrowserSignals(computerUse, playbook);
691
+ cancellation = {
692
+ ...cancellation,
693
+ status: finalProbe.status === "completed" ? "completed" : "blocked",
694
+ currentStep: "done",
695
+ evidenceSummary: finalProbe.detail ??
696
+ `${serviceName} flow finished in the local browser.`,
697
+ artifactCount: artifacts.length,
698
+ metadata: {
699
+ ...cancellation.metadata,
700
+ artifacts,
701
+ },
702
+ updatedAt: new Date().toISOString(),
703
+ finishedAt: new Date().toISOString(),
704
+ };
705
+ await this.repository.updateSubscriptionCancellation(cancellation);
706
+ return { cancellation, candidate };
707
+ }
708
+ summarizeSubscriptionAudit(summary) {
709
+ if (summary.candidates.length === 0) {
710
+ return summary.audit.summary;
711
+ }
712
+ return [
713
+ summary.audit.summary,
714
+ ...summary.candidates.slice(0, 5).map((candidate) => {
715
+ const annual = candidate.annualCostEstimateUsd === null
716
+ ? ""
717
+ : `, est $${candidate.annualCostEstimateUsd.toFixed(2)}/yr`;
718
+ return `- ${candidate.serviceName} (${candidate.state}, ${candidate.cadence}${annual})`;
719
+ }),
720
+ ].join("\n");
721
+ }
722
+ summarizeSubscriptionCancellation(summary) {
723
+ const lines = [summarizeCancellationStatus(summary.cancellation)];
724
+ if (summary.cancellation.evidenceSummary) {
725
+ lines.push(summary.cancellation.evidenceSummary);
726
+ }
727
+ if (summary.candidate) {
728
+ lines.push(`Candidate confidence ${summary.candidate.confidence.toFixed(2)} from ${summary.candidate.provider}.`);
729
+ }
730
+ return lines.join(" ");
731
+ }
732
+ resolveSubscriptionIntent(text) {
733
+ const normalized = text.trim().toLowerCase();
734
+ if (!normalized) {
735
+ return { mode: null };
736
+ }
737
+ const matchedService = findServiceInText(text);
738
+ if (/\baudit\b|\breport\b|\breview\b|\bfind\b.*\bsubscription\b|\bwhat subscriptions\b/.test(normalized)) {
739
+ return {
740
+ mode: "audit",
741
+ ...matchedService,
742
+ };
743
+ }
744
+ if (/\bcancel\b|\bunsubscribe\b|\bend\b.*\bsubscription\b/.test(normalized)) {
745
+ return {
746
+ mode: "cancel",
747
+ ...matchedService,
748
+ executor: /\bin my browser\b|\bpersonal browser\b/.test(normalized)
749
+ ? "user_browser"
750
+ : "agent_browser",
751
+ };
752
+ }
753
+ if (/\bstatus\b|\bwhat happened\b|\bupdate\b.*\bsubscription\b/.test(normalized)) {
754
+ return {
755
+ mode: "status",
756
+ ...matchedService,
757
+ };
758
+ }
759
+ return { mode: null, ...matchedService };
760
+ }
761
+ }
762
+ return LifeOpsSubscriptionsServiceMixin;
763
+ }