@elizaos/agent 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 (240) hide show
  1. package/apps/app-lifeops/src/action.d.ts.map +1 -1
  2. package/apps/app-lifeops/src/action.js +21 -2
  3. package/apps/app-lifeops/src/actions/app-blocker.d.ts +3 -1
  4. package/apps/app-lifeops/src/actions/app-blocker.d.ts.map +1 -1
  5. package/apps/app-lifeops/src/actions/app-blocker.js +2 -0
  6. package/apps/app-lifeops/src/actions/calendar.d.ts.map +1 -1
  7. package/apps/app-lifeops/src/actions/calendar.js +140 -7
  8. package/apps/app-lifeops/src/actions/computer-use.d.ts.map +1 -1
  9. package/apps/app-lifeops/src/actions/computer-use.js +248 -17
  10. package/apps/app-lifeops/src/actions/cross-channel-send.d.ts +3 -1
  11. package/apps/app-lifeops/src/actions/cross-channel-send.d.ts.map +1 -1
  12. package/apps/app-lifeops/src/actions/cross-channel-send.js +1 -0
  13. package/apps/app-lifeops/src/actions/dossier.d.ts.map +1 -1
  14. package/apps/app-lifeops/src/actions/dossier.js +51 -34
  15. package/apps/app-lifeops/src/actions/gmail.d.ts.map +1 -1
  16. package/apps/app-lifeops/src/actions/gmail.js +5 -0
  17. package/apps/app-lifeops/src/actions/inbox.d.ts.map +1 -1
  18. package/apps/app-lifeops/src/actions/inbox.js +8 -1
  19. package/apps/app-lifeops/src/actions/life.d.ts.map +1 -1
  20. package/apps/app-lifeops/src/actions/life.extractor.d.ts.map +1 -1
  21. package/apps/app-lifeops/src/actions/life.extractor.js +103 -4
  22. package/apps/app-lifeops/src/actions/life.js +8 -0
  23. package/apps/app-lifeops/src/actions/non-actionable-request.d.ts +6 -0
  24. package/apps/app-lifeops/src/actions/non-actionable-request.d.ts.map +1 -0
  25. package/apps/app-lifeops/src/actions/non-actionable-request.js +33 -0
  26. package/apps/app-lifeops/src/actions/password-manager.d.ts.map +1 -1
  27. package/apps/app-lifeops/src/actions/password-manager.js +11 -2
  28. package/apps/app-lifeops/src/actions/relationships.d.ts +3 -1
  29. package/apps/app-lifeops/src/actions/relationships.d.ts.map +1 -1
  30. package/apps/app-lifeops/src/actions/relationships.js +167 -11
  31. package/apps/app-lifeops/src/actions/remote-desktop.js +1 -1
  32. package/apps/app-lifeops/src/actions/screen-time.d.ts.map +1 -1
  33. package/apps/app-lifeops/src/actions/screen-time.js +8 -2
  34. package/apps/app-lifeops/src/actions/start-remote-session.js +1 -1
  35. package/apps/app-lifeops/src/actions/subscriptions.d.ts +5 -0
  36. package/apps/app-lifeops/src/actions/subscriptions.d.ts.map +1 -0
  37. package/apps/app-lifeops/src/actions/subscriptions.js +251 -0
  38. package/apps/app-lifeops/src/actions/twilio-call.d.ts.map +1 -1
  39. package/apps/app-lifeops/src/actions/twilio-call.js +48 -3
  40. package/apps/app-lifeops/src/actions/update-owner-profile.d.ts.map +1 -1
  41. package/apps/app-lifeops/src/actions/update-owner-profile.js +2 -1
  42. package/apps/app-lifeops/src/actions/website-blocker.d.ts +6 -2
  43. package/apps/app-lifeops/src/actions/website-blocker.d.ts.map +1 -1
  44. package/apps/app-lifeops/src/actions/website-blocker.js +38 -0
  45. package/apps/app-lifeops/src/actions/x-read.d.ts.map +1 -1
  46. package/apps/app-lifeops/src/actions/x-read.js +27 -3
  47. package/apps/app-lifeops/src/dossier/action.d.ts.map +1 -1
  48. package/apps/app-lifeops/src/dossier/action.js +8 -7
  49. package/apps/app-lifeops/src/followup/actions/listOverdueFollowups.d.ts.map +1 -1
  50. package/apps/app-lifeops/src/followup/actions/listOverdueFollowups.js +1 -0
  51. package/apps/app-lifeops/src/followup/actions/markFollowupDone.d.ts.map +1 -1
  52. package/apps/app-lifeops/src/followup/actions/markFollowupDone.js +2 -1
  53. package/apps/app-lifeops/src/followup/actions/setFollowupThreshold.d.ts.map +1 -1
  54. package/apps/app-lifeops/src/followup/actions/setFollowupThreshold.js +2 -1
  55. package/apps/app-lifeops/src/lifeops/calendly-client.d.ts.map +1 -1
  56. package/apps/app-lifeops/src/lifeops/calendly-client.js +4 -2
  57. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.d.ts.map +1 -1
  58. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.js +6 -5
  59. package/apps/app-lifeops/src/lifeops/password-manager-bridge.d.ts +1 -1
  60. package/apps/app-lifeops/src/lifeops/password-manager-bridge.d.ts.map +1 -1
  61. package/apps/app-lifeops/src/lifeops/password-manager-bridge.js +84 -6
  62. package/apps/app-lifeops/src/lifeops/remote-desktop.d.ts.map +1 -1
  63. package/apps/app-lifeops/src/lifeops/remote-desktop.js +30 -0
  64. package/apps/app-lifeops/src/lifeops/repository.d.ts +15 -0
  65. package/apps/app-lifeops/src/lifeops/repository.d.ts.map +1 -1
  66. package/apps/app-lifeops/src/lifeops/repository.js +330 -1
  67. package/apps/app-lifeops/src/lifeops/service-mixin-discord.js +222 -42
  68. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.d.ts +2 -0
  69. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.d.ts.map +1 -0
  70. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.js +763 -0
  71. package/apps/app-lifeops/src/lifeops/service.d.ts +63 -0
  72. package/apps/app-lifeops/src/lifeops/service.d.ts.map +1 -1
  73. package/apps/app-lifeops/src/lifeops/service.js +2 -1
  74. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.d.ts +54 -0
  75. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.d.ts.map +1 -0
  76. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.js +213 -0
  77. package/apps/app-lifeops/src/lifeops/subscriptions-types.d.ts +80 -0
  78. package/apps/app-lifeops/src/lifeops/subscriptions-types.d.ts.map +1 -0
  79. package/apps/app-lifeops/src/lifeops/twilio.d.ts.map +1 -1
  80. package/apps/app-lifeops/src/lifeops/twilio.js +4 -2
  81. package/apps/app-lifeops/src/lifeops/whatsapp-client.d.ts.map +1 -1
  82. package/apps/app-lifeops/src/lifeops/whatsapp-client.js +4 -2
  83. package/apps/app-lifeops/src/lifeops/x-poster.d.ts.map +1 -1
  84. package/apps/app-lifeops/src/lifeops/x-poster.js +12 -6
  85. package/apps/app-lifeops/src/lifeops/x-reader.d.ts.map +1 -1
  86. package/apps/app-lifeops/src/lifeops/x-reader.js +7 -5
  87. package/apps/app-lifeops/src/plugin.d.ts.map +1 -1
  88. package/apps/app-lifeops/src/plugin.js +31 -24
  89. package/apps/app-lifeops/src/provider.d.ts.map +1 -1
  90. package/apps/app-lifeops/src/provider.js +4 -4
  91. package/apps/app-lifeops/src/providers/inbox-triage.js +2 -2
  92. package/apps/app-lifeops/src/providers/lifeops.d.ts.map +1 -1
  93. package/apps/app-lifeops/src/providers/lifeops.js +19 -10
  94. package/apps/app-lifeops/src/providers/website-blocker.js +1 -1
  95. package/apps/app-lifeops/src/public.d.ts +4 -0
  96. package/apps/app-lifeops/src/public.d.ts.map +1 -0
  97. package/apps/app-lifeops/src/public.js +3 -0
  98. package/apps/app-lifeops/src/routes/plugin.d.ts +18 -0
  99. package/apps/app-lifeops/src/routes/plugin.d.ts.map +1 -0
  100. package/apps/app-lifeops/src/routes/plugin.js +270 -0
  101. package/apps/app-lifeops/src/website-blocker/chat-integration/actions/blockUntilTaskComplete.d.ts.map +1 -1
  102. package/apps/app-lifeops/src/website-blocker/chat-integration/actions/blockUntilTaskComplete.js +18 -2
  103. package/apps/app-training/src/core/training-orchestrator.d.ts.map +1 -1
  104. package/apps/app-training/src/core/training-orchestrator.js +4 -0
  105. package/apps/app-training/src/services/training-trigger.d.ts +32 -18
  106. package/apps/app-training/src/services/training-trigger.d.ts.map +1 -1
  107. package/apps/app-training/src/services/training-trigger.js +10 -1
  108. package/package.json +4 -4
  109. package/packages/agent/src/actions/browser-session.d.ts +3 -0
  110. package/packages/agent/src/actions/browser-session.d.ts.map +1 -0
  111. package/packages/agent/src/actions/browser-session.js +207 -0
  112. package/packages/agent/src/actions/context-signal.d.ts +1 -1
  113. package/packages/agent/src/actions/context-signal.d.ts.map +1 -1
  114. package/packages/agent/src/actions/context-signal.js +1 -1
  115. package/packages/agent/src/actions/extract-page.d.ts +3 -0
  116. package/packages/agent/src/actions/extract-page.d.ts.map +1 -0
  117. package/packages/agent/src/actions/extract-page.js +124 -0
  118. package/packages/agent/src/actions/grounded-action-reply.js +1 -1
  119. package/packages/agent/src/actions/recent-conversation-texts.d.ts +9 -0
  120. package/packages/agent/src/actions/recent-conversation-texts.d.ts.map +1 -0
  121. package/packages/agent/src/actions/recent-conversation-texts.js +81 -0
  122. package/packages/agent/src/actions/set-user-name.js +1 -1
  123. package/packages/agent/src/actions/web-search.d.ts +4 -10
  124. package/packages/agent/src/actions/web-search.d.ts.map +1 -1
  125. package/packages/agent/src/actions/web-search.js +89 -19
  126. package/packages/agent/src/api/binance-skill-helpers.d.ts.map +1 -1
  127. package/packages/agent/src/api/binance-skill-helpers.js +1 -1
  128. package/packages/agent/src/api/chat-augmentation.d.ts +1 -20
  129. package/packages/agent/src/api/chat-augmentation.d.ts.map +1 -1
  130. package/packages/agent/src/api/chat-augmentation.js +2 -110
  131. package/packages/agent/src/api/conversation-metadata.d.ts +9 -0
  132. package/packages/agent/src/api/conversation-metadata.d.ts.map +1 -0
  133. package/packages/agent/src/api/conversation-metadata.js +98 -0
  134. package/packages/agent/src/api/conversation-routes.d.ts.map +1 -1
  135. package/packages/agent/src/api/conversation-routes.js +40 -9
  136. package/packages/agent/src/api/lifeops-browser-packaging.d.ts +1 -1
  137. package/packages/agent/src/api/lifeops-browser-packaging.d.ts.map +1 -1
  138. package/packages/agent/src/api/lifeops-browser-packaging.js +1 -1
  139. package/packages/agent/src/api/permissions-routes.js +1 -1
  140. package/packages/agent/src/api/server-helpers-auth.js +1 -1
  141. package/packages/agent/src/api/server-helpers.d.ts +2 -1
  142. package/packages/agent/src/api/server-helpers.d.ts.map +1 -1
  143. package/packages/agent/src/api/server-helpers.js +2 -110
  144. package/packages/agent/src/api/server-types.d.ts +14 -0
  145. package/packages/agent/src/api/server-types.d.ts.map +1 -1
  146. package/packages/agent/src/api/server.d.ts.map +1 -1
  147. package/packages/agent/src/api/server.js +3 -13
  148. package/packages/agent/src/api/workbench-routes.d.ts.map +1 -1
  149. package/packages/agent/src/api/workbench-routes.js +0 -14
  150. package/packages/agent/src/auth/credentials.d.ts.map +1 -1
  151. package/packages/agent/src/auth/credentials.js +8 -0
  152. package/packages/agent/src/config/zod-schema.d.ts +2 -2
  153. package/packages/agent/src/config/zod-schema.providers-core.d.ts +3 -3
  154. package/packages/agent/src/providers/automation-terminal-bridge.d.ts +3 -0
  155. package/packages/agent/src/providers/automation-terminal-bridge.d.ts.map +1 -0
  156. package/packages/agent/src/providers/automation-terminal-bridge.js +71 -0
  157. package/packages/agent/src/providers/index.d.ts +1 -0
  158. package/packages/agent/src/providers/index.d.ts.map +1 -1
  159. package/packages/agent/src/providers/index.js +1 -0
  160. package/packages/agent/src/providers/recent-conversations.d.ts.map +1 -1
  161. package/packages/agent/src/providers/recent-conversations.js +5 -0
  162. package/packages/agent/src/providers/relevant-conversations.d.ts.map +1 -1
  163. package/packages/agent/src/providers/relevant-conversations.js +5 -0
  164. package/packages/agent/src/providers/user-name.d.ts.map +1 -1
  165. package/packages/agent/src/providers/user-name.js +1 -1
  166. package/packages/agent/src/runtime/eliza-plugin.d.ts.map +1 -1
  167. package/packages/agent/src/runtime/eliza-plugin.js +6 -0
  168. package/packages/agent/src/runtime/plugin-resolver.d.ts.map +1 -1
  169. package/packages/agent/src/runtime/plugin-resolver.js +115 -0
  170. package/packages/agent/src/services/browser-workspace-types.d.ts +5 -1
  171. package/packages/agent/src/services/browser-workspace-types.d.ts.map +1 -1
  172. package/packages/agent/src/services/browser-workspace.d.ts.map +1 -1
  173. package/packages/agent/src/services/browser-workspace.js +178 -7
  174. package/packages/agent/src/services/coding-agent-context.d.ts +4 -4
  175. package/packages/agent/src/services/escalation.d.ts +0 -11
  176. package/packages/agent/src/services/escalation.d.ts.map +1 -1
  177. package/packages/agent/src/services/escalation.js +19 -58
  178. package/packages/agent/src/services/hosted-tools.d.ts +70 -0
  179. package/packages/agent/src/services/hosted-tools.d.ts.map +1 -0
  180. package/packages/agent/src/services/hosted-tools.js +87 -0
  181. package/packages/agent/src/services/owner-name.d.ts +4 -0
  182. package/packages/agent/src/services/owner-name.d.ts.map +1 -0
  183. package/packages/agent/src/services/owner-name.js +46 -0
  184. package/packages/agent/src/services/registry-client-queries.d.ts +1 -1
  185. package/packages/agent/src/services/registry-client-queries.js +1 -1
  186. package/packages/agent/src/services/registry-client-types.d.ts +1 -1
  187. package/packages/agent/src/services/stream-manager.d.ts +1 -1
  188. package/packages/app-core/src/config/boot-config-store.d.ts +1 -1
  189. package/packages/app-core/src/config/boot-config-store.d.ts.map +1 -1
  190. package/packages/shared/src/contracts/lifeops.d.ts +26 -0
  191. package/packages/shared/src/contracts/lifeops.d.ts.map +1 -1
  192. package/packages/shared/src/contracts/lifeops.js +28 -0
  193. package/packages/typescript/src/actions.d.ts.map +1 -1
  194. package/packages/typescript/src/actions.js +12 -0
  195. package/packages/typescript/src/features/advanced-capabilities/actions/scheduleFollowUp.d.ts.map +1 -1
  196. package/packages/typescript/src/features/advanced-capabilities/actions/scheduleFollowUp.js +8 -2
  197. package/packages/typescript/src/features/advanced-capabilities/providers/settings.d.ts.map +1 -1
  198. package/packages/typescript/src/features/advanced-capabilities/providers/settings.js +11 -3
  199. package/packages/typescript/src/features/basic-capabilities/providers/actions.d.ts.map +1 -1
  200. package/packages/typescript/src/features/basic-capabilities/providers/actions.js +23 -1
  201. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.d.ts +5 -0
  202. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.d.ts.map +1 -0
  203. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.js +22 -0
  204. package/packages/typescript/src/features/basic-capabilities/providers/providers.d.ts.map +1 -1
  205. package/packages/typescript/src/features/basic-capabilities/providers/providers.js +10 -5
  206. package/packages/typescript/src/features/knowledge/documents-provider.d.ts.map +1 -1
  207. package/packages/typescript/src/features/knowledge/documents-provider.js +1 -0
  208. package/packages/typescript/src/features/shared/schedule-follow-up-response.d.ts +10 -0
  209. package/packages/typescript/src/features/shared/schedule-follow-up-response.d.ts.map +1 -0
  210. package/packages/typescript/src/features/shared/schedule-follow-up-response.js +49 -0
  211. package/packages/typescript/src/features/trajectories/TrajectoriesService.d.ts +1 -0
  212. package/packages/typescript/src/features/trajectories/TrajectoriesService.d.ts.map +1 -1
  213. package/packages/typescript/src/features/trajectories/TrajectoriesService.js +21 -2
  214. package/packages/typescript/src/features/trust/providers/settings.d.ts.map +1 -1
  215. package/packages/typescript/src/features/trust/providers/settings.js +11 -1
  216. package/packages/typescript/src/prompts.d.ts +4 -4
  217. package/packages/typescript/src/prompts.d.ts.map +1 -1
  218. package/packages/typescript/src/prompts.js +10 -2
  219. package/packages/typescript/src/schemas/character.d.ts +3 -3
  220. package/packages/typescript/src/services/message.d.ts +1 -0
  221. package/packages/typescript/src/services/message.d.ts.map +1 -1
  222. package/packages/typescript/src/services/message.js +483 -18
  223. package/packages/typescript/src/types/components.d.ts +6 -0
  224. package/packages/typescript/src/types/components.d.ts.map +1 -1
  225. package/packages/typescript/src/utils/context-routing.d.ts.map +1 -1
  226. package/packages/typescript/src/utils/context-routing.js +5 -1
  227. package/packages/typescript/src/utils/toon.d.ts.map +1 -1
  228. package/packages/typescript/src/utils/toon.js +23 -2
  229. package/packages/typescript/src/utils.d.ts +1 -0
  230. package/packages/typescript/src/utils.d.ts.map +1 -1
  231. package/packages/typescript/src/utils.js +1 -0
  232. package/apps/app-lifeops/src/types/app-blocker-settings-card.d.ts +0 -2
  233. package/apps/app-lifeops/src/types/app-blocker-settings-card.d.ts.map +0 -1
  234. package/apps/app-lifeops/src/types/index.d.ts +0 -3
  235. package/apps/app-lifeops/src/types/index.d.ts.map +0 -1
  236. package/apps/app-lifeops/src/types/index.js +0 -1
  237. package/apps/app-lifeops/src/types/website-blocker-settings-card.d.ts +0 -2
  238. package/apps/app-lifeops/src/types/website-blocker-settings-card.d.ts.map +0 -1
  239. package/apps/app-lifeops/src/types/website-blocker-settings-card.js +0 -1
  240. /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
+ }